Border cases for doseq

108 views
Skip to first unread message

Cecil Westerhof

unread,
Feb 25, 2015, 11:59:32 AM2/25/15
to clo...@googlegroups.com
At the moment I have the following function:
    (defn do-show-table
      [table]
      (doseq [{:keys [table_name]} (show-tables db-spec)]
             (when (= (lower-case table) (lower-case table_name))
               (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                 (printf format "Field" "Type" "Null?" "Key" "Default")
                 (doseq [{:keys [field type null key default]}
                          (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]
                        (printf format field type null key default))))))

In this case it is not very important, because the outer sequence will not be very big. But I would like to leave the doseq at the moment that the when is executed. It will be done 0 or 1 times, so after it is done, there is no use in continuing the sequence walk.

The second part is that I would like to do an action if it is done 0 times after the doseq. Is this possible?

--
Cecil Westerhof

Andy Fingerhut

unread,
Feb 25, 2015, 12:14:17 PM2/25/15
to clo...@googlegroups.com
doseq does not have anything precisely like C/Java/Perl/etc.'s 'break' or 'continue'.  The closest thing might be the :while keyword.  You can see some one example of its use near the end of the examples on this page: http://clojuredocs.org/clojure.core/doseq

The best way I know to get such behavior is to use Clojure's loop, where if/when/whatever-conditional-statements-you-wish can used to control explicitly to do another loop iteration using recur, or not.

Andy

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Andy Fingerhut

unread,
Feb 25, 2015, 12:16:38 PM2/25/15
to clo...@googlegroups.com
Oh, and the page of examples for 'for' has more examples of :when and :while that work similarly to doseq, and may be more informative: http://clojuredocs.org/clojure.core/for

Cecil Westerhof

unread,
Feb 25, 2015, 1:08:23 PM2/25/15
to clo...@googlegroups.com
2015-02-25 18:14 GMT+01:00 Andy Fingerhut <andy.fi...@gmail.com>:
doseq does not have anything precisely like C/Java/Perl/etc.'s 'break' or 'continue'.  The closest thing might be the :while keyword.  You can see some one example of its use near the end of the examples on this page: http://clojuredocs.org/clojure.core/doseq

The best way I know to get such behavior is to use Clojure's loop, where if/when/whatever-conditional-statements-you-wish can used to control explicitly to do another loop iteration using recur, or not.

​I made the following with loop:
    (defn do-show-table
      [table]
      (loop [all-tables (show-tables db-spec)]
        (let [this-table (get (first all-tables) :table_name)]
          (if (= all-tables ())
              (printf "Table not found: %s\n" table)
            (if (= (lower-case table) (lower-case this-table))

                (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                  (printf "Table %s:\n" table)

                  (printf format "Field" "Type" "Null?" "Key" "Default")
                  (doseq [{:keys [field type null key default]}
                         (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]
                         (printf format field type null key default))
                  (println))
              (recur (rest all-tables)))))))

It solves both problems.


On Wed, Feb 25, 2015 at 8:59 AM, Cecil Westerhof <cldwes...@gmail.com> wrote:
At the moment I have the following function:
    (defn do-show-table
      [table]
      (doseq [{:keys [table_name]} (show-tables db-spec)]
             (when (= (lower-case table) (lower-case table_name))
               (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                 (printf format "Field" "Type" "Null?" "Key" "Default")
                 (doseq [{:keys [field type null key default]}
                          (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]
                        (printf format field type null key default))))))

In this case it is not very important, because the outer sequence will not be very big. But I would like to leave the doseq at the moment that the when is executed. It will be done 0 or 1 times, so after it is done, there is no use in continuing the sequence walk.

The second part is that I would like to do an action if it is done 0 times after the doseq. Is this possible?

--
Cecil Westerhof

Aaron Cohen

unread,
Feb 25, 2015, 1:23:50 PM2/25/15
to clo...@googlegroups.com
On Wed, Feb 25, 2015 at 1:08 PM, Cecil Westerhof <cldwes...@gmail.com> wrote:


2015-02-25 18:14 GMT+01:00 Andy Fingerhut <andy.fi...@gmail.com>:
doseq does not have anything precisely like C/Java/Perl/etc.'s 'break' or 'continue'.  The closest thing might be the :while keyword.  You can see some one example of its use near the end of the examples on this page: http://clojuredocs.org/clojure.core/doseq

The best way I know to get such behavior is to use Clojure's loop, where if/when/whatever-conditional-statements-you-wish can used to control explicitly to do another loop iteration using recur, or not.

​I made the following with loop:
    (defn do-show-table
      [table]
      (loop [all-tables (show-tables db-spec)]
        (let [this-table (get (first all-tables) :table_name)]
          (if (= all-tables ())
              (printf "Table not found: %s\n" table)
            (if (= (lower-case table) (lower-case this-table))
                (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                  (printf "Table %s:\n" table)
                  (printf format "Field" "Type" "Null?" "Key" "Default")
                  (doseq [{:keys [field type null key default]}
                         (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]

Please don't get into the habit of doing db queries like this, you're one "Little Bobby Tables" away from an sql injection. In this case you should do (jdbc/query db-spec ["SHOW COLUMNS FROM ?" table]).

I think you would be well-served by a general clojure principle here of trying to get the data you're are dealing within into "values" as quickly as possible, and then working with those.

Rather than inter-mixing your querying and display as you're doing here, I would design this as a query to fill some maps describing your tables, and then use clojure.pprint/print-table to print out the resulting map when desired.

--Aaron



 
                         (printf format field type null key default))
                  (println))
              (recur (rest all-tables)))))))

It solves both problems.


On Wed, Feb 25, 2015 at 8:59 AM, Cecil Westerhof <cldwes...@gmail.com> wrote:
At the moment I have the following function:
    (defn do-show-table
      [table]
      (doseq [{:keys [table_name]} (show-tables db-spec)]
             (when (= (lower-case table) (lower-case table_name))
               (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                 (printf format "Field" "Type" "Null?" "Key" "Default")
                 (doseq [{:keys [field type null key default]}
                          (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]
                        (printf format field type null key default))))))

In this case it is not very important, because the outer sequence will not be very big. But I would like to leave the doseq at the moment that the when is executed. It will be done 0 or 1 times, so after it is done, there is no use in continuing the sequence walk.

The second part is that I would like to do an action if it is done 0 times after the doseq. Is this possible?

--
Cecil Westerhof

--

Cecil Westerhof

unread,
Feb 25, 2015, 1:35:17 PM2/25/15
to clo...@googlegroups.com
2015-02-25 19:23 GMT+01:00 Aaron Cohen <aa...@assonance.org>:
On Wed, Feb 25, 2015 at 1:08 PM, Cecil Westerhof <cldwes...@gmail.com> wrote:


2015-02-25 18:14 GMT+01:00 Andy Fingerhut <andy.fi...@gmail.com>:
doseq does not have anything precisely like C/Java/Perl/etc.'s 'break' or 'continue'.  The closest thing might be the :while keyword.  You can see some one example of its use near the end of the examples on this page: http://clojuredocs.org/clojure.core/doseq

The best way I know to get such behavior is to use Clojure's loop, where if/when/whatever-conditional-statements-you-wish can used to control explicitly to do another loop iteration using recur, or not.

​I made the following with loop:
    (defn do-show-table
      [table]
      (loop [all-tables (show-tables db-spec)]
        (let [this-table (get (first all-tables) :table_name)]
          (if (= all-tables ())
              (printf "Table not found: %s\n" table)
            (if (= (lower-case table) (lower-case this-table))
                (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                  (printf "Table %s:\n" table)
                  (printf format "Field" "Type" "Null?" "Key" "Default")
                  (doseq [{:keys [field type null key default]}
                         (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]

Please don't get into the habit of doing db queries like this, you're one "Little Bobby Tables" away from an sql injection. In this case you should do (jdbc/query db-spec ["SHOW COLUMNS FROM ?" table]).

​That gives:
    JdbcSQLException Syntax error in SQL statement "SHOW COLUMNS FROM ?"; expected "identifier"; SQL statement:
    SHOW COLUMNS FROM ? [42001-184]  org.h2.engine.SessionRemote.done (SessionRemote.java:622)

​That is why I do it in this way. As I understood it this has to do with that with a 'show columns from' the table can not be supplied. (I first tried Yesql, only when that did not work I went to JDBC.)​
 

 
I think you would be well-served by a general clojure principle here of trying to get the data you're are dealing within into "values" as quickly as possible, and then working with those.

Rather than inter-mixing your querying and display as you're doing here, I would design this as a query to fill some maps describing your tables, and then use clojure.pprint/print-table to print out the resulting map when desired.

​This was just a proof of concept to get things working in the REPL. Later on it should be done much neater. (For example a text​
 
​based version and a GUI version.)​

 
                         (printf format field type null key default))

                  (println))
              (recur (rest all-tables)))))))

It solves both problems.


On Wed, Feb 25, 2015 at 8:59 AM, Cecil Westerhof <cldwes...@gmail.com> wrote:
At the moment I have the following function:
    (defn do-show-table
      [table]
      (doseq [{:keys [table_name]} (show-tables db-spec)]
             (when (= (lower-case table) (lower-case table_name))
               (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                 (printf format "Field" "Type" "Null?" "Key" "Default")
                 (doseq [{:keys [field type null key default]}
                          (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]
                        (printf format field type null key default))))))

In this case it is not very important, because the outer sequence will not be very big. But I would like to leave the doseq at the moment that the when is executed. It will be done 0 or 1 times, so after it is done, there is no use in continuing the sequence walk.

The second part is that I would like to do an action if it is done 0 times after the doseq. Is this possible?

--
Cecil Westerhof

Aaron Cohen

unread,
Feb 25, 2015, 1:42:03 PM2/25/15
to clo...@googlegroups.com
On Wed, Feb 25, 2015 at 1:35 PM, Cecil Westerhof <cldwes...@gmail.com> wrote:
2015-02-25 19:23 GMT+01:00 Aaron Cohen <aa...@assonance.org>:
On Wed, Feb 25, 2015 at 1:08 PM, Cecil Westerhof <cldwes...@gmail.com> wrote:


2015-02-25 18:14 GMT+01:00 Andy Fingerhut <andy.fi...@gmail.com>:
doseq does not have anything precisely like C/Java/Perl/etc.'s 'break' or 'continue'.  The closest thing might be the :while keyword.  You can see some one example of its use near the end of the examples on this page: http://clojuredocs.org/clojure.core/doseq

The best way I know to get such behavior is to use Clojure's loop, where if/when/whatever-conditional-statements-you-wish can used to control explicitly to do another loop iteration using recur, or not.

​I made the following with loop:
    (defn do-show-table
      [table]
      (loop [all-tables (show-tables db-spec)]
        (let [this-table (get (first all-tables) :table_name)]
          (if (= all-tables ())
              (printf "Table not found: %s\n" table)
            (if (= (lower-case table) (lower-case this-table))
                (let [format "%-20s %-30s %-5s %-5s %-20s\n"]
                  (printf "Table %s:\n" table)
                  (printf format "Field" "Type" "Null?" "Key" "Default")
                  (doseq [{:keys [field type null key default]}
                         (jdbc/query db-spec [(str "SHOW COLUMNS FROM " table)])]

Please don't get into the habit of doing db queries like this, you're one "Little Bobby Tables" away from an sql injection. In this case you should do (jdbc/query db-spec ["SHOW COLUMNS FROM ?" table]).

​That gives:
    JdbcSQLException Syntax error in SQL statement "SHOW COLUMNS FROM ?"; expected "identifier"; SQL statement:
    SHOW COLUMNS FROM ? [42001-184]  org.h2.engine.SessionRemote.done (SessionRemote.java:622)

​That is why I do it in this way. As I understood it this has to do with that with a 'show columns from' the table can not be supplied. (I first tried Yesql, only when that did not work I went to JDBC.)​
 

I see, sorry to jump the gun and be incorrect while doing so. 

Erik Price

unread,
Feb 25, 2015, 1:42:37 PM2/25/15
to clo...@googlegroups.com

Another way to structure this problem is as sequences:

  1. Start with a sequence of tables. (show-tables db-spec)
  2. Filter down to only the tables you care about. (filter my-pred tables-seq)
  3. Take only the first of these. (take 1 filtered-tables-seq)
  4. If there is a table, do your side effects with it. (doseq [t 0-or-1-filtered-tables] (...))

That way, you will only do the work zero or one times, but the depth of your code is much shallower, and easier to read.

e


--

Aaron Cohen

unread,
Feb 25, 2015, 2:13:39 PM2/25/15
to clo...@googlegroups.com
As penance, here's an example that is closer to what I was describing:

(require '[clojure.java.jdbc :as j])
(require '[clojure.pprint :as pp])

(def db-spec 
    {:subprotocol "derby" 
     :classname "org.apache.derby.jdbc.EmbeddedDriver"
     :subname "testDb"
     :create true})

(j/db-do-commands db-spec 
    (j/create-table-ddl :fruit
        [:name "varchar(32)" :primary :key]
        [:appearance "varchar(32)"]
        [:cost :int]
        [:grade :real]))

(j/db-do-commands db-spec 
    (j/create-table-ddl :vegetables
        [:name "varchar(32)" :primary :key]
        [:appearance "varchar(32)"]
        [:cost :int]
        [:grade :real]))

;; Print the columns from all application tables
(pp/print-table [:table_name :column_name :type_name :column_size] 
   (j/with-db-metadata [md db-spec] 
      (j/metadata-result (.getColumns md nil "APP" nil nil))))


-----------------------------

This produces:

| :table_name | :column_name | :type_name | :column_size |
|-------------+--------------+------------+--------------|
|       FRUIT |         NAME |    VARCHAR |           32 |
|       FRUIT |   APPEARANCE |    VARCHAR |           32 |
|       FRUIT |         COST |    INTEGER |           10 |
|       FRUIT |        GRADE |       REAL |           23 |
|  VEGETABLES |         NAME |    VARCHAR |           32 |
|  VEGETABLES |   APPEARANCE |    VARCHAR |           32 |
|  VEGETABLES |         COST |    INTEGER |           10 |
|  VEGETABLES |        GRADE |       REAL |           23 |

Sean Corfield

unread,
Feb 25, 2015, 2:22:50 PM2/25/15
to clo...@googlegroups.com
Cecil,

Have you looked at the community documentation for java.jdbc? It has some examples of DDL and metadata:


Sean
Reply all
Reply to author
Forward
0 new messages