Skip to content
Snippets Groups Projects
Unverified Commit 6adb3a6a authored by Cam Saul's avatar Cam Saul
Browse files

Test suite improvements & other code cleanup :shower:

parent 006a1801
No related branches found
No related tags found
No related merge requests found
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
The aim here is to completely encapsulate the connection pool library we use -- that way we can swap it out if we The aim here is to completely encapsulate the connection pool library we use -- that way we can swap it out if we
want to at some point without having to touch any other files. (TODO - this is currently true of everything except want to at some point without having to touch any other files. (TODO - this is currently true of everything except
for the options, which are c3p0-specific -- consider abstracting those as well?)" for the options, which are c3p0-specific -- consider abstracting those as well?)"
(:require [metabase.util :as u]) (:require [metabase.util :as u]
[metabase.util.schema :as su]
[schema.core :as s])
(:import com.mchange.v2.c3p0.DataSources (:import com.mchange.v2.c3p0.DataSources
[java.sql Driver DriverManager] [java.sql Driver DriverManager]
[java.util Map Properties] [java.util Map Properties]
...@@ -46,13 +48,19 @@ ...@@ -46,13 +48,19 @@
(defn- spec->properties ^Properties [spec] (defn- spec->properties ^Properties [spec]
(map->properties (dissoc spec :classname :subprotocol :subname))) (map->properties (dissoc spec :classname :subprotocol :subname)))
(defn- unpooled-data-source ^DataSource [{:keys [subname subprotocol], :as spec}] (def ^:private JDBCSpec
{:pre [(string? subname) (string? subprotocol)]} {:subname su/NonBlankString
:subprotocol su/NonBlankString
s/Any s/Any})
(s/defn ^:private unpooled-data-source :- DataSource
[{:keys [subname subprotocol], :as spec} :- JDBCSpec]
(proxy-data-source (format "jdbc:%s:%s" subprotocol subname) (spec->properties spec))) (proxy-data-source (format "jdbc:%s:%s" subprotocol subname) (spec->properties spec)))
(defn- pooled-data-source ^DataSource (defn- pooled-data-source ^DataSource
([spec] ([spec]
(DataSources/pooledDataSource (unpooled-data-source spec))) (DataSources/pooledDataSource (unpooled-data-source spec)))
([spec, ^Map pool-properties] ([spec, ^Map pool-properties]
(DataSources/pooledDataSource (unpooled-data-source spec), pool-properties))) (DataSources/pooledDataSource (unpooled-data-source spec), pool-properties)))
......
...@@ -79,7 +79,7 @@ ...@@ -79,7 +79,7 @@
[_ database] [_ database]
(set-pool! (u/get-id database) nil)) (set-pool! (u/get-id database) nil))
(def ^:private db->pooled-spec-lock (Object.)) (def ^:private create-pool-lock (Object.))
(defn db->pooled-connection-spec (defn db->pooled-connection-spec
"Return a JDBC connection spec that includes a cp30 `ComboPooledDataSource`. "Return a JDBC connection spec that includes a cp30 `ComboPooledDataSource`.
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
;; don't want to end up with a bunch of simultaneous threads creating pools only to have them destroyed the very ;; don't want to end up with a bunch of simultaneous threads creating pools only to have them destroyed the very
;; next instant. This will cause their queries to fail. Thus we should do the usual locking here and make sure only ;; next instant. This will cause their queries to fail. Thus we should do the usual locking here and make sure only
;; one thread will be creating a pool at a given instant. ;; one thread will be creating a pool at a given instant.
(locking db->pooled-spec-lock (locking create-pool-lock
(or (or
;; check if another thread created the pool while we were waiting to acquire the lock ;; check if another thread created the pool while we were waiting to acquire the lock
(get @database-id->connection-pool (u/get-id database-or-id)) (get @database-id->connection-pool (u/get-id database-or-id))
......
...@@ -23,9 +23,8 @@ ...@@ -23,9 +23,8 @@
[toucan.db :as db]) [toucan.db :as db])
(:import [java.io File IOException])) (:import [java.io File IOException]))
(when-not *compile-files* (when config/is-dev?
(when config/is-dev? (alter-meta! #'stencil.core/render-file assoc :style/indent 1))
(alter-meta! #'stencil.core/render-file assoc :style/indent 1)))
;; Dev only -- disable template caching ;; Dev only -- disable template caching
(when config/is-dev? (when config/is-dev?
......
...@@ -99,11 +99,11 @@ ...@@ -99,11 +99,11 @@
;;; ------------------------------------------------------ list ------------------------------------------------------ ;;; ------------------------------------------------------ list ------------------------------------------------------
(defn- formar-cards-list (defn- format-cards-list
"Format a sequence of Cards as a nice multiline list for use in responses." "Format a sequence of Cards as a nice multiline list for use in responses."
[cards] [cards]
(apply str (interpose "\n" (for [{id :id, card-name :name} cards] (str/join "\n" (for [{id :id, card-name :name} cards]
(format "%d. <%s|\"%s\">" id (urls/card-url id) card-name))))) (format "%d. <%s|\"%s\">" id (urls/card-url id) card-name))))
(defn- list-cards [] (defn- list-cards []
(filter-metabot-readable (filter-metabot-readable
...@@ -127,7 +127,7 @@ ...@@ -127,7 +127,7 @@
(let [cards (list-cards)] (let [cards (list-cards)]
(str (tru "Here''s your {0} most recent cards:" (count cards)) (str (tru "Here''s your {0} most recent cards:" (count cards))
"\n" "\n"
(formar-cards-list cards)))) (format-cards-list cards))))
;;; ------------------------------------------------------ show ------------------------------------------------------ ;;; ------------------------------------------------------ show ------------------------------------------------------
...@@ -135,7 +135,9 @@ ...@@ -135,7 +135,9 @@
(defn- cards-with-name [card-name] (defn- cards-with-name [card-name]
(db/select [Card :id :name] (db/select [Card :id :name]
:%lower.name [:like (str \% (str/lower-case card-name) \%)] :%lower.name [:like (str \% (str/lower-case card-name) \%)]
:archived false)) :archived false
{:order-by [[:%lower.name :asc]]
:limit 10}))
(defn- card-with-name [card-name] (defn- card-with-name [card-name]
(let [[first-card & more, :as cards] (cards-with-name card-name)] (let [[first-card & more, :as cards] (cards-with-name card-name)]
...@@ -145,7 +147,7 @@ ...@@ -145,7 +147,7 @@
(str (str
(tru "Could you be a little more specific, or use the ID? I found these cards with names that matched:") (tru "Could you be a little more specific, or use the ID? I found these cards with names that matched:")
"\n" "\n"
(formar-cards-list cards))))) (format-cards-list cards)))))
first-card)) first-card))
(defn- id-or-name->card [card-id-or-name] (defn- id-or-name->card [card-id-or-name]
......
...@@ -21,7 +21,9 @@ ...@@ -21,7 +21,9 @@
:stddev :stddev
:standard-deviation-aggregations :standard-deviation-aggregations
:joined-field ;; `:fk->` is normally replaced by `:joined-field` already but the middleware that does the replacement won't run
;; if the driver doesn't support foreign keys, meaning the clauses can leak thru
#{:joined-field :fk->}
:foreign-keys)) :foreign-keys))
(defn- check-features* [{query-type :type, :as query}] (defn- check-features* [{query-type :type, :as query}]
......
...@@ -182,15 +182,18 @@ ...@@ -182,15 +182,18 @@
resolved-source-query (assoc-in [:query :source-query] resolved-source-query)))) resolved-source-query (assoc-in [:query :source-query] resolved-source-query))))
(defn- resolve-joined-tables* [{query-type :type, :as query}] (defn- resolve-joined-tables* [{query-type :type, :as query}]
;; if this is a native query, or if `driver/*driver*` is bound *and* it DOES NOT support `:foreign-keys`, return
;; query as is. Otherwise add implicit joins for `fk->` clauses
(if (or (= query-type :native) (if (or (= query-type :native)
(and driver/*driver* (some-> driver/*driver* ((complement driver/supports?) :foreign-keys)))
(not (driver/supports? driver/*driver* :foreign-keys))))
query query
(resolve-joined-tables-in-query-all-levels query))) (resolve-joined-tables-in-query-all-levels query)))
(defn resolve-joined-tables (defn resolve-joined-tables
"Fetch and store any Tables other than the source Table referred to by `fk->` clauses in an MBQL query, and add a "Fetch and store any Tables other than the source Table referred to by `fk->` clauses in an MBQL query, and add a
`:join-tables` key inside the MBQL inner query dictionary containing information about the `JOIN`s (or equivalent) `:join-tables` key inside the MBQL inner query containing information about the `JOIN`s (or equivalent) that need to
that need to be performed for these tables." be performed for these tables.
This middleware also replaces all `fk->` clauses with `joined-field` clauses, which are easier to work with."
[qp] [qp]
(comp qp resolve-joined-tables*)) (comp qp resolve-joined-tables*))
...@@ -280,46 +280,36 @@ ...@@ -280,46 +280,36 @@
"Get the stack trace associated with E and return it as a vector with non-metabase frames after the last Metabase "Get the stack trace associated with E and return it as a vector with non-metabase frames after the last Metabase
frame filtered out.")) frame filtered out."))
;; These next two functions are a workaround for this bug https://dev.clojure.org/jira/browse/CLJ-1790 (extend-protocol IFilteredStacktrace
;; When Throwable/Thread are type-hinted, they return an array of type StackTraceElement, this causes nil
;; a VerifyError. Adding a layer of indirection here avoids the problem. Once we upgrade to Clojure 1.9 (filtered-stacktrace [_] nil)
;; we should be able to remove this code.
(defn- throwable-get-stack-trace [^Throwable t]
(.getStackTrace t))
(defn- thread-get-stack-trace [^Thread t] Throwable
(.getStackTrace t)) (filtered-stacktrace [^Throwable this]
(filtered-stacktrace (.getStackTrace this)))
(extend nil Thread
IFilteredStacktrace {:filtered-stacktrace (constantly nil)}) (filtered-stacktrace [^Thread this]
(filtered-stacktrace (.getStackTrace this))))
(extend Throwable
IFilteredStacktrace {:filtered-stacktrace (fn [this]
(filtered-stacktrace (throwable-get-stack-trace this)))})
(extend Thread
IFilteredStacktrace {:filtered-stacktrace (fn [this]
(filtered-stacktrace (thread-get-stack-trace this)))})
(defn- metabase-frame? [frame]
(re-find #"metabase" (str frame)))
;; StackTraceElement[] is what the `.getStackTrace` method for Thread and Throwable returns
(extend (Class/forName "[Ljava.lang.StackTraceElement;") (extend (Class/forName "[Ljava.lang.StackTraceElement;")
IFilteredStacktrace IFilteredStacktrace
{:filtered-stacktrace {:filtered-stacktrace
(fn [this] (fn [this]
;; keep all the frames before the last Metabase frame, but then filter out any other non-Metabase frames after ;; keep all the frames before the last Metabase frame, but then filter out any other non-Metabase frames after
;; that ;; that
(let [[frames-after-last-mb other-frames] (split-with (complement metabase-frame?) (let [[frames-after-last-mb other-frames] (split-with #(not (str/includes? % "metabase"))
(map str (seq this))) (seq this))
[last-mb-frame & frames-before-last-mb] (map #(str/replace % #"^metabase\." "") [last-mb-frame & frames-before-last-mb] (for [frame other-frames
(filter metabase-frame? other-frames))] :when (str/includes? frame "metabase")]
(str/replace frame #"^metabase\." ""))]
(concat (concat
frames-after-last-mb (map str frames-after-last-mb)
;; add a little arrow to the frame so it stands out more ;; add a little arrow to the frame so it stands out more
(cons (some->> last-mb-frame (str "--> ")) (cons
frames-before-last-mb))))}) (some->> last-mb-frame (str "--> "))
frames-before-last-mb))))})
(defn deref-with-timeout (defn deref-with-timeout
"Call `deref` on a something derefable (e.g. a future or promise), and throw an exception if it takes more than "Call `deref` on a something derefable (e.g. a future or promise), and throw an exception if it takes more than
......
...@@ -11,10 +11,9 @@ ...@@ -11,10 +11,9 @@
(:import honeysql.format.ToSql (:import honeysql.format.ToSql
java.util.Locale)) java.util.Locale))
(when-not *compile-files* (when config/is-dev?
(when config/is-dev? (alter-meta! #'honeysql.core/format assoc :style/indent 1)
(alter-meta! #'honeysql.core/format assoc :style/indent 1) (alter-meta! #'honeysql.core/call assoc :style/indent 1))
(alter-meta! #'honeysql.core/call assoc :style/indent 1)))
(defn- english-upper-case (defn- english-upper-case
"Use this function when you need to upper-case an identifier or table name. Similar to `clojure.string/upper-case` "Use this function when you need to upper-case an identifier or table name. Similar to `clojure.string/upper-case`
...@@ -78,6 +77,11 @@ ...@@ -78,6 +77,11 @@
(:components component) (:components component)
[component])] [component])]
component))) component)))
;; don't use `->Identifier` or `map->Identifier`. Use the `identifier` function instead, which cleans up its input
(when-not config/is-prod?
(alter-meta! #'->Identifier assoc :private true)
(alter-meta! #'map->Identifier assoc :private true))
;; Single-quoted string literal ;; Single-quoted string literal
(defrecord Literal [literal] (defrecord Literal [literal]
...@@ -91,6 +95,11 @@ ...@@ -91,6 +95,11 @@
(pretty [_] (pretty [_]
(list 'literal literal))) (list 'literal literal)))
;; as with `Identifier` you should use the the `literal` function below instead of the auto-generated factory functions.
(when-not config/is-prod?
(alter-meta! #'->Literal assoc :private true)
(alter-meta! #'map->Literal assoc :private true))
(defn literal (defn literal
"Wrap keyword or string `s` in single quotes and a HoneySQL `raw` form. "Wrap keyword or string `s` in single quotes and a HoneySQL `raw` form.
...@@ -107,8 +116,8 @@ ...@@ -107,8 +116,8 @@
(def ^{:arglists '([& exprs])} * "Math operator. Interpose `*` between `exprs` and wrap in parentheses." (partial hsql/call :*)) (def ^{:arglists '([& exprs])} * "Math operator. Interpose `*` between `exprs` and wrap in parentheses." (partial hsql/call :*))
(def ^{:arglists '([& exprs])} mod "Math operator. Interpose `%` between `exprs` and wrap in parentheses." (partial hsql/call :%)) (def ^{:arglists '([& exprs])} mod "Math operator. Interpose `%` between `exprs` and wrap in parentheses." (partial hsql/call :%))
(defn inc "Add 1 to X." [x] (+ x 1)) (defn inc "Add 1 to `x`." [x] (+ x 1))
(defn dec "Subtract 1 from X." [x] (- x 1)) (defn dec "Subtract 1 from `x`." [x] (- x 1))
(defn cast (defn cast
...@@ -134,13 +143,13 @@ ...@@ -134,13 +143,13 @@
[x decimal-places] [x decimal-places]
(hsql/call :round x decimal-places)) (hsql/call :round x decimal-places))
(defn ->date "CAST X to a `date`." [x] (cast :date x)) (defn ->date "CAST `x` to a `date`." [x] (cast :date x))
(defn ->datetime "CAST X to a `datetime`." [x] (cast :datetime x)) (defn ->datetime "CAST `x` to a `datetime`." [x] (cast :datetime x))
(defn ->timestamp "CAST X to a `timestamp`." [x] (cast :timestamp x)) (defn ->timestamp "CAST `x` to a `timestamp`." [x] (cast :timestamp x))
(defn ->timestamp-with-time-zone "CAST X to a `timestamp with time zone`." [x] (cast "timestamp with time zone" x)) (defn ->timestamp-with-time-zone "CAST `x` to a `timestamp with time zone`." [x] (cast "timestamp with time zone" x))
(defn ->integer "CAST X to a `integer`." [x] (cast :integer x)) (defn ->integer "CAST `x` to a `integer`." [x] (cast :integer x))
(defn ->time "CAST X to a `time` datatype" [x] (cast :time x)) (defn ->time "CAST `x` to a `time` datatype" [x] (cast :time x))
(defn ->boolean "CAST X to a `boolean` datatype" [x] (cast :boolean x)) (defn ->boolean "CAST `x` to a `boolean` datatype" [x] (cast :boolean x))
;;; Random SQL fns. Not all DBs support all these! ;;; Random SQL fns. Not all DBs support all these!
(def ^{:arglists '([& exprs])} floor "SQL `floor` function." (partial hsql/call :floor)) (def ^{:arglists '([& exprs])} floor "SQL `floor` function." (partial hsql/call :floor))
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
java.util.Map)) java.util.Map))
(defprotocol PrettyPrintable (defprotocol PrettyPrintable
"Implmement this protocol to return custom representations of objects when printing them." "Implmement this protocol to return custom representations of objects when printing them. This only seems to work if
it's done as part of the type declaration (`defrecord`); it doesn't seem to be respected if you use
`extend-protocol` for an existing type. Not sure why this is :("
(pretty [_] (pretty [_]
"Return an appropriate representation of this object to be used when printing it, such as in the REPL or in log "Return an appropriate representation of this object to be used when printing it, such as in the REPL or in log
messages.")) messages."))
......
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
[set :as set]] [set :as set]]
[expectations :as expectations] [expectations :as expectations]
[metabase.util :as u] [metabase.util :as u]
[metabase.util.date :as du]) [metabase.util.date :as du]))
(:import java.util.concurrent.TimeoutException))
;;; ---------------------------------------- Expectations Framework Settings ----------------------------------------- ;;; ---------------------------------------- Expectations Framework Settings -----------------------------------------
...@@ -62,12 +61,14 @@ ...@@ -62,12 +61,14 @@
[run] [run]
(fn [test-fn] (fn [test-fn]
(let [start-time-ms (System/currentTimeMillis)] (let [start-time-ms (System/currentTimeMillis)]
(u/prog1 (run test-fn) (run test-fn)
(let [duration-ms (- (System/currentTimeMillis) start-time-ms)] (let [duration-ms (- (System/currentTimeMillis) start-time-ms)]
(when (> duration-ms slow-test-threshold-ms) (when (> duration-ms slow-test-threshold-ms)
(let [{:keys [file line]} (-> test-fn meta :the-var meta)] (println
(println (u/format-color 'red "%s %s is a slow test! It took %s to finish." (let [{:keys [file line]} (-> test-fn meta :the-var meta)]
file line (du/format-milliseconds duration-ms)))))))))) (u/format-color 'red "%s %s is a slow test! It took %s to finish."
file line (du/format-milliseconds duration-ms)))
"(This may have been because it was loading test data.)"))))))
;;; ------------------------------------------------ enforce-timeout ------------------------------------------------- ;;; ------------------------------------------------ enforce-timeout -------------------------------------------------
...@@ -75,18 +76,22 @@ ...@@ -75,18 +76,22 @@
(def ^:private test-timeout-ms (* 60 1000)) (def ^:private test-timeout-ms (* 60 1000))
(defn- enforce-timeout (defn- enforce-timeout
"If any test takes longer that 60 seconds to run return a TimeoutException, effectively failing the test." "If any test takes longer that 60 seconds to run print a message and stop running tests. (This usually happens when
something is fundamentally broken, and we don't want to continue running thousands of tests that can hang for a
minute each.)"
[run] [run]
(fn [test-fn] (fn [test-fn]
(deref (when (= (deref (future (run test-fn)) test-timeout-ms ::timed-out)
(future ::timed-out)
(try (let [{:keys [file line]} (-> test-fn meta :the-var meta)]
(run test-fn) (println
(catch Throwable e (u/format-color 'red "%s %s timed out after %s" file line (du/format-milliseconds test-timeout-ms)))
e))) (println "Stacktraces:")
test-timeout-ms (doseq [[thread stacktrace] (Thread/getAllStackTraces)]
;; return Exception than throwing, otherwise it will mess up our test running (println "\n" (u/pprint-to-str 'red thread))
(TimeoutException. (format "Test timed out after %s" (du/format-milliseconds test-timeout-ms)))))) (doseq [frame stacktrace]
(println frame))))
(System/exit 1))))
;;; ---------------------------------------------- check-table-cleanup ----------------------------------------------- ;;; ---------------------------------------------- check-table-cleanup -----------------------------------------------
...@@ -101,12 +106,9 @@ ...@@ -101,12 +106,9 @@
message" message"
[models-to-check] [models-to-check]
(for [model models-to-check (for [model models-to-check
:let [instances-found (count (model)) :let [instances-found (count (model))]
more-than-one? (> 1 instances-found)] :when (pos? instances-found)]
:when (< 0 instances-found)] (format "Found %d instances of %s that were not cleaned up." instances-found (:name model))))
(str "Found '" instances-found "' instance" (when more-than-one? "s")
" of '" (:name model) "' that " (if more-than-one? "were" "was")
" not cleaned up.")))
(def ^{:arglists '([run])} check-table-cleanup (def ^{:arglists '([run])} check-table-cleanup
"Function that will run around each test. This function is usually a noop, but it useful for helping to debug stale "Function that will run around each test. This function is usually a noop, but it useful for helping to debug stale
...@@ -117,34 +119,37 @@ ...@@ -117,34 +119,37 @@
identity identity
(fn [run] (fn [run]
(fn [test-fn] (fn [test-fn]
(let [result (run test-fn) (run test-fn)
{:keys [file line]} (-> test-fn meta :the-var meta) (let [error-msgs (tables-with-data->error-msg models-to-check)]
error-msgs (tables-with-data->error-msg models-to-check)]
(when (seq error-msgs) (when (seq error-msgs)
(println "\n-----------------------------------------------------") (println "\n-----------------------------------------------------")
(doseq [error-msg error-msgs] (doseq [error-msg error-msgs]
(println error-msg)) (println error-msg))
(println "-----------------------------------------------------") (println "-----------------------------------------------------")
(printf "\nStale test rows found in tables, check '%s' at line '%s'\n\n" file line) (let [{:keys [file line]} (-> test-fn meta :the-var meta)]
(printf "\nStale test rows found in tables, check '%s' at line '%s'\n\n" file line))
(flush) (flush)
;; I found this necessary as throwing an exception would show the exception, but the test run would hang and ;; I found this necessary as throwing an exception would show the exception, but the test run would hang and
;; you'd have to Ctrl-C anyway ;; you'd have to Ctrl-C anyway
(System/exit 1)) (System/exit 1)))))))
result)))))
;;; -------------------------------------------- Putting it all together --------------------------------------------- ;;; -------------------------------------------- Putting it all together ---------------------------------------------
(defn- log-tests [run] (defn- log-tests [run]
(fn [test-fn] (comp
(let [{:keys [file line]} (-> test-fn meta :the-var meta)] run
(println (format "Run %s %s" file line))) (fn [test-fn]
(run test-fn))) (let [{:keys [file line]} (-> test-fn meta :the-var meta)]
(println (format "Run %s %s" file line)))
test-fn)))
;; This middleware does not need to worry about passing results to the next function in the chain; `test-fn` always
;; returns `nil`
(def ^:private ^{:expectations-options :in-context} test-middleware (def ^:private ^{:expectations-options :in-context} test-middleware
(-> (fn [test-fn] (-> (fn [test-fn]
(test-fn)) (test-fn))
;; uncomment `log-tests` if you need to debug tests or see which ones are being noisy ;; uncomment `log-tests` if you need to debug tests or see which ones are being noisy or hanging forever
#_log-tests #_log-tests
log-slow-tests log-slow-tests
enforce-timeout enforce-timeout
......
...@@ -294,27 +294,36 @@ ...@@ -294,27 +294,36 @@
this behavior." this behavior."
{:style/indent 1} {:style/indent 1}
([format-fns rows] ([format-fns rows]
(format-rows-by format-fns (not :format-nil-values?) rows)) (format-rows-by format-fns false rows))
([format-fns format-nil-values? rows]
(cond ([format-fns format-nil-values? response]
(= (:status rows) :failed) (when (= (:status response) :failed)
(do (println "Error running query:" (u/pprint-to-str 'red rows)) (println "Error running query:" (u/pprint-to-str 'red response))
(throw (ex-info (:error rows) rows))) (throw (ex-info (:error response) response)))
(:data rows) (-> response
(update-in rows [:data :rows] (partial format-rows-by format-fns)) ((fn format-rows [rows]
(cond
(:rows rows) (:data rows)
(update rows :rows (partial format-rows-by format-fns)) (update rows :data format-rows)
:else (:rows rows)
(vec (for [row rows] (update rows :rows format-rows)
(vec (for [[f v] (partition 2 (interleave format-fns row))]
(when (or v format-nil-values?) (sequential? rows)
(try (f v) (vec
(catch Throwable e (for [row rows]
(printf "(%s %s) failed: %s" f v (.getMessage e)) (vec
(throw e))))))))))) (for [[f v] (partition 2 (interleave format-fns row))]
(when (or v format-nil-values?)
(try
(f v)
(catch Throwable e
(printf "(%s %s) failed: %s" f v (.getMessage e))
(throw e))))))))
:else
(throw (ex-info "Unexpected response: rows are not sequential!" {:response response}))))))))
(def ^{:arglists '([results])} formatted-venues-rows (def ^{:arglists '([results])} formatted-venues-rows
"Helper function to format the rows in `results` when running a 'raw data' query against the Venues test table." "Helper function to format the rows in `results` when running a 'raw data' query against the Venues test table."
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment