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

View file

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

View file

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

View file

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

View file

@ -37,3 +37,5 @@
(def entid db/entid)
(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)))))))