M .gitignore => .gitignore +1 -1
@@ 1,3 1,3 @@
*.db
-/janet_modules
+/jpm_tree
/tags
M doozer/backends/sql.janet => doozer/backends/sql.janet +5 -12
@@ 1,3 1,5 @@
+(import /doozer/common)
+
(def backend
```
The generic doozer backend for all SQL dialects. To be used as a
@@ 38,19 40,10 @@
(fn render-operation
[self op args]
(let [template (case op
- := "%s = %s"
- :not= "%s <> %s"
- :> "%s > %s"
- :< "%s < %s"
- :>= "%s >= %s"
- :<= "%s <= %s"
- :not "NOT %s"
- :in "%s IN %s"
- :nil? "%s IS NULL"
- :and "(%s AND %s)"
- :or "(%s OR %s)"
nil "%s"
- (errorf "Tried to render unknown operator: %q" op))]
+ (if-let [t (common/operators (symbol op))]
+ t
+ (errorf "Tried to render unknown operator: %q" op)))]
(string/format template ;args)))
:render-as
M doozer/common.janet => doozer/common.janet +28 -0
@@ 6,3 6,31 @@
(> (length prefix) 7) (string/slice prefix 0 7)
prefix))
(string prefix (gensym)))
+
+(def operators
+ {'= "%s = %s"
+ 'not= "%s <> %s"
+ '+ "%s + %s"
+ '- "%s - %s"
+ '* "%s * %s"
+ '/ "%s / %s"
+ 'mod "%s % %s"
+ '= "%s = %s"
+ 'not= "%s <> %s"
+ '> "%s > %s"
+ '< "%s < %s"
+ '>= "%s >= %s"
+ '<= "%s <= %s"
+ 'between "%s BETWEEN %s AND %s"
+ 'in "%s IN %s"
+ 'not-in "%s NOT IN %s"
+ 'exists "EXISTS %s"
+ 'nil? "%s IS NULL"
+ 'and "(%s AND %s)"
+ 'or "(%s OR %s)"
+ 'not "NOT %s"
+ 'like "%s LIKE %s"
+ 'glob "%s GLOB %s"
+ # TODO: Make this variadic.
+ 'string "%s || %s"
+ 'unique "UNIQUE %s"})
M doozer/compose.janet => doozer/compose.janet +3 -3
@@ 52,10 52,10 @@
(put column-reference :table bindings-referent)
(put column-reference :table-ref nil))
(errorf `
- Encountered table reference %q; unknown symbol.
+ Encountered table reference %q; unknown symbol.
- Available bindings: %q
- `
+ Available bindings: %q
+ `
(column-reference :table-ref)
bindings))
# Handle the case where no table has been specified; use the implied
M doozer/from.janet => doozer/from.janet +36 -20
@@ 48,21 48,6 @@
[query]
@{:subquery query})
-(defn- op-type
- [op]
- (case op
- '= :=
- 'not= :not=
- '< :<
- '> :>
- '>= :>=
- '<= :<=
- 'not :not
- 'in :in
- 'nil? :nil
- 'and :and
- 'or :or))
-
## Parsing
(defn- make-expression
@@ 87,8 72,8 @@
rest (array/slice rest 1)]
@{:fragment fragment
:args (map make-expression rest)})
- (if-let [op-keyword (op-type head)]
- @{:op op-keyword
+ (if (common/operators head)
+ @{:op (keyword head)
:args (map make-expression rest)}
(make-expr-atom form))))))
@@ 166,6 151,9 @@
(set limit (first rest)))
(each form forms
+ (defn err [] (errorf "Encountered unknown form in query: %q" form))
+ (unless (indexed? form) (err))
+
(def head (first form))
(def rest (array/slice form 1))
(case head
@@ 173,7 161,7 @@
'where (handle-wheres rest)
'select (handle-selects rest)
'limit (handle-limit rest)
- (errorf "Encountered unknown form in query: %q" form)))
+ (err)))
{:join joins
:where wheres
@@ 346,9 334,37 @@
Any unqualified symbol references will be treated, as in `from`, as
references to columns on the main query table. No new bindings can be created
- with this macro; however, existing named bindings can be referred to. See the
+ with this function; however, existing named bindings can be referred to. See the
`:as` key in the `:join` section of the `from` documentation for details.
```
[query where-expr]
- (let [where-query (dsl-to-query [where-expr])]
+ (let [where-query (dsl-to-query [['where where-expr]])]
(compose/compose-query query nil where-query)))
+
+(defn join
+ ```
+ Given an existing query, create a new query with an additional single
+ `join` clause.
+
+ Any unqualified symbol references will be treated, as in `from`, as
+ references to columns on the joined table. No new bindings can be created
+ with this function; however, existing named bindings can be referred to. See the
+ `:as` key in the `:join` section of the `from` documentation for details.
+ ```
+ [query join-expr]
+ (let [join-query (dsl-to-query [['join '_ join-expr]])]
+ (compose/compose-query query nil join-query)))
+
+(defn select
+ ```
+ Given an existing query, create a new query with an additional single
+ `select` clause.
+
+ Any unqualified symbol references will be treated, as in `from`, as
+ references to columns on the main query table. No new bindings can be created
+ with this function; however, existing named bindings can be referred to. See the
+ `:as` key in the `:join` section of the `from` documentation for details.
+ ```
+ [query & select-exprs]
+ (let [select-query (dsl-to-query [['select ;select-exprs]])]
+ (compose/compose-query query nil select-query)))
A test/from-suppl.janet => test/from-suppl.janet +52 -0
@@ 0,0 1,52 @@
+(use testament)
+(import /doozer)
+
+(defmacro- let-query
+ [q & body]
+ ~(let [@query ,q
+ {:from @from
+ :as @alias
+ :join @joins
+ :select @selects
+ :where @wheres
+ :limit @limit} @query]
+ ,;body))
+
+(def q (doozer/from (as "artists" "a")))
+
+(deftest where-test
+ (let-query (doozer/where q '(= name "Metallica"))
+ (let [[where] @wheres]
+ (is (= := (where :op)))
+ (is (= "name" (get-in where [:args 0 :column])))
+ (is (= "Metallica" (get-in where [:args 1 :value]))))))
+
+(deftest join-test
+ (let-query (doozer/join q '["albums" (= ArtistId (. "a" ArtistId))])
+ (let [[join] @joins]
+ (is (= "albums" (join :table)))
+ (let [join-as (join :as)]
+ (is (= "ArtistId" (get-in join [:on :args 0 :column])))
+ (is (= join-as (get-in join [:on :args 0 :table]))))
+ (is (= "ArtistId" (get-in join [:on :args 1 :column])))
+ (is (= @alias (get-in join [:on :args 1 :table]))))))
+
+(deftest join-as-test
+ (let-query (doozer/join q '(as ["albums" (= ArtistId (. "a" ArtistId))] "new_join"))
+ (let [[join] @joins]
+ (is (= "albums" (join :table)))
+ (is (= "new_join" (join :as)))
+ (is (= "ArtistId" (get-in join [:on :args 0 :column])))
+ (is (= "new_join" (get-in join [:on :args 0 :table])))
+ (is (= "ArtistId" (get-in join [:on :args 1 :column])))
+ (is (= @alias (get-in join [:on :args 1 :table]))))))
+
+(deftest select-test
+ (let-query (doozer/select q 'name '(. "a" ArtistId))
+ (let [[sel1 sel2] @selects]
+ (is (= "name" (sel1 :column)))
+ (is (= @alias (sel1 :table)))
+ (is (= "ArtistId" (sel2 :column)))
+ (is (= @alias (sel2 :table))))))
+
+(run-tests!)
M test/from.janet => test/from.janet +7 -0
@@ 236,6 236,13 @@
(is (string/has-prefix? "_" @alias))
(is (@from :subquery))))
+(deftest subquery-uses-table-alias-test
+ (let-query (doozer/from "artists" a
+ (where (not (exists (subquery
+ (doozer/from "albums" ab
+ (where (= (. ab ArtistId)
+ (. a ArtistId)))))))))))
+
(deftest from-as-test
(let-query (doozer/from (as "artists" "artz"))
(is (= "artz" @alias))))