Rework query tests to use a live DB. Fixes #35.

This commit is contained in:
Richard Newman 2016-08-19 09:15:16 -07:00
parent 4d2079380c
commit 15df2d9eac

View file

@ -1,19 +1,32 @@
(ns datomish.test.query (ns datomish.test.query
#?(:cljs
(:require-macros
[datomish.pair-chan :refer [go-pair <?]]
[datomish.node-tempfile-macros :refer [with-tempfile]]
[cljs.core.async.macros :as a :refer [go]]))
(:require (:require
[datomish.query.cc :as cc]
[datomish.query.context :as context] [datomish.query.context :as context]
[datomish.query.source :as source] [datomish.query.source :as source]
[datomish.query.transforms :as transforms] [datomish.query.transforms :as transforms]
[datomish.query :as query] [datomish.query :as query]
[datomish.db :as db]
[datomish.schema :as schema] [datomish.schema :as schema]
[datomish.transact :as transact]
[datomish.api :as d]
#?@(:clj #?@(:clj
[ [[datomish.pair-chan :refer [go-pair <?]]
[honeysql.core :as sql :refer [param]] [datomish.jdbc-sqlite]
[clojure.test :as t :refer [is are deftest testing]]]) [datomish.test-macros :refer [deftest-db]]
[honeysql.core :as sql :refer [param]]
[tempfile.core :refer [tempfile with-tempfile]]
[clojure.test :as t :refer [is are deftest testing]]])
#?@(:cljs #?@(:cljs
[ [[datomish.promise-sqlite]
[honeysql.core :as sql :refer-macros [param]] [datomish.test-macros :refer-macros [deftest-db]]
[cljs.test :as t :refer-macros [is are deftest testing]]]) [honeysql.core :as sql :refer-macros [param]]
) [datomish.node-tempfile :refer [tempfile]]
[cljs.test :as t :refer-macros [is are deftest testing]]]))
#?(:clj #?(:clj
(:import [clojure.lang ExceptionInfo]))) (:import [clojure.lang ExceptionInfo])))
@ -29,305 +42,404 @@
(fgensym s (dec (swap! counter inc))))))) (fgensym s (dec (swap! counter inc)))))))
(def simple-schema (def simple-schema
{:db/txInstant {:db/ident :db/txInstant [{:db/id (d/id-literal :db.part/user)
:db/valueType :long :db.install/_attribute :db.part/db
:db/cardinality :db.cardinality/one} :db/ident :db/txInstant
:foo/int {:db/ident :foo/int :db/valueType :db.type/long
:db/valueType :db.type/integer :db/cardinality :db.cardinality/one}
:db/cardinality :db.cardinality/one} {:db/id (d/id-literal :db.part/user)
:foo/str {:db/ident :foo/str :db.install/_attribute :db.part/db
:db/valueType :db.type/string :db/ident :foo/bar
:db/cardinality :db.cardinality/many}}) :db/valueType :db.type/string
:db/cardinality :db.cardinality/many}
{:db/id (d/id-literal :db.part/user)
:db.install/_attribute :db.part/db
:db/ident :foo/int
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one}
{:db/id (d/id-literal :db.part/user)
:db.install/_attribute :db.part/db
:db/ident :foo/str
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many}])
(defn mock-source [db schema] (def page-schema
(source/map->DatomsSource [{:db/id (d/id-literal :db.part/user)
{:table :datoms :db.install/_attribute :db.part/db
:fulltext-table :fulltext_values :db/ident :page/loves
:fulltext-view :all_datoms :db/valueType :db.type/ref
:columns [:e :a :v :tx :added] :db/cardinality :db.cardinality/many}
:attribute-transform transforms/attribute-transform-string {:db/id (d/id-literal :db.part/user)
:constant-transform transforms/constant-transform-default :db.install/_attribute :db.part/db
:table-alias (comp (make-predictable-gensym) name) :db/ident :page/likes
:schema (schema/map->Schema :db/valueType :db.type/ref
{:schema schema :db/cardinality :db.cardinality/many}
:rschema nil}) {:db/id (d/id-literal :db.part/user)
:make-constraints nil})) :db.install/_attribute :db.part/db
:db/ident :page/url
:db/valueType :db.type/string
:db/unique :db.unique/identity
:db/cardinality :db.cardinality/one}
{:db/id (d/id-literal :db.part/user)
:db.install/_attribute :db.part/db
:db/ident :page/title
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/id (d/id-literal :db.part/user)
:db.install/_attribute :db.part/db
:db/ident :page/starred
:db/valueType :db.type/boolean
:db/cardinality :db.cardinality/one}])
(defn- expand [find schema] (def schema-with-page
(let [context (context/->Context (mock-source nil schema) nil nil) (concat
simple-schema
page-schema))
(defn mock-source [db]
(assoc (datomish.db/datoms-source db)
:table-alias (comp (make-predictable-gensym) name)))
(defn conn->context [conn]
(context/->Context (mock-source (d/db conn)) nil nil))
(defn- expand [find conn]
(let [context (conn->context conn)
parsed (query/parse find)] parsed (query/parse find)]
(query/find->sql-clause context parsed))) (query/find->sql-clause context parsed)))
(defn- populate [find schema] (defn- populate [find conn]
(let [context (context/->Context (mock-source nil schema) nil nil) (let [context (conn->context conn)
parsed (query/parse find)] parsed (query/parse find)]
(query/find-into-context context parsed))) (query/find-into-context context parsed)))
(deftest test-type-extraction (defn <initialize-with-schema [conn schema]
(go-pair
(let [tx (<? (d/<transact! conn schema))]
(let [idents (map :db/ident schema)
db (d/db conn)]
(into {}
(map (fn [ident]
[ident (d/entid db ident)])
idents))))))
(deftest-db test-type-extraction conn
;; We expect to be able to look up the default types.
(is (integer? (d/entid (d/db conn) :db.type/ref)))
(is (integer? (d/entid (d/db conn) :db.type/long)))
;; Add our own schema.
(<? (<initialize-with-schema conn simple-schema))
(testing "Variable entity." (testing "Variable entity."
(is (= (:known-types (:cc (populate '[:find ?e ?v :in $ :where [?e :foo/int ?v]] simple-schema))) (is (= (->
{'?v :db.type/integer (populate '[:find ?e ?v :in $ :where [?e :foo/int ?v]] conn)
:cc :known-types)
{'?v :db.type/long
'?e :db.type/ref}))) '?e :db.type/ref})))
(testing "Numeric entid." (testing "Numeric entid."
(is (= (:known-types (:cc (populate '[:find ?v :in $ :where [6 :foo/int ?v]] simple-schema))) (is (= (->
{'?v :db.type/integer}))) (populate '[:find ?v :in $ :where [6 :foo/int ?v]] conn)
:cc :known-types)
{'?v :db.type/long})))
(testing "Keyword entity." (testing "Keyword entity."
(is (= (:known-types (:cc (populate '[:find ?v :in $ :where [:my/thing :foo/int ?v]] simple-schema))) (is (= (->
{'?v :db.type/integer})))) (populate '[:find ?v :in $ :where [:my/thing :foo/int ?v]] conn)
:cc :known-types)
{'?v :db.type/long}))))
(deftest test-value-constant-constraint-descends-into-not-and-or (deftest-db test-value-constant-constraint-descends-into-not-and-or conn
(testing "Elision of types inside a join." (let [attrs (<? (<initialize-with-schema conn simple-schema))]
(is (= '{:select ([:datoms0.e :e] (testing "Elision of types inside a join."
[:datoms0.v :v]), (is (= {:select '([:datoms0.e :e]
:modifiers [:distinct], [:datoms0.v :v]),
:from [[:datoms datoms0]], :modifiers [:distinct],
:where (:and :from [[:datoms 'datoms0]],
[:= :datoms0.a "foo/int"] :where (list :and
[:not [:= :datoms0.a (:foo/int attrs)]
[:exists [:not
{:select [1], [:exists
:from [[:all_datoms all_datoms1]], {:select [1],
:where (:and :from [[:all_datoms 'all_datoms1]],
[:= :all_datoms1.e 15] :where (list :and
[:= :datoms0.v :all_datoms1.v])}]])} [:= :all_datoms1.e 15]
(expand [:= :datoms0.v :all_datoms1.v])}]])}
'[:find ?e ?v :in $ :where (expand
[?e :foo/int ?v] '[:find ?e ?v :in $ :where
(not [15 ?a ?v])] [?e :foo/int ?v]
simple-schema)))) (not [15 ?a ?v])]
conn))))
(testing "Type collisions inside :not." (testing "Type collisions inside :not."
(is (thrown-with-msg? (is (thrown-with-msg?
ExceptionInfo #"\?v already has type :db\.type\/integer" ExceptionInfo #"v already has type :db.type.long"
(expand (expand
'[:find ?e ?v :in $ :where '[:find ?e ?v :in $ :where
[?e :foo/int ?v] [?e :foo/int ?v]
(not [15 :foo/str ?v])] (not [15 :foo/str ?v])]
simple-schema)))) conn))))
(testing "Type collisions inside :or"
(is (thrown-with-msg?
ExceptionInfo #"\?v already has type :db\.type\/integer"
(expand
'[:find ?e ?v :in $ :where
[?e :foo/int ?v]
(or
[15 :foo/str ?v]
[10 :foo/int ?v])]
simple-schema)))))
(deftest test-type-collision (testing "Type collisions inside :or"
(is (thrown-with-msg?
ExceptionInfo #"v already has type :db.type.long"
(expand
'[:find ?e ?v :in $ :where
[?e :foo/int ?v]
(or
[15 :foo/str ?v]
[10 :foo/int ?v])]
conn))))))
(deftest-db test-type-collision conn
(<? (<initialize-with-schema conn simple-schema))
(let [find '[:find ?e ?v :in $ (let [find '[:find ?e ?v :in $
:where :where
[?e :foo/int ?v] [?e :foo/int ?v]
[?x :foo/str ?v]]] [?x :foo/str ?v]]]
(is (thrown-with-msg? (is (thrown-with-msg?
ExceptionInfo #"\?v already has type :db\.type\/integer" ExceptionInfo #"v already has type :db.type.long"
(populate find simple-schema))))) (populate find conn)))))
(deftest test-value-constant-constraint (deftest-db test-value-constant-constraint conn
(<? (<initialize-with-schema conn simple-schema))
(is (= {:select '([:all_datoms0.e :foo]), (is (= {:select '([:all_datoms0.e :foo]),
:modifiers [:distinct], :modifiers [:distinct],
:from '[[:all_datoms all_datoms0]], :from '[[:all_datoms all_datoms0]],
:where (list :and :where (list :and
(list :or (list :or
[:= :all_datoms0.value_type_tag 0] [:= :all_datoms0.value_type_tag 0]
;; In CLJS, this can also be an `instant`.
#?@(:cljs [[:= :all_datoms0.value_type_tag 4]])
[:= :all_datoms0.value_type_tag 5]) [:= :all_datoms0.value_type_tag 5])
[:= :all_datoms0.v 99])} [:= :all_datoms0.v 99])}
(expand (expand
'[:find ?foo :in $ :where '[:find ?foo :in $ :where
[?foo _ 99]] [?foo _ 99]]
simple-schema)))) conn))))
(deftest test-value-constant-constraint-elided-using-schema (deftest-db test-value-constant-constraint-elided-using-schema conn
(testing "There's no need to produce value_type_tag constraints when the attribute is specified." (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (testing "Our attributes were interned."
(= '{:select ([:datoms1.v :timestampMicros] [:datoms0.e :page]), (is (integer? (d/entid (d/db conn) :foo/str)))
:modifiers [:distinct], (is (integer? (d/entid (d/db conn) :page/starred))))
:from [[:datoms datoms0]
[:datoms datoms1]],
:where (:and
;; We don't need a type check on the range of page/starred...
[:= :datoms0.a "page/starred"]
[:= :datoms0.v 1]
[:= :datoms1.a "db/txInstant"]
[:not
[:exists
{:select [1],
:from [[:datoms datoms2]],
:where (:and
[:= :datoms2.a "foo/bar"]
[:= :datoms0.e :datoms2.e])}]]
[:= :datoms0.tx :datoms1.e])}
(expand
'[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t]
[?t :db/txInstant ?timestampMicros]
(not [?page :foo/bar _])]
(merge (testing "There's no need to produce value_type_tag constraints when the attribute is specified."
simple-schema (is
{:page/starred {:db/valueType :db.type/boolean (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:db/ident :page/starred :modifiers [:distinct],
:db/cardinality :db.cardinality/one}})))))) :from [[:datoms 'datoms0]
[:datoms 'datoms1]],
:where (list :and
;; We don't need a type check on the range of page/starred...
[:= :datoms0.a (:page/starred attrs)]
[:= :datoms0.v 1]
[:= :datoms1.a (:db/txInstant attrs)]
[:not
[:exists
{:select [1],
:from [[:datoms 'datoms2]],
:where (list :and
[:= :datoms2.a (:foo/bar attrs)]
[:= :datoms0.e :datoms2.e])}]]
[:= :datoms0.tx :datoms1.e])}
(expand
'[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t]
[?t :db/txInstant ?timestampMicros]
(not [?page :foo/bar _])]
(deftest test-basic-join conn))))))
(is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct],
:from '[[:datoms datoms0]
[:datoms datoms1]],
:where (list
:and
[:= :datoms0.a "page/starred"]
[:= :datoms0.value_type_tag 1] ; boolean
[:= :datoms0.v 1]
[:= :datoms1.a "db/txInstant"]
[:not
(list :and (list :> :datoms0.tx (sql/param :latest)))]
[:= :datoms0.tx :datoms1.e])}
(expand
'[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t]
[?t :db/txInstant ?timestampMicros]
(not [(> ?t ?latest)])]
simple-schema))))
(deftest test-pattern-not-join (deftest-db test-basic-join conn
(is (= '{:select ([:datoms1.v :timestampMicros] [:datoms0.e :page]), ;; Note that we use a schema without :page/starred, so we
:modifiers [:distinct], ;; don't know what type it is.
:from [[:datoms datoms0] (let [attrs (<? (<initialize-with-schema conn simple-schema))]
[:datoms datoms1]], (is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:where (:and :modifiers [:distinct],
[:= :datoms0.a "page/starred"] :from '([:datoms datoms0]
[:datoms datoms1]),
:where (list
:and
;; Note that :page/starred is literal, because
;; it's not present in the interned schema.
[:= :datoms0.a :page/starred]
[:= :datoms0.value_type_tag 1] ; boolean [:= :datoms0.value_type_tag 1] ; boolean
[:= :datoms0.v 1] [:= :datoms0.v 1]
[:= :datoms1.a "db/txInstant"] [:= :datoms1.a (:db/txInstant attrs)]
[:not
(list :and (list :> :datoms0.tx (sql/param :latest)))]
[:= :datoms0.tx :datoms1.e])}
(expand
'[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t]
[?t :db/txInstant ?timestampMicros]
(not [(> ?t ?latest)])]
conn)))))
(deftest-db test-pattern-not-join conn
(let [attrs (<? (<initialize-with-schema conn simple-schema))]
(is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct],
:from [[:datoms 'datoms0]
[:datoms 'datoms1]],
:where (list
:and
;; Note that :page/starred is literal, because
;; it's not present in the interned schema.
[:= :datoms0.a :page/starred]
[:= :datoms0.value_type_tag 1] ; boolean
[:= :datoms0.v 1]
[:= :datoms1.a (:db/txInstant attrs)]
[:not [:not
[:exists [:exists
{:select [1], {:select [1],
:from [[:datoms datoms2]], :from [[:datoms 'datoms2]],
:where (:and :where (list :and
[:= :datoms2.a "foo/bar"] [:= :datoms2.a (:foo/bar attrs)]
[:= :datoms0.e :datoms2.e])}]] [:= :datoms0.e :datoms2.e])}]]
[:= :datoms0.tx :datoms1.e])} [:= :datoms0.tx :datoms1.e])}
(expand (expand
'[:find ?timestampMicros ?page :in $ ?latest :where '[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t] [?page :page/starred true ?t]
[?t :db/txInstant ?timestampMicros] [?t :db/txInstant ?timestampMicros]
(not [?page :foo/bar _])] (not [?page :foo/bar _])]
simple-schema)))) conn)))))
;; Note that clause ordering is not directly correlated to the output: cross-bindings end up ;; Note that clause ordering is not directly correlated to the output: cross-bindings end up
;; at the front. The SQL engine will do its own analysis. See `clauses/expand-where-from-bindings`. ;; at the front. The SQL engine will do its own analysis. See `clauses/expand-where-from-bindings`.
(deftest test-not-clause-ordering-preserved (deftest-db test-not-clause-ordering-preserved conn
(is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
:modifiers [:distinct], (is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:from '[[:datoms datoms0] :modifiers [:distinct],
[:datoms datoms1]], :from '[[:datoms datoms0]
:where (list [:datoms datoms1]],
:and :where (list
[:= :datoms0.a "page/starred"] :and
[:= :datoms0.value_type_tag 1] ; boolean ;; We don't need a value tag constraint -- we know the range of the attribute.
[:= :datoms0.v 1] [:= :datoms0.a (:page/starred attrs)]
[:not
(list :and (list :> :datoms0.tx (sql/param :latest)))]
[:= :datoms1.a "db/txInstant"]
[:= :datoms0.tx :datoms1.e]
)}
(expand
'[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t]
(not [(> ?t ?latest)])
[?t :db/txInstant ?timestampMicros]]
simple-schema))))
(deftest test-pattern-not-join-ordering-preserved
(is (= '{:select ([:datoms2.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct],
:from [[:datoms datoms0]
[:datoms datoms2]],
:where (:and
[:= :datoms0.a "page/starred"]
[:= :datoms0.value_type_tag 1] ; boolean
[:= :datoms0.v 1] [:= :datoms0.v 1]
[:not [:not
[:exists (list :and (list :> :datoms0.tx (sql/param :latest)))]
{:select [1], [:= :datoms1.a (:db/txInstant attrs)]
:from [[:datoms datoms1]], [:= :datoms0.tx :datoms1.e]
:where (:and
[:= :datoms1.a "foo/bar"]
[:= :datoms0.e :datoms1.e])}]]
[:= :datoms2.a "db/txInstant"]
[:= :datoms0.tx :datoms2.e]
)} )}
(expand (expand
'[:find ?timestampMicros ?page :in $ ?latest :where '[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t] [?page :page/starred true ?t]
(not [?page :foo/bar _]) (not [(> ?t ?latest)])
[?t :db/txInstant ?timestampMicros]] [?t :db/txInstant ?timestampMicros]]
simple-schema)))) conn)))))
(deftest test-single-or (deftest-db test-pattern-not-join-ordering-preserved conn
(is (= '{:select ([:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
:modifiers [:distinct], (is (= {:select '([:datoms2.v :timestampMicros] [:datoms0.e :page]),
:from ([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]), :modifiers [:distinct],
:where (:and :from [[:datoms 'datoms0]
[:= :datoms0.a "page/url"] [:datoms 'datoms2]],
[:= :datoms0.value_type_tag 10] :where (list :and
[:= :datoms0.v "http://example.com/"] ;; We don't need a value tag constraint -- we know the range of the attribute.
[:= :datoms1.a "page/title"] [:= :datoms0.a (:page/starred attrs)]
[:= :datoms2.a "page/loves"] [:= :datoms0.v 1]
[:= :datoms0.e :datoms1.e] [:not
[:= :datoms0.e :datoms2.v])} [:exists
(expand {:select [1],
'[:find ?page :in $ ?latest :where :from [[:datoms 'datoms1]],
[?page :page/url "http://example.com/"] :where (list :and
[?page :page/title ?title] [:= :datoms1.a (:foo/bar attrs)]
(or [:= :datoms0.e :datoms1.e])}]]
[?entity :page/loves ?page])] [:= :datoms2.a (:db/txInstant attrs)]
simple-schema)))) [:= :datoms0.tx :datoms2.e])}
(expand
'[:find ?timestampMicros ?page :in $ ?latest :where
[?page :page/starred true ?t]
(not [?page :foo/bar _])
[?t :db/txInstant ?timestampMicros]]
conn)))))
(deftest test-simple-or (deftest-db test-single-or conn
(is (= '{:select ([:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
:modifiers [:distinct], (is (= {:select '([:datoms0.e :page]),
:from ([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]), :modifiers [:distinct],
:where (:and :from '([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]),
[:= :datoms0.a "page/url"] :where (list :and
[:= :datoms0.value_type_tag 10] [:= :datoms0.a (:page/url attrs)]
[:= :datoms0.v "http://example.com/"] [:= :datoms0.v "http://example.com/"]
[:= :datoms1.a "page/title"] [:= :datoms1.a (:page/title attrs)]
(:or [:= :datoms2.a (:page/loves attrs)]
[:= :datoms2.a "page/likes"] [:= :datoms0.e :datoms1.e]
[:= :datoms2.a "page/loves"]) [:= :datoms0.e :datoms2.v])}
[:= :datoms0.e :datoms1.e] (expand
[:= :datoms0.e :datoms2.v])} '[:find ?page :in $ ?latest :where
(expand [?page :page/url "http://example.com/"]
'[:find ?page :in $ ?latest :where [?page :page/title ?title]
[?page :page/url "http://example.com/"] (or
[?page :page/title ?title] [?entity :page/loves ?page])]
(or conn)))))
[?entity :page/likes ?page]
[?entity :page/loves ?page])]
simple-schema))))
(deftest test-tag-projection (deftest-db test-simple-or conn
(is (= '{:select ([:datoms0.e :page] (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
[:datoms1.v :thing] (is (= {:select '([:datoms0.e :page]),
[:datoms1.value_type_tag :_thing_type_tag]), :modifiers [:distinct],
:modifiers [:distinct], :from '([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]),
:from ([:datoms datoms0] :where (list :and
[:datoms datoms1]), [:= :datoms0.a (:page/url attrs)]
:where (:and [:= :datoms0.v "http://example.com/"]
[:= :datoms0.a "page/url"] [:= :datoms1.a (:page/title attrs)]
[:= :datoms0.value_type_tag 10] (list :or
[:= :datoms0.v "http://example.com/"] [:= :datoms2.a (:page/likes attrs)]
(:or [:= :datoms2.a (:page/loves attrs)])
[:= :datoms1.a "page/likes"] [:= :datoms0.e :datoms1.e]
[:= :datoms1.a "page/loves"]) [:= :datoms0.e :datoms2.v])}
[:= :datoms0.e :datoms1.e])} (expand
(expand '[:find ?page :in $ ?latest :where
'[:find ?page ?thing :in $ ?latest :where [?page :page/url "http://example.com/"]
[?page :page/url "http://example.com/"] [?page :page/title ?title]
(or (or
[?page :page/likes ?thing] [?entity :page/likes ?page]
[?page :page/loves ?thing])] [?entity :page/loves ?page])]
simple-schema)))) conn)))))
(defn tag-clauses [column input]
(let [codes (cc/->tag-codes input)]
(if (= 1 (count codes))
[:= column (first codes)]
(cons :or (map (fn [tag]
[:= column tag])
codes)))))
(deftest-db test-url-tag conn
(let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (= {:select '([:all_datoms0.e :page]
[:datoms1.v :thing]),
:modifiers [:distinct],
:from '([:all_datoms all_datoms0]
[:datoms datoms1]),
:where (list
:and
(tag-clauses :all_datoms0.value_type_tag "http://example.com/")
[:= :all_datoms0.v "http://example.com/"]
(list
:or
[:= :datoms1.a (:page/likes attrs)]
[:= :datoms1.a (:page/loves attrs)])
[:= :all_datoms0.e :datoms1.e])}
(expand
'[:find ?page ?thing :in $ ?latest :where
[?page _ "http://example.com/"]
(or
[?page :page/likes ?thing]
[?page :page/loves ?thing])]
conn)))))
(deftest-db test-tag-projection conn
(let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (= {:select '([:all_datoms0.e :page]
[:all_datoms0.v :thing]
[:all_datoms0.value_type_tag :_thing_type_tag]),
:modifiers [:distinct],
:from '([:all_datoms all_datoms0])}
(expand
'[:find ?page ?thing :in $ :where
[?page _ ?thing]]
conn)))))