Add d/q; make query minimally schema aware.
This commit is contained in:
parent
0f7c1cad79
commit
4d34c820b8
6 changed files with 160 additions and 93 deletions
|
@ -144,13 +144,35 @@
|
|||
]
|
||||
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]
|
||||
;; 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
|
||||
;; for simplicity, we assume that an entid has at most one associated ident, and vice-versa. See
|
||||
;; http://docs.datomic.com/identity.html#idents.
|
||||
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))
|
||||
|
||||
|
|
|
@ -116,17 +116,18 @@
|
|||
[q]
|
||||
(dp/parse-query q))
|
||||
|
||||
(comment
|
||||
(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
|
||||
'[:find ?timestampMicros ?page :in $ ?latest :where
|
||||
[?page :page/starred true ?t]
|
||||
[?t :db/txInstant ?timestampMicros]
|
||||
(not [(> ?t ?latest)]) ])
|
||||
{:latest 5})
|
||||
)
|
||||
#_
|
||||
(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
|
||||
'[:find ?timestampMicros ?page :in $ ?latest :where
|
||||
[?page :page/starred true ?t]
|
||||
[?t :db/txInstant ?timestampMicros]
|
||||
(not [(> ?t ?latest)]) ])
|
||||
{:latest 5})
|
||||
|
||||
#_
|
||||
(datomish.query/find->sql-string
|
||||
|
|
|
@ -4,46 +4,46 @@
|
|||
|
||||
(ns datomish.query.clauses
|
||||
(:require
|
||||
[datomish.query.cc :as cc]
|
||||
[datomish.query.functions :as functions]
|
||||
[datomish.query.source
|
||||
:refer [attribute-in-source
|
||||
constant-in-source
|
||||
source->from
|
||||
source->constraints]]
|
||||
[datomish.util :as util #?(:cljs :refer-macros :clj :refer) [raise raise-str cond-let]]
|
||||
[datascript.parser :as dp
|
||||
#?@(:cljs
|
||||
[:refer
|
||||
[
|
||||
Constant
|
||||
DefaultSrc
|
||||
Function
|
||||
Not
|
||||
Or
|
||||
Pattern
|
||||
Placeholder
|
||||
PlainSymbol
|
||||
Predicate
|
||||
Variable
|
||||
]])]
|
||||
[honeysql.core :as sql]
|
||||
[clojure.string :as str]
|
||||
)
|
||||
[datomish.query.cc :as cc]
|
||||
[datomish.query.functions :as functions]
|
||||
[datomish.query.source
|
||||
:refer [attribute-in-source
|
||||
constant-in-source
|
||||
source->from
|
||||
source->constraints]]
|
||||
[datomish.util :as util #?(:cljs :refer-macros :clj :refer) [raise raise-str cond-let]]
|
||||
[datascript.parser :as dp
|
||||
#?@(:cljs
|
||||
[:refer
|
||||
[
|
||||
Constant
|
||||
DefaultSrc
|
||||
Function
|
||||
Not
|
||||
Or
|
||||
Pattern
|
||||
Placeholder
|
||||
PlainSymbol
|
||||
Predicate
|
||||
Variable
|
||||
]])]
|
||||
[honeysql.core :as sql]
|
||||
[clojure.string :as str]
|
||||
)
|
||||
#?(:clj
|
||||
(:import
|
||||
[datascript.parser
|
||||
Constant
|
||||
DefaultSrc
|
||||
Function
|
||||
Not
|
||||
Or
|
||||
Pattern
|
||||
Placeholder
|
||||
PlainSymbol
|
||||
Predicate
|
||||
Variable
|
||||
])))
|
||||
(:import
|
||||
[datascript.parser
|
||||
Constant
|
||||
DefaultSrc
|
||||
Function
|
||||
Not
|
||||
Or
|
||||
Pattern
|
||||
Placeholder
|
||||
PlainSymbol
|
||||
Predicate
|
||||
Variable
|
||||
])))
|
||||
|
||||
;; Pattern building is recursive, so we need forward declarations.
|
||||
(declare
|
||||
|
@ -114,7 +114,7 @@
|
|||
(defn- plain-symbol->sql-predicate-symbol [fn]
|
||||
(when-not (instance? PlainSymbol 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]
|
||||
(when-not (instance? Predicate predicate)
|
||||
|
@ -240,14 +240,14 @@
|
|||
|
||||
(defn not-join->where-fragment [not-join]
|
||||
[:not
|
||||
(if (empty? (:bindings (:cc not-join)))
|
||||
;; If the `not` doesn't establish any bindings, it means it only contains
|
||||
;; expressions that constrain variables established outside itself.
|
||||
;; We can just return an expression.
|
||||
(cons :and (:wheres (:cc not-join)))
|
||||
(if (empty? (:bindings (:cc not-join)))
|
||||
;; If the `not` doesn't establish any bindings, it means it only contains
|
||||
;; expressions that constrain variables established outside itself.
|
||||
;; We can just return an expression.
|
||||
(cons :and (:wheres (:cc not-join)))
|
||||
|
||||
;; If it does establish bindings, then it has to be a subquery.
|
||||
[:exists (merge {:select [1]} (cc->partial-subquery (:cc not-join)))])])
|
||||
;; If it does establish bindings, then it has to be a subquery.
|
||||
[:exists (merge {:select [1]} (cc->partial-subquery (:cc not-join)))])])
|
||||
|
||||
|
||||
;; A simple Or clause is one in which each branch can be evaluated against
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
|
||||
(ns datomish.query.source
|
||||
(:require
|
||||
[datomish.query.transforms :as transforms]
|
||||
[datascript.parser
|
||||
#?@(:cljs
|
||||
[:refer [Variable Constant Placeholder]])])
|
||||
[datomish.query.transforms :as transforms]
|
||||
[datascript.parser
|
||||
#?@(:cljs
|
||||
[:refer [Variable Constant Placeholder]])])
|
||||
#?(: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)))
|
||||
|
||||
;;;
|
||||
|
@ -43,25 +43,25 @@
|
|||
(constant-in-source [source constant]))
|
||||
|
||||
(defrecord
|
||||
DatomsSource
|
||||
[table ; Typically :datoms.
|
||||
fulltext-table ; Typically :fulltext_values
|
||||
fulltext-view ; Typically :all_datoms
|
||||
columns ; e.g., [:e :a :v :tx]
|
||||
DatomsSource
|
||||
[table ; Typically :datoms.
|
||||
fulltext-table ; Typically :fulltext_values
|
||||
fulltext-view ; Typically :all_datoms
|
||||
columns ; e.g., [:e :a :v :tx]
|
||||
|
||||
;; `attribute-transform` is a function from attribute to constant value. Used to
|
||||
;; turn, e.g., :p/attribute into an interned integer.
|
||||
;; `constant-transform` is a function from constant value to constant value. Used to
|
||||
;; turn, e.g., the literal 'true' into 1.
|
||||
attribute-transform
|
||||
constant-transform
|
||||
;; `attribute-transform` is a function from attribute to constant value. Used to
|
||||
;; turn, e.g., :p/attribute into an interned integer.
|
||||
;; `constant-transform` is a function from constant value to constant value. Used to
|
||||
;; turn, e.g., the literal 'true' into 1.
|
||||
attribute-transform
|
||||
constant-transform
|
||||
|
||||
;; `table-alias` is a function from table to alias, e.g., :datoms => :datoms1234.
|
||||
table-alias
|
||||
;; `table-alias` is a function from table to alias, e.g., :datoms => :datoms1234.
|
||||
table-alias
|
||||
|
||||
;; Not currently used.
|
||||
make-constraints ; ?fn [source alias] => [where-clauses]
|
||||
]
|
||||
;; Not currently used.
|
||||
make-constraints ; ?fn [source alias] => [where-clauses]
|
||||
]
|
||||
Source
|
||||
|
||||
(source->from [source attribute]
|
||||
|
@ -93,15 +93,3 @@
|
|||
|
||||
(constant-in-source [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 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