Add d/q; make query minimally schema aware.

This commit is contained in:
Nick Alexander 2016-08-05 17:58:46 -07:00 committed by Richard Newman
parent 65ed0976dd
commit b4e5c88d6a
6 changed files with 160 additions and 93 deletions

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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}))

View file

@ -37,3 +37,5 @@
(def entid db/entid) (def entid db/entid)
(def ident db/ident) (def ident db/ident)
(def <q db/<?q)

View 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)))))))