Back

metadata (clj)

(source)

protocol

(metadata this)
Return the raw `ResultSetMetaData` object from the result set. Should not cause any row realization. If `next.jdbc.datafy` has been required, this metadata will be fully-realized as a Clojure data structure, otherwise this should not be allowed to 'leak' outside of the reducing function as it may depend on the connection remaining open, in order to be valid.

Examples

next-jdbc
  What's left to be tested:
  * ReadableColumn protocol extension point"
  (:require [clojure.core.protocols :as core-p]
            [clojure.datafy :as d]
            [clojure.string :as str]
            [clojure.test :refer [deftest is testing use-fixtures]]
            [next.jdbc.protocols :as p]
            [next.jdbc.result-set :as rs]
            [next.jdbc.specs :as specs]
            [next.jdbc.test-fixtures :refer [with-test-db ds column
                                              default-options
                                              derby? mssql? mysql? postgres?]])
  (:import (java.sql ResultSet ResultSetMetaData)))

(deftest test-mapify
  (testing "no row builder is used"
    (is (= [true]
           (into [] (map map?) ; it looks like a real map now
                 (p/-execute (ds) ["select * from fruit where id = ?" 1]
                             {:builder-fn (constantly nil)}))))
    (is (= ["Apple"]
           (into [] (map :name) ; keyword selection works
                 (p/-execute (ds) ["select * from fruit where id = ?" 1]
                             {:builder-fn (constantly nil)}))))
    (is (= [[2 [:name "Banana"]]]
           (into [] (map (juxt #(get % "id") ; get by string key works
                               #(find % :name))) ; get MapEntry works
                 (p/-execute (ds) ["select * from fruit where id = ?" 2]
                             {:builder-fn (constantly nil)}))))
    (is (= [{:id 3 :name "Peach"}]
           (into [] (map #(select-keys % [:id :name])) ; select-keys works
                 (p/-execute (ds) ["select * from fruit where id = ?" 3]
                             {:builder-fn (constantly nil)}))))
    (is (= [[:orange 4]]
           (into [] (map #(vector (if (contains? % :name) ; contains works
                                    (keyword (str/lower-case (:name %)))
                                    :unnamed)
                                  (get % :id 0))) ; get with not-found works
                 (p/-execute (ds) ["select * from fruit where id = ?" 4]
                             {:builder-fn (constantly nil)}))))
    (is (= [{}]
           (into [] (map empty) ; return empty map without building
                 (p/-execute (ds) ["select * from fruit where id = ?" 1]
                             {:builder-fn (constantly nil)})))))
  (testing "count does not build a map"
    (let [count-builder (fn [_1 _2]
                          (reify rs/RowBuilder
                            (column-count [_] 13)))]
      (is (= [13]
             (into [] (map count) ; count relies on columns, not row fields
                   (p/-execute (ds) ["select * from fruit where id = ?" 1]
                               {:builder-fn count-builder}))))))
  (testing "assoc, dissoc, cons, seq, and = build maps"
    (is (map? (reduce (fn [_ row] (reduced (assoc row :x 1)))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (= 6 (count (reduce (fn [_ row] (reduced (assoc row :x 1)))
                            nil
                            (p/-execute (ds) ["select * from fruit"] {})))))
    (is (map? (reduce (fn [_ row] (reduced
                                   (dissoc row (column :FRUIT/NAME))))
                      nil
                      (p/-execute (ds) ["select * from fruit"]
                                  (default-options)))))
    (is (= 4 (count (reduce (fn [_ row] (reduced
                                         (dissoc row (column :FRUIT/NAME))))
                            nil
                            (p/-execute (ds) ["select * from fruit"]
                                        (default-options))))))
    (is (seq? (reduce (fn [_ row] (reduced (seq row)))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (seq? (reduce (fn [_ row] (reduced (cons :seq row)))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (= :seq (first (reduce (fn [_ row] (reduced (cons :seq row)))
                               nil
                               (p/-execute (ds) ["select * from fruit"] {})))))
    (is (false? (reduce (fn [_ row] (reduced (= row {})))
                        nil
                        (p/-execute (ds) ["select * from fruit"] {}))))
    (is (map-entry? (second (reduce (fn [_ row] (reduced (cons :seq row)))
                                    nil
                                    (p/-execute (ds) ["select * from fruit"] {})))))
    (is (every? map-entry? (reduce (fn [_ row] (reduced (seq row)))
                                   nil
                                   (p/-execute (ds) ["select * from fruit"] {}))))
    (is (map? (reduce (fn [_ row] (reduced (conj row {:a 1})))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (map? (reduce (fn [_ row] (reduced (conj row [:a 1])))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (map? (reduce (fn [_ row] (reduced (conj row {:a 1 :b 2})))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (= 1 (:a (reduce (fn [_ row] (reduced (conj row {:a 1})))
                         nil
                         (p/-execute (ds) ["select * from fruit"] {})))))
    (is (= 1 (:a (reduce (fn [_ row] (reduced (conj row [:a 1])))
                         nil
                         (p/-execute (ds) ["select * from fruit"] {})))))
    (is (= 1 (:a (reduce (fn [_ row] (reduced (conj row {:a 1 :b 2})))
                         nil
                         (p/-execute (ds) ["select * from fruit"] {})))))
    (is (= 2 (:b (reduce (fn [_ row] (reduced (conj row {:a 1 :b 2})))
                         nil
                         (p/-execute (ds) ["select * from fruit"] {})))))
    (is (vector? (reduce (fn [_ row] (reduced (conj row :a)))
                         nil
                         (p/-execute (ds) ["select * from fruit"]
                                     {:builder-fn rs/as-arrays}))))
    (is (= :a (peek (reduce (fn [_ row] (reduced (conj row :a)))
                            nil
                            (p/-execute (ds) ["select * from fruit"]
                                        {:builder-fn rs/as-arrays})))))
    (is (= :b (peek (reduce (fn [_ row] (reduced (conj row :a :b)))
                            nil
                            (p/-execute (ds) ["select * from fruit"]
                                        {:builder-fn rs/as-arrays}))))))
  (testing "datafiable-row builds map; with metadata"
    (is (map? (reduce (fn [_ row] (reduced (rs/datafiable-row row (ds) {})))
                      nil
                      (p/-execute (ds) ["select * from fruit"] {}))))
    (is (contains? (meta (reduce (fn [_ row] (reduced (rs/datafiable-row row (ds) {})))
                                 nil
                                 (p/-execute (ds) ["select * from fruit"] {})))
                   `core-p/datafy))))

(deftest metadata-result-set
  (let [metadata (with-open [con (p/get-connection (ds) {})]
                   (-> (.getMetaData con)
                       (.getTables nil nil nil (into-array ["TABLE" "VIEW"]))
                       (rs/datafiable-result-set (ds) {})))]
    (is (vector? metadata))
    (is (map? (first metadata)))
    ;; we should find :something/table_name with a value of "fruit"
    ;; may be upper/lower-case, could have any qualifier
    (is (some (fn [row]
                (some #(and (= "table_name" (-> % key name str/lower-case))
                            (= "fruit" (-> % val name str/lower-case)))
                      row))
              metadata))))
next-jdbc
(ns next.jdbc.datafy-test
  "Tests for the datafy extensions over JDBC types."
  (:require [clojure.datafy :as d]
            [clojure.set :as set]
            [clojure.test :refer [deftest is testing use-fixtures]]
            [next.jdbc :as jdbc]
            [next.jdbc.datafy]
            [next.jdbc.result-set :as rs]
            [next.jdbc.specs :as specs]
            [next.jdbc.test-fixtures
             :refer [with-test-db db ds
                      derby? jtds? mysql? postgres? sqlite?]]))

(def ^:private basic-database-metadata-keys
  "Generic JDBC Connection fields."
  #{:JDBCMajorVersion :JDBCMinorVersion :SQLKeywords :SQLStateType :URL
    :catalogSeparator :catalogTerm :catalogs
    :clientInfoProperties :connection
    :databaseMajorVersion :databaseMinorVersion
    :databaseProductName :databaseProductVersion
    :defaultTransactionIsolation
    :driverMajorVersion :driverMinorVersion :driverName :driverVersion
    :extraNameCharacters :identifierQuoteString
    :maxBinaryLiteralLength :maxCatalogNameLength :maxCharLiteralLength
    :maxColumnNameLength :maxColumnsInGroupBy :maxColumnsInIndex
    :maxColumnsInOrderBy :maxColumnsInSelect :maxColumnsInTable
    :maxConnections
    :maxCursorNameLength :maxIndexLength
    :maxProcedureNameLength :maxRowSize :maxSchemaNameLength
    :maxStatementLength :maxStatements :maxTableNameLength
    :maxTablesInSelect :maxUserNameLength :numericFunctions
    :procedureTerm :resultSetHoldability :rowIdLifetime
    :schemaTerm :schemas :searchStringEscape :stringFunctions
    :systemFunctions :tableTypes :timeDateFunctions
    :typeInfo :userName
    ;; boolean properties
    :catalogAtStart :readOnly
    ;; configured to be added as if by clojure.core/bean
    :class
    ;; added by next.jdbc.datafy if the datafication succeeds
    :all-tables})

(deftest database-metadata-datafy-tests
  (testing "database metadata datafication"
    (with-open [con (jdbc/get-connection (ds))]
      (let [reference-keys (cond-> basic-database-metadata-keys
                             (jtds?)     (-> (disj :clientInfoProperties :rowIdLifetime)
                                             (conj :clientInfoProperties/exception
                                                   :rowIdLifetime/exception))
                             (postgres?) (-> (disj :rowIdLifetime)
                                             (conj :rowIdLifetime/exception))
                             (sqlite?)   (-> (disj :clientInfoProperties :rowIdLifetime)
                                             (conj :clientInfoProperties/exception
                                                   :rowIdLifetime/exception)))
            data (set (keys (d/datafy (.getMetaData con))))]
        (when-let [diff (seq (set/difference data reference-keys))]
          (println (format "%6s :%-10s %s"
                           (:dbtype (db)) "db-meta" (str (sort diff)))))
        (is (= reference-keys
               (set/intersection reference-keys data))))))
  (testing "nav to catalogs yields object"
    (with-open [con (jdbc/get-connection (ds))]
      (let [data (d/datafy (.getMetaData con))]
        (doseq [k (cond-> #{:catalogs :clientInfoProperties :schemas :tableTypes :typeInfo}
                    (jtds?)   (disj :clientInfoProperties)
                    (sqlite?) (disj :clientInfoProperties))]
          (let [rs (d/nav data k nil)]
            (is (vector? rs))
            (is (every? map? rs))))))))

(deftest result-set-metadata-datafy-tests
  (testing "result set metadata datafication"
    (let [data (reduce (fn [_ row] (reduced (rs/metadata row)))
                       nil
                       (jdbc/plan (ds) [(str "SELECT * FROM "
                                             (if (mysql?) "fruit" "FRUIT"))]))]
      (is (vector? data))
      (is (= 5 (count data)))
      (is (every? map? data))
      (is (every? :label data)))))