~subsetpark/doozer

e3c58a83f008929e1573bb1ab1425f308d95cb7f — Zach Smith 10 months ago 78e3270
Break out operators into common
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))))