Add d/q; make query minimally schema aware.
This commit is contained in:
parent
65ed0976dd
commit
b4e5c88d6a
6 changed files with 160 additions and 93 deletions
|
@ -144,13 +144,35 @@
|
||||||
]
|
]
|
||||||
rowid)))
|
rowid)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn datoms-attribute-transform
|
||||||
|
[db x]
|
||||||
|
{:pre [(db? db)]}
|
||||||
|
(entid db x))
|
||||||
|
|
||||||
|
(defn datoms-constant-transform
|
||||||
|
[db x]
|
||||||
|
{:pre [(db? db)]}
|
||||||
|
(sqlite-schema/->SQLite x))
|
||||||
|
|
||||||
|
(defn datoms-source [db]
|
||||||
|
(source/map->DatomsSource
|
||||||
|
{:table :datoms
|
||||||
|
:fulltext-table :fulltext_values
|
||||||
|
:fulltext-view :all_datoms
|
||||||
|
:columns [:e :a :v :tx :added]
|
||||||
|
:attribute-transform (partial datoms-attribute-transform db)
|
||||||
|
:constant-transform (partial datoms-constant-transform db)
|
||||||
|
:table-alias source/gensym-table-alias
|
||||||
|
:make-constraints nil}))
|
||||||
|
|
||||||
(defrecord DB [sqlite-connection schema entids ident-map current-tx]
|
(defrecord DB [sqlite-connection schema entids ident-map current-tx]
|
||||||
;; ident-map maps between keyword idents and integer entids. The set of idents and entids is
|
;; ident-map maps between keyword idents and integer entids. The set of idents and entids is
|
||||||
;; disjoint, so we represent both directions of the mapping in the same map for simplicity. Also
|
;; disjoint, so we represent both directions of the mapping in the same map for simplicity. Also
|
||||||
;; for simplicity, we assume that an entid has at most one associated ident, and vice-versa. See
|
;; for simplicity, we assume that an entid has at most one associated ident, and vice-versa. See
|
||||||
;; http://docs.datomic.com/identity.html#idents.
|
;; http://docs.datomic.com/identity.html#idents.
|
||||||
IDB
|
IDB
|
||||||
(query-context [db] (context/->Context (source/datoms-source db) nil nil))
|
(query-context [db] (context/->Context (datoms-source db) nil nil))
|
||||||
|
|
||||||
(schema [db] (.-schema db))
|
(schema [db] (.-schema db))
|
||||||
|
|
||||||
|
|
|
@ -116,17 +116,18 @@
|
||||||
[q]
|
[q]
|
||||||
(dp/parse-query q))
|
(dp/parse-query q))
|
||||||
|
|
||||||
(comment
|
#_
|
||||||
(def sql-quoting-style nil)
|
(def sql-quoting-style nil)
|
||||||
(datomish.query/find->sql-string
|
|
||||||
(datomish.query.context/->Context (datomish.query.source/datoms-source nil) nil nil)
|
#_
|
||||||
(datomish.query/parse
|
(datomish.query/find->sql-string
|
||||||
'[:find ?timestampMicros ?page :in $ ?latest :where
|
(datomish.query.context/->Context (datomish.query.source/datoms-source nil) nil nil)
|
||||||
[?page :page/starred true ?t]
|
(datomish.query/parse
|
||||||
[?t :db/txInstant ?timestampMicros]
|
'[:find ?timestampMicros ?page :in $ ?latest :where
|
||||||
(not [(> ?t ?latest)]) ])
|
[?page :page/starred true ?t]
|
||||||
{:latest 5})
|
[?t :db/txInstant ?timestampMicros]
|
||||||
)
|
(not [(> ?t ?latest)]) ])
|
||||||
|
{:latest 5})
|
||||||
|
|
||||||
#_
|
#_
|
||||||
(datomish.query/find->sql-string
|
(datomish.query/find->sql-string
|
||||||
|
|
|
@ -4,46 +4,46 @@
|
||||||
|
|
||||||
(ns datomish.query.clauses
|
(ns datomish.query.clauses
|
||||||
(:require
|
(:require
|
||||||
[datomish.query.cc :as cc]
|
[datomish.query.cc :as cc]
|
||||||
[datomish.query.functions :as functions]
|
[datomish.query.functions :as functions]
|
||||||
[datomish.query.source
|
[datomish.query.source
|
||||||
:refer [attribute-in-source
|
:refer [attribute-in-source
|
||||||
constant-in-source
|
constant-in-source
|
||||||
source->from
|
source->from
|
||||||
source->constraints]]
|
source->constraints]]
|
||||||
[datomish.util :as util #?(:cljs :refer-macros :clj :refer) [raise raise-str cond-let]]
|
[datomish.util :as util #?(:cljs :refer-macros :clj :refer) [raise raise-str cond-let]]
|
||||||
[datascript.parser :as dp
|
[datascript.parser :as dp
|
||||||
#?@(:cljs
|
#?@(:cljs
|
||||||
[:refer
|
[:refer
|
||||||
[
|
[
|
||||||
Constant
|
Constant
|
||||||
DefaultSrc
|
DefaultSrc
|
||||||
Function
|
Function
|
||||||
Not
|
Not
|
||||||
Or
|
Or
|
||||||
Pattern
|
Pattern
|
||||||
Placeholder
|
Placeholder
|
||||||
PlainSymbol
|
PlainSymbol
|
||||||
Predicate
|
Predicate
|
||||||
Variable
|
Variable
|
||||||
]])]
|
]])]
|
||||||
[honeysql.core :as sql]
|
[honeysql.core :as sql]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
)
|
)
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import
|
(:import
|
||||||
[datascript.parser
|
[datascript.parser
|
||||||
Constant
|
Constant
|
||||||
DefaultSrc
|
DefaultSrc
|
||||||
Function
|
Function
|
||||||
Not
|
Not
|
||||||
Or
|
Or
|
||||||
Pattern
|
Pattern
|
||||||
Placeholder
|
Placeholder
|
||||||
PlainSymbol
|
PlainSymbol
|
||||||
Predicate
|
Predicate
|
||||||
Variable
|
Variable
|
||||||
])))
|
])))
|
||||||
|
|
||||||
;; Pattern building is recursive, so we need forward declarations.
|
;; Pattern building is recursive, so we need forward declarations.
|
||||||
(declare
|
(declare
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
(defn- plain-symbol->sql-predicate-symbol [fn]
|
(defn- plain-symbol->sql-predicate-symbol [fn]
|
||||||
(when-not (instance? PlainSymbol fn)
|
(when-not (instance? PlainSymbol fn)
|
||||||
(raise-str "Predicate functions must be named by plain symbols." fn))
|
(raise-str "Predicate functions must be named by plain symbols." fn))
|
||||||
(#{:> :< :=} (keyword (name (:symbol fn)))))
|
(#{:> :>= :< :<= := :!=} (keyword (name (:symbol fn)))))
|
||||||
|
|
||||||
(defn apply-predicate-clause [cc predicate]
|
(defn apply-predicate-clause [cc predicate]
|
||||||
(when-not (instance? Predicate predicate)
|
(when-not (instance? Predicate predicate)
|
||||||
|
@ -240,14 +240,14 @@
|
||||||
|
|
||||||
(defn not-join->where-fragment [not-join]
|
(defn not-join->where-fragment [not-join]
|
||||||
[:not
|
[:not
|
||||||
(if (empty? (:bindings (:cc not-join)))
|
(if (empty? (:bindings (:cc not-join)))
|
||||||
;; If the `not` doesn't establish any bindings, it means it only contains
|
;; If the `not` doesn't establish any bindings, it means it only contains
|
||||||
;; expressions that constrain variables established outside itself.
|
;; expressions that constrain variables established outside itself.
|
||||||
;; We can just return an expression.
|
;; We can just return an expression.
|
||||||
(cons :and (:wheres (:cc not-join)))
|
(cons :and (:wheres (:cc not-join)))
|
||||||
|
|
||||||
;; If it does establish bindings, then it has to be a subquery.
|
;; If it does establish bindings, then it has to be a subquery.
|
||||||
[:exists (merge {:select [1]} (cc->partial-subquery (:cc not-join)))])])
|
[:exists (merge {:select [1]} (cc->partial-subquery (:cc not-join)))])])
|
||||||
|
|
||||||
|
|
||||||
;; A simple Or clause is one in which each branch can be evaluated against
|
;; A simple Or clause is one in which each branch can be evaluated against
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
|
|
||||||
(ns datomish.query.source
|
(ns datomish.query.source
|
||||||
(:require
|
(:require
|
||||||
[datomish.query.transforms :as transforms]
|
[datomish.query.transforms :as transforms]
|
||||||
[datascript.parser
|
[datascript.parser
|
||||||
#?@(:cljs
|
#?@(:cljs
|
||||||
[:refer [Variable Constant Placeholder]])])
|
[:refer [Variable Constant Placeholder]])])
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(:import [datascript.parser Variable Constant Placeholder])))
|
(:import [datascript.parser Variable Constant Placeholder])))
|
||||||
|
|
||||||
(defn- gensym-table-alias [table]
|
(defn gensym-table-alias [table]
|
||||||
(gensym (name table)))
|
(gensym (name table)))
|
||||||
|
|
||||||
;;;
|
;;;
|
||||||
|
@ -43,25 +43,25 @@
|
||||||
(constant-in-source [source constant]))
|
(constant-in-source [source constant]))
|
||||||
|
|
||||||
(defrecord
|
(defrecord
|
||||||
DatomsSource
|
DatomsSource
|
||||||
[table ; Typically :datoms.
|
[table ; Typically :datoms.
|
||||||
fulltext-table ; Typically :fulltext_values
|
fulltext-table ; Typically :fulltext_values
|
||||||
fulltext-view ; Typically :all_datoms
|
fulltext-view ; Typically :all_datoms
|
||||||
columns ; e.g., [:e :a :v :tx]
|
columns ; e.g., [:e :a :v :tx]
|
||||||
|
|
||||||
;; `attribute-transform` is a function from attribute to constant value. Used to
|
;; `attribute-transform` is a function from attribute to constant value. Used to
|
||||||
;; turn, e.g., :p/attribute into an interned integer.
|
;; turn, e.g., :p/attribute into an interned integer.
|
||||||
;; `constant-transform` is a function from constant value to constant value. Used to
|
;; `constant-transform` is a function from constant value to constant value. Used to
|
||||||
;; turn, e.g., the literal 'true' into 1.
|
;; turn, e.g., the literal 'true' into 1.
|
||||||
attribute-transform
|
attribute-transform
|
||||||
constant-transform
|
constant-transform
|
||||||
|
|
||||||
;; `table-alias` is a function from table to alias, e.g., :datoms => :datoms1234.
|
;; `table-alias` is a function from table to alias, e.g., :datoms => :datoms1234.
|
||||||
table-alias
|
table-alias
|
||||||
|
|
||||||
;; Not currently used.
|
;; Not currently used.
|
||||||
make-constraints ; ?fn [source alias] => [where-clauses]
|
make-constraints ; ?fn [source alias] => [where-clauses]
|
||||||
]
|
]
|
||||||
Source
|
Source
|
||||||
|
|
||||||
(source->from [source attribute]
|
(source->from [source attribute]
|
||||||
|
@ -93,15 +93,3 @@
|
||||||
|
|
||||||
(constant-in-source [source constant]
|
(constant-in-source [source constant]
|
||||||
((:constant-transform source) constant)))
|
((:constant-transform source) constant)))
|
||||||
|
|
||||||
(defn datoms-source [db]
|
|
||||||
(map->DatomsSource
|
|
||||||
{:table :datoms
|
|
||||||
:fulltext-table :fulltext_values
|
|
||||||
:fulltext-view :all_datoms
|
|
||||||
:columns [:e :a :v :tx :added]
|
|
||||||
:attribute-transform transforms/attribute-transform-string
|
|
||||||
:constant-transform transforms/constant-transform-default
|
|
||||||
:table-alias gensym-table-alias
|
|
||||||
:make-constraints nil}))
|
|
||||||
|
|
||||||
|
|
|
@ -37,3 +37,5 @@
|
||||||
(def entid db/entid)
|
(def entid db/entid)
|
||||||
|
|
||||||
(def ident db/ident)
|
(def ident db/ident)
|
||||||
|
|
||||||
|
(def <q db/<?q)
|
||||||
|
|
54
test/datomish/query_test.cljc
Normal file
54
test/datomish/query_test.cljc
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
(ns datomish.query-test
|
||||||
|
#?(: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
|
||||||
|
[datomish.api :as d]
|
||||||
|
#?@(:clj [[datomish.pair-chan :refer [go-pair <?]]
|
||||||
|
[tempfile.core :refer [tempfile with-tempfile]]
|
||||||
|
[datomish.test-macros :refer [deftest-async]]
|
||||||
|
[clojure.test :as t :refer [is are deftest testing]]
|
||||||
|
[clojure.core.async :refer [go <! >!]]])
|
||||||
|
#?@(:cljs [[datomish.pair-chan]
|
||||||
|
[datomish.test-macros :refer-macros [deftest-async]]
|
||||||
|
[datomish.node-tempfile :refer [tempfile]]
|
||||||
|
[cljs.test :as t :refer-macros [is are deftest testing async]]
|
||||||
|
[cljs.core.async :as a :refer [<! >!]]]))
|
||||||
|
#?(:clj
|
||||||
|
(:import [clojure.lang ExceptionInfo]))
|
||||||
|
#?(:clj
|
||||||
|
(:import [datascript.db DB])))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(def Throwable js/Error))
|
||||||
|
|
||||||
|
(def test-schema
|
||||||
|
[{:db/id (d/id-literal :test -1)
|
||||||
|
:db/ident :x
|
||||||
|
:db/unique :db.unique/identity
|
||||||
|
:db/valueType :db.type/integer
|
||||||
|
:db.install/_attribute :db.part/db}
|
||||||
|
])
|
||||||
|
|
||||||
|
(deftest-async test-q
|
||||||
|
(with-tempfile [t (tempfile)]
|
||||||
|
(let [conn (<? (d/<connect t))
|
||||||
|
{tx0 :tx} (<? (d/<transact! conn test-schema))]
|
||||||
|
(try
|
||||||
|
(let [{tx1 :tx} (<? (d/<transact! conn [{:db/id 101 :x 505}]))]
|
||||||
|
|
||||||
|
(is (= (<? (d/<q (d/db conn)
|
||||||
|
`[:find ?e ?a ?v ?tx :in $ :where
|
||||||
|
[?e ?a ?v ?tx]
|
||||||
|
[(> ?tx ~tx0)]
|
||||||
|
[(!= ?a ~(d/entid (d/db conn) :db/txInstant))] ;; TODO: map ident->entid for values.
|
||||||
|
] {}))
|
||||||
|
[[101 (d/entid (d/db conn) :x) 505 tx1]]))) ;; TODO: map entid->ident on egress.
|
||||||
|
(finally
|
||||||
|
(<? (d/<close conn)))))))
|
Loading…
Reference in a new issue