diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d79ae5..b40bc45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ The following changes have been committed to the **master** branch since the 1.0 The following changes have been committed to the **issue-60** branch since the 1.0.6 release: -* Address #60 by allowing for additional schema entry formats: `:table/column` is equivalent to the old `[:table :column :one]` and `[:table/column]` is equivalent to the old `[:table :column :many]`. I'm still evaluating what might be needed for #61 before settling on a suitable format for schema extensions. +* Address #60 by supporting simpler schema entry formats: `:table/column` is equivalent to the old `[:table :column :one]` and `[:table/column]` is equivalent to the old `[:table :column :many]`. The older formats will continue to be supported but should be considered deprecated. ## Stable Builds diff --git a/doc/datafy-nav-and-schema.md b/doc/datafy-nav-and-schema.md index 0f31be8..c4ec5a5 100644 --- a/doc/datafy-nav-and-schema.md +++ b/doc/datafy-nav-and-schema.md @@ -20,22 +20,24 @@ In addition to `execute!` and `execute-one!`, you can call `next.jdbc.result-set By default, `next.jdbc` assumes that a column named `id` or `_id` is a foreign key into a table called `` with a primary key called `id`. As an example, if you have a table `address` which has columns `id` (the primary key), `name`, `email`, etc, and a table `contact` which has various columns including `addressid`, then if you retrieve a result set based on `contact`, call `datafy` on it and then "drill down" into the columns, when `(nav row :contact/addressid v)` is called (where `v` is the value of that column in that row) `next.jdbc`'s implementation of `nav` will fetch a single row from the `address` table, identified by `id` matching `v`. -You can override this default behavior for any column in any table by providing a `:schema` option that is a hash map whose keys are column names (usually the table-qualified keywords that `next.jdbc` produces by default) and whose values are tuples containing the name of the table to which that column is a foreign key and the name of the key column within that table. These tuples can optionally include a third value which indicates the cardinality of the foreign key relationship: `:one` or `:many`. The default is `:one` and indicates a one-to-one or many-to-one relationship -- `nav`igation will produce a single row. `:many` indicates a one-to-many or many-to-many relationship -- `nav`igation will produce a result set. +You can override this default behavior for any column in any table by providing a `:schema` option that is a hash map whose keys are column names (usually the table-qualified keywords that `next.jdbc` produces by default) and whose values are table-qualified keywords, optionally wrapped in vectors, that identity the name of the table to which that column is a foreign key and the name of the key column within that table. The default behavior in the example above is equivalent to this `:schema` value: ```clojure -{:contact/addressid [:address :id :one]} ; :one is the default and could be omitted +{:contact/addressid :address/id} ; a one-to-one or many-to-one relationship ``` -If you had a table to track the current valid/bouncing status of email addresses, where `email` is the primary key, you could provide automatic navigation into that using: +If you had a table to track the valid/bouncing status of email addresses over time, `:deliverability`, where `email` is the non-unique key, you could provide automatic navigation into that using: ```clojure -{:contact/addressid [:address :id :one] - :address/email [:deliverability :email]} +{:contact/addressid :address/id + :address/email [:deliverability/email]} ; one-to-many or many-to-many ``` -If you use foreign key constraints in your database, you could probably generate this `:schema` data structure automatically from the metadata in your database. +When you indicate a `*-to-many` relationship, by wrapping the foreign table/key in a vector, `next.jdbc`'s implementation of `nav` will fetch a multi-row result set from the target table. + +If you use foreign key constraints in your database, you could probably generate this `:schema` data structure automatically from the metadata in your database. Similarly, if you use a library that depends on an entity relationship map (such as [seql](https://exoscale.github.io/seql/) or [walkable](https://walkable.gitlab.io/)), then you could probably generate this `:schema` data structure from that entity map. ## Behind The Scenes diff --git a/test/next/jdbc/result_set_test.clj b/test/next/jdbc/result_set_test.clj index ffc9cfa..9b127d9 100644 --- a/test/next/jdbc/result_set_test.clj +++ b/test/next/jdbc/result_set_test.clj @@ -30,7 +30,32 @@ ;; check nav produces a single map with the expected key/value data (is (= 1 ((if (postgres?) :fruit/id :FRUIT/ID) object))) (is (= "Apple" ((if (postgres?) :fruit/name :FRUIT/NAME) object)))))) - (testing "custom schema :one" + (testing "custom schema *-to-1" + (let [connectable (ds) + test-row (rs/datafiable-row {:foo/bar 2} connectable + {:schema {:foo/bar :fruit/id}}) + data (d/datafy test-row) + v (get data :foo/bar)] + ;; check datafication is sane + (is (= 2 v)) + (let [object (d/nav data :foo/bar v)] + ;; check nav produces a single map with the expected key/value data + (is (= 2 ((if (postgres?) :fruit/id :FRUIT/ID) object))) + (is (= "Banana" ((if (postgres?) :fruit/name :FRUIT/NAME) object)))))) + (testing "custom schema *-to-many" + (let [connectable (ds) + test-row (rs/datafiable-row {:foo/bar 3} connectable + {:schema {:foo/bar [:fruit/id]}}) + data (d/datafy test-row) + v (get data :foo/bar)] + ;; check datafication is sane + (is (= 3 v)) + (let [object (d/nav data :foo/bar v)] + ;; check nav produces a result set with the expected key/value data + (is (vector? object)) + (is (= 3 ((if (postgres?) :fruit/id :FRUIT/ID) (first object)))) + (is (= "Peach" ((if (postgres?) :fruit/name :FRUIT/NAME) (first object))))))) + (testing "legacy schema tuples" (let [connectable (ds) test-row (rs/datafiable-row {:foo/bar 2} connectable {:schema {:foo/bar [:fruit :id]}}) @@ -41,8 +66,7 @@ (let [object (d/nav data :foo/bar v)] ;; check nav produces a single map with the expected key/value data (is (= 2 ((if (postgres?) :fruit/id :FRUIT/ID) object))) - (is (= "Banana" ((if (postgres?) :fruit/name :FRUIT/NAME) object)))))) - (testing "custom schema :many" + (is (= "Banana" ((if (postgres?) :fruit/name :FRUIT/NAME) object))))) (let [connectable (ds) test-row (rs/datafiable-row {:foo/bar 3} connectable {:schema {:foo/bar [:fruit :id :many]}})