Back

RowBuilder (clj)

(source)

protocol

Protocol for building rows in various representations. The default implementation for building hash maps: `MapResultSetBuilder`

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))))

(defn fruit-builder [^ResultSet rs ^ResultSetMetaData rsmeta]
  (reify
    rs/RowBuilder
    (->row [_] (->Fruit (.getObject rs "id")
                        (.getObject rs "name")
                        (.getObject rs "appearance")
                        (.getObject rs "cost")
                        (.getObject rs "grade")))
    (column-count [_] 0) ; no need to iterate over columns
    (with-column [_ row i] row)
    (with-column-value [_ row col v] row)
    (row! [_ row] row)
    rs/ResultSetBuilder
    (->rs [_] (transient []))
    (with-row [_ rs row] (conj! rs row))
    (rs! [_ rs] (persistent! rs))
    clojure.lang.ILookup ; only supports :cols and :rsmeta
    (valAt [this k] (get this k nil))
    (valAt [this k not-found]
      (case k
        :cols [:id :name :appearance :cost :grade]
        :rsmeta rsmeta
        not-found))))