~subsetpark/doozer

doozer/doozer/compose.janet -rw-r--r-- 4.0 KiB
4a8e0e3e — Zach Smith update docs 1 year, 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
(import /doozer/common)

(defn- base-query
  [source alias]
  @{:from source
    # top-level `:as` is a slight oddity. It pertains to the FROM and is
    # treated as such at rendering time; however, it's kept at the level of the
    # base query because it has to remain unique across multiple compositions.
    :as alias
    :where @[]
    :join @[]
    :select @[]})

(defn- compose-base
  [query-or-table]
  (match query-or-table
    {:as alias :source {:as _}} (error "Only a single FROM alias is allowed in a query")
    {:as alias :source query-or-table} (base-query query-or-table alias)
    {:table tab} (base-query query-or-table (common/make-alias tab))
    {:subquery _} (base-query query-or-table (common/make-alias))
    query-or-table))

(defn- add-join-bindings
  [bindings joins]
  (each {:binding binding :as as} joins
    (put bindings binding as))
  bindings)

(defn- resolve-binding
  ```
  Recursively resolve any bindings in `expr`. `bindings` is a mapping of
  symbols to absolute table names; `default-table` is the default table for the
  context of this binding (in a join, the table being joined; in a where or
  select, the `from` table.)
  ```
  [bindings from-table expr]

  (defn- resolve-columns
    [args &opt implied-table]
    # Determine the table to fill in for "bare" (column-only) references. If
    # it's not provided, assume the reference is to the FROM table.
    (default implied-table from-table)

    (defn- resolve-column
      [column-reference]
      (match column-reference
        # Handle the case where a table has been specified as a reference and
        # needs resolution
        {:table-ref table-ref}
        (if-let [bindings-referent (bindings table-ref)]
          (do
            (put column-reference :table bindings-referent)
            (put column-reference :table-ref nil))
          (errorf `
                  Encountered table reference %q; unknown symbol.

                  Available bindings: %q
                  `
                  (column-reference :table-ref)
                  bindings))
        # Handle the case where no table has been specified; use the implied
        # table (source in selects and wheres, joined table in joins)
        {:column col :implied-table true}
        (do
          (put column-reference :table implied-table)
          (put column-reference :implied-table nil))

        # Recurse
        {:args _}
        (resolve-binding bindings from-table column-reference)))

    (each column-reference args
      (resolve-column column-reference)))

  (match expr
    # join: default table is the table being joined
    {:referent expr} (resolve-columns [expr])
    {:on {:args args} :as joined-table} (do
                                          (resolve-columns args joined-table)
                                          (put expr :binding nil))
    {:args args} (resolve-columns args)
    {:column column} (resolve-columns [expr])))

(defn- resolve-bindings
  [joins-or-wheres bindings default-table]
  (each expr joins-or-wheres
    (resolve-binding bindings default-table expr)))

(defn compose-query
  [query-or-table binding {:join join :where where :select select}]
  (let [{:from source
         :as source-as
         :where original-where
         :select original-select
         :join original-join} (compose-base query-or-table)
        selects (cond
                  (and (not (empty? original-select)) (not (empty? select)))
                  (error "Only a single select expression is allowed in a query")

                  (not (empty? original-select)) original-select
                  select)
        joins (array ;original-join ;join)
        all-bindings (-> @{}
                         (add-join-bindings joins)
                         (put (string binding) source-as))
        wheres (array ;original-where ;where)]

    (resolve-bindings selects all-bindings source-as)
    (resolve-bindings wheres all-bindings source-as)
    (resolve-bindings joins all-bindings source-as)

    @{:from source
      :as source-as
      :where wheres
      :join joins
      :select selects}))