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 <?]]
[datomish.jdbc-sqlite]
[datomish.test-macros :refer [deftest-db]]
[honeysql.core :as sql :refer [param]] [honeysql.core :as sql :refer [param]]
[tempfile.core :refer [tempfile with-tempfile]]
[clojure.test :as t :refer [is are deftest testing]]]) [clojure.test :as t :refer [is are deftest testing]]])
#?@(:cljs #?@(:cljs
[ [[datomish.promise-sqlite]
[datomish.test-macros :refer-macros [deftest-db]]
[honeysql.core :as sql :refer-macros [param]] [honeysql.core :as sql :refer-macros [param]]
[cljs.test :as t :refer-macros [is are deftest testing]]]) [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,133 +42,205 @@
(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/ident :db/txInstant
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one} :db/cardinality :db.cardinality/one}
:foo/int {:db/ident :foo/int {:db/id (d/id-literal :db.part/user)
:db/valueType :db.type/integer :db.install/_attribute :db.part/db
:db/cardinality :db.cardinality/one} :db/ident :foo/bar
:foo/str {:db/ident :foo/str
:db/valueType :db.type/string :db/valueType :db.type/string
:db/cardinality :db.cardinality/many}}) :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]
(testing "Variable entity." (go-pair
(is (= (:known-types (:cc (populate '[:find ?e ?v :in $ :where [?e :foo/int ?v]] simple-schema))) (let [tx (<? (d/<transact! conn schema))]
{'?v :db.type/integer (let [idents (map :db/ident schema)
'?e :db.type/ref}))) db (d/db conn)]
(testing "Numeric entid." (into {}
(is (= (:known-types (:cc (populate '[:find ?v :in $ :where [6 :foo/int ?v]] simple-schema))) (map (fn [ident]
{'?v :db.type/integer}))) [ident (d/entid db ident)])
(testing "Keyword entity." idents))))))
(is (= (:known-types (:cc (populate '[:find ?v :in $ :where [:my/thing :foo/int ?v]] simple-schema)))
{'?v :db.type/integer}))))
(deftest test-value-constant-constraint-descends-into-not-and-or (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."
(is (= (->
(populate '[:find ?e ?v :in $ :where [?e :foo/int ?v]] conn)
:cc :known-types)
{'?v :db.type/long
'?e :db.type/ref})))
(testing "Numeric entid."
(is (= (->
(populate '[:find ?v :in $ :where [6 :foo/int ?v]] conn)
:cc :known-types)
{'?v :db.type/long})))
(testing "Keyword entity."
(is (= (->
(populate '[:find ?v :in $ :where [:my/thing :foo/int ?v]] conn)
:cc :known-types)
{'?v :db.type/long}))))
(deftest-db test-value-constant-constraint-descends-into-not-and-or conn
(let [attrs (<? (<initialize-with-schema conn simple-schema))]
(testing "Elision of types inside a join." (testing "Elision of types inside a join."
(is (= '{:select ([:datoms0.e :e] (is (= {:select '([:datoms0.e :e]
[:datoms0.v :v]), [:datoms0.v :v]),
:modifiers [:distinct], :modifiers [:distinct],
:from [[:datoms datoms0]], :from [[:datoms 'datoms0]],
:where (:and :where (list :and
[:= :datoms0.a "foo/int"] [:= :datoms0.a (:foo/int attrs)]
[:not [:not
[:exists [:exists
{:select [1], {:select [1],
:from [[:all_datoms all_datoms1]], :from [[:all_datoms 'all_datoms1]],
:where (:and :where (list :and
[:= :all_datoms1.e 15] [:= :all_datoms1.e 15]
[:= :datoms0.v :all_datoms1.v])}]])} [:= :datoms0.v :all_datoms1.v])}]])}
(expand (expand
'[:find ?e ?v :in $ :where '[:find ?e ?v :in $ :where
[?e :foo/int ?v] [?e :foo/int ?v]
(not [15 ?a ?v])] (not [15 ?a ?v])]
simple-schema)))) 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" (testing "Type collisions inside :or"
(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]
(or (or
[15 :foo/str ?v] [15 :foo/str ?v]
[10 :foo/int ?v])] [10 :foo/int ?v])]
simple-schema))))) conn))))))
(deftest test-type-collision (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-db test-value-constant-constraint-elided-using-schema conn
(let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(testing "Our attributes were interned."
(is (integer? (d/entid (d/db conn) :foo/str)))
(is (integer? (d/entid (d/db conn) :page/starred))))
(deftest test-value-constant-constraint-elided-using-schema
(testing "There's no need to produce value_type_tag constraints when the attribute is specified." (testing "There's no need to produce value_type_tag constraints when the attribute is specified."
(is (is
(= '{:select ([:datoms1.v :timestampMicros] [:datoms0.e :page]), (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from [[:datoms datoms0] :from [[:datoms 'datoms0]
[:datoms datoms1]], [:datoms 'datoms1]],
:where (:and :where (list :and
;; We don't need a type check on the range of page/starred... ;; We don't need a type check on the range of page/starred...
[:= :datoms0.a "page/starred"] [:= :datoms0.a (:page/starred attrs)]
[:= :datoms0.v 1] [:= :datoms0.v 1]
[:= :datoms1.a "db/txInstant"] [:= :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
@ -164,23 +249,24 @@
[?t :db/txInstant ?timestampMicros] [?t :db/txInstant ?timestampMicros]
(not [?page :foo/bar _])] (not [?page :foo/bar _])]
(merge conn))))))
simple-schema
{:page/starred {:db/valueType :db.type/boolean
:db/ident :page/starred
:db/cardinality :db.cardinality/one}}))))))
(deftest test-basic-join (deftest-db test-basic-join conn
;; Note that we use a schema without :page/starred, so we
;; don't know what type it is.
(let [attrs (<? (<initialize-with-schema conn simple-schema))]
(is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]), (is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from '[[:datoms datoms0] :from '([:datoms datoms0]
[:datoms datoms1]], [:datoms datoms1]),
:where (list :where (list
:and :and
[:= :datoms0.a "page/starred"] ;; 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 [:not
(list :and (list :> :datoms0.tx (sql/param :latest)))] (list :and (list :> :datoms0.tx (sql/param :latest)))]
[:= :datoms0.tx :datoms1.e])} [:= :datoms0.tx :datoms1.e])}
@ -189,24 +275,28 @@
[?page :page/starred true ?t] [?page :page/starred true ?t]
[?t :db/txInstant ?timestampMicros] [?t :db/txInstant ?timestampMicros]
(not [(> ?t ?latest)])] (not [(> ?t ?latest)])]
simple-schema)))) conn)))))
(deftest test-pattern-not-join (deftest-db test-pattern-not-join conn
(is (= '{:select ([:datoms1.v :timestampMicros] [:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn simple-schema))]
(is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from [[:datoms datoms0] :from [[:datoms 'datoms0]
[:datoms datoms1]], [:datoms 'datoms1]],
:where (:and :where (list
[:= :datoms0.a "page/starred"] :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 [: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
@ -214,23 +304,24 @@
[?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
(let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]), (is (= {:select '([:datoms1.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from '[[:datoms datoms0] :from '[[:datoms datoms0]
[:datoms datoms1]], [:datoms datoms1]],
:where (list :where (list
:and :and
[:= :datoms0.a "page/starred"] ;; We don't need a value tag constraint -- we know the range of the attribute.
[:= :datoms0.value_type_tag 1] ; boolean [:= :datoms0.a (:page/starred attrs)]
[:= :datoms0.v 1] [:= :datoms0.v 1]
[:not [:not
(list :and (list :> :datoms0.tx (sql/param :latest)))] (list :and (list :> :datoms0.tx (sql/param :latest)))]
[:= :datoms1.a "db/txInstant"] [:= :datoms1.a (:db/txInstant attrs)]
[:= :datoms0.tx :datoms1.e] [:= :datoms0.tx :datoms1.e]
)} )}
(expand (expand
@ -238,44 +329,44 @@
[?page :page/starred true ?t] [?page :page/starred true ?t]
(not [(> ?t ?latest)]) (not [(> ?t ?latest)])
[?t :db/txInstant ?timestampMicros]] [?t :db/txInstant ?timestampMicros]]
simple-schema)))) conn)))))
(deftest test-pattern-not-join-ordering-preserved (deftest-db test-pattern-not-join-ordering-preserved conn
(is (= '{:select ([:datoms2.v :timestampMicros] [:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (= {:select '([:datoms2.v :timestampMicros] [:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from [[:datoms datoms0] :from [[:datoms 'datoms0]
[:datoms datoms2]], [:datoms 'datoms2]],
:where (:and :where (list :and
[:= :datoms0.a "page/starred"] ;; We don't need a value tag constraint -- we know the range of the attribute.
[:= :datoms0.value_type_tag 1] ; boolean [:= :datoms0.a (:page/starred attrs)]
[:= :datoms0.v 1] [:= :datoms0.v 1]
[:not [:not
[:exists [:exists
{:select [1], {:select [1],
:from [[:datoms datoms1]], :from [[:datoms 'datoms1]],
:where (:and :where (list :and
[:= :datoms1.a "foo/bar"] [:= :datoms1.a (:foo/bar attrs)]
[:= :datoms0.e :datoms1.e])}]] [:= :datoms0.e :datoms1.e])}]]
[:= :datoms2.a "db/txInstant"] [:= :datoms2.a (:db/txInstant attrs)]
[:= :datoms0.tx :datoms2.e] [:= :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 [?page :foo/bar _])
[?t :db/txInstant ?timestampMicros]] [?t :db/txInstant ?timestampMicros]]
simple-schema)))) conn)))))
(deftest test-single-or (deftest-db test-single-or conn
(is (= '{:select ([:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (= {:select '([:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from ([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]), :from '([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]),
:where (:and :where (list :and
[:= :datoms0.a "page/url"] [:= :datoms0.a (:page/url attrs)]
[:= :datoms0.value_type_tag 10]
[:= :datoms0.v "http://example.com/"] [:= :datoms0.v "http://example.com/"]
[:= :datoms1.a "page/title"] [:= :datoms1.a (:page/title attrs)]
[:= :datoms2.a "page/loves"] [:= :datoms2.a (:page/loves attrs)]
[:= :datoms0.e :datoms1.e] [:= :datoms0.e :datoms1.e]
[:= :datoms0.e :datoms2.v])} [:= :datoms0.e :datoms2.v])}
(expand (expand
@ -284,20 +375,20 @@
[?page :page/title ?title] [?page :page/title ?title]
(or (or
[?entity :page/loves ?page])] [?entity :page/loves ?page])]
simple-schema)))) conn)))))
(deftest test-simple-or (deftest-db test-simple-or conn
(is (= '{:select ([:datoms0.e :page]), (let [attrs (<? (<initialize-with-schema conn schema-with-page))]
(is (= {:select '([:datoms0.e :page]),
:modifiers [:distinct], :modifiers [:distinct],
:from ([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]), :from '([:datoms datoms0] [:datoms datoms1] [:datoms datoms2]),
:where (:and :where (list :and
[:= :datoms0.a "page/url"] [:= :datoms0.a (:page/url attrs)]
[:= :datoms0.value_type_tag 10]
[:= :datoms0.v "http://example.com/"] [:= :datoms0.v "http://example.com/"]
[:= :datoms1.a "page/title"] [:= :datoms1.a (:page/title attrs)]
(:or (list :or
[:= :datoms2.a "page/likes"] [:= :datoms2.a (:page/likes attrs)]
[:= :datoms2.a "page/loves"]) [:= :datoms2.a (:page/loves attrs)])
[:= :datoms0.e :datoms1.e] [:= :datoms0.e :datoms1.e]
[:= :datoms0.e :datoms2.v])} [:= :datoms0.e :datoms2.v])}
(expand (expand
@ -307,27 +398,48 @@
(or (or
[?entity :page/likes ?page] [?entity :page/likes ?page]
[?entity :page/loves ?page])] [?entity :page/loves ?page])]
simple-schema)))) conn)))))
(deftest test-tag-projection (defn tag-clauses [column input]
(is (= '{:select ([:datoms0.e :page] (let [codes (cc/->tag-codes input)]
[:datoms1.v :thing] (if (= 1 (count codes))
[:datoms1.value_type_tag :_thing_type_tag]), [:= 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], :modifiers [:distinct],
:from ([:datoms datoms0] :from '([:all_datoms all_datoms0]
[:datoms datoms1]), [:datoms datoms1]),
:where (:and :where (list
[:= :datoms0.a "page/url"] :and
[:= :datoms0.value_type_tag 10] (tag-clauses :all_datoms0.value_type_tag "http://example.com/")
[:= :datoms0.v "http://example.com/"] [:= :all_datoms0.v "http://example.com/"]
(:or (list
[:= :datoms1.a "page/likes"] :or
[:= :datoms1.a "page/loves"]) [:= :datoms1.a (:page/likes attrs)]
[:= :datoms0.e :datoms1.e])} [:= :datoms1.a (:page/loves attrs)])
[:= :all_datoms0.e :datoms1.e])}
(expand (expand
'[:find ?page ?thing :in $ ?latest :where '[:find ?page ?thing :in $ ?latest :where
[?page :page/url "http://example.com/"] [?page _ "http://example.com/"]
(or (or
[?page :page/likes ?thing] [?page :page/likes ?thing]
[?page :page/loves ?thing])] [?page :page/loves ?thing])]
simple-schema)))) 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)))))