瀏覽代碼

Introduce rules to query-dsl

Fixes #4217 as each use of page-property have unique bindings that don't
clobber each other
Gabriel Horner 3 年之前
父節點
當前提交
12395c61fb

+ 81 - 75
src/main/frontend/db/query_dsl.cljs

@@ -10,6 +10,7 @@
             [frontend.db.model :as model]
             [frontend.db.query-react :as react]
             [frontend.db.utils :as db-utils]
+            [frontend.db.rules :as rules]
             [frontend.template :as template]
             [frontend.text :as text]
             [frontend.util :as util]
@@ -50,8 +51,10 @@
   (when where
     (let [q (if blocks?                   ; FIXME: it doesn't need to be either blocks or pages
               `[:find (~'pull ~'?b ~model/block-attrs)
+                :in ~'$ ~'%
                 :where]
               '[:find (pull ?p [*])
+                :in $ %
                 :where])
           result (if (coll? (first where))
                    (apply conj q where)
@@ -156,16 +159,6 @@
      ;; For integer pages that aren't strings
      [(list 'contains? sym (str v))])]))
 
-(defn ->page-property-query
-  [k v]
-  [['?p :block/name]
-   ['?p :block/properties '?prop]
-   [(list 'get '?prop (keyword k)) '?v]
-   (list
-    'or
-    [(list '= '?v v)]
-    [(list 'contains? '?v v)])])
-
 (defn- build-query-property-two-arg
   [e current-filter counter]
   (let [k (string/replace (name (nth e 1)) "_" "-")
@@ -183,15 +176,17 @@
   [fe clauses current-filter nested-and?]
   (cond
     (= fe 'not)
-    (let [clauses (if (coll? (first clauses))
-                    (apply concat clauses)
-                    clauses)
-          clauses (if (and (= 1 (count clauses))
-                           (= 'and (ffirst clauses)))
-                    ;; unflatten
-                    (rest (first clauses))
-                    clauses)]
-      (cons fe (seq clauses)))
+    (if (every? list? clauses)
+      (cons fe (seq clauses))
+      (let [clauses (if (coll? (first clauses))
+                      (apply concat clauses)
+                      clauses)
+            clauses (if (and (= 1 (count clauses))
+                             (= 'and (ffirst clauses)))
+                      ;; unflatten
+                      (rest (first clauses))
+                      clauses)]
+        (cons fe (seq clauses))))
 
     (coll? (first clauses))
     (cond
@@ -205,10 +200,12 @@
 
       :else
       (->> (map (fn [result]
-                  (let [result (if (vector? (ffirst result))
-                                 (apply concat result)
-                                 result)]
-                    (cons 'and (seq result)))) clauses)
+                  (if (list? result)
+                    result
+                    (let [result (if (vector? (ffirst result))
+                                   (apply concat result)
+                                   result)]
+                      (cons 'and (seq result))))) clauses)
            (apply list fe)))
 
     :else
@@ -218,35 +215,41 @@
 
 (defn- build-query-and-or-not
   [repo e {:keys [current-filter vars] :as env} level fe]
-  (let [clauses (->> (map (fn [form]
-                            (build-query repo form (assoc env :current-filter fe) (inc level)))
-                          (rest e))
+  (let [raw-clauses (map (fn [form]
+                           (build-query repo form (assoc env :current-filter fe) (inc level)))
+                         (rest e))
+        clauses (->> raw-clauses
+                     (map :query)
                      remove-nil?
                      (distinct))
         nested-and? (and (= fe 'and) (= current-filter 'and))]
     (when (seq clauses)
       (let [result (build-query-and-or-not-result
                     fe clauses current-filter nested-and?)
-            vars' (set/union (set @vars) (collect-vars result))]
+            vars' (set/union (set @vars) (collect-vars result))
+            query (cond
+                    ;; TODO: more thoughts
+                    (and (= current-filter 'and)
+                         (= 'or fe)
+                         (= #{'?b} vars'))
+                    [(concat result [['?b]])]
+
+                    nested-and?
+                    result
+
+                    (and (zero? level) (= 'and fe))
+                    (if (list? (first clauses))
+                      result
+                      (distinct (apply concat clauses)))
+
+                    (and (zero? level) (= 'or fe))
+                    result
+
+                    :else
+                    [result])]
         (reset! vars vars')
-        (cond
-          ;; TODO: more thoughts
-          (and (= current-filter 'and)
-               (= 'or fe)
-               (= #{'?b} vars'))
-          [(concat result [['?b]])]
-
-          nested-and?
-          result
-
-          (and (zero? level) (= 'and fe))
-          (distinct (apply concat clauses))
-
-          (and (zero? level) (= 'or fe))
-          result
-
-          :else
-          [result])))))
+        {:query query
+         :rules (distinct (mapcat :rules raw-clauses))}))))
 
 (defn- build-query-between-two-arg
   [e]
@@ -291,14 +294,13 @@
   [e]
   (let [[k v] (rest e)
         k (string/replace (name k) "_" "-")]
-    (if-not (nil? v)
-      (let [v (text/parse-property k v)
-            v (if (coll? v) (first v) v)]
-        (->page-property-query k v))
-      [['?p :block/name]
-       ['?p :block/properties '?prop]
-       [(list 'get '?prop (keyword k)) '?prop-v]
-       [true]])))
+    (if (some? v)
+      (let [v' (text/parse-property k v)
+            val (if (coll? v') (first v') v')]
+        {:query (list 'page-property '?p (keyword k) val)
+         :rules [:page-property]})
+      {:query (list 'has-page-property '?p (keyword k))
+       :rules [:has-page-property]})))
 
 (defn- build-query-page-tags
   [e counter]
@@ -333,20 +335,21 @@
        page-ref?
        (let [page-name (-> (text/page-ref-un-brackets! e)
                            (util/page-name-sanity-lc))]
-         [['?b :block/path-refs [:block/name page-name]]])
+         {:query [['?b :block/path-refs [:block/name page-name]]]})
 
        (string? e)                      ; block content full-text search, could be slow
        (do
          (reset! blocks? true)
-         [['?b :block/content '?content]
-          [(list 'clojure.string/includes? '?content e)]])
+         {:query
+          [['?b :block/content '?content]
+           [(list 'clojure.string/includes? '?content e)]]})
 
        (contains? #{'and 'or 'not} fe)
        (build-query-and-or-not repo e env level fe)
 
        (and (= 'between fe)
             (= 3 (count e)))
-       (build-query-between-two-arg e)
+       {:query (build-query-between-two-arg e)}
 
        ;; (between created_at -1d today)
        (and (= 'between fe)
@@ -361,24 +364,24 @@
              (when (and start end)
                (let [[start end] (sort [start end])
                      sym '?v]
-                 [['?b :block/properties '?prop]
-                  [(list 'get '?prop k) sym]
-                  [(list '>= sym start)]
-                  [(list '< sym end)]])))))
+                 {:query [['?b :block/properties '?prop]
+                          [(list 'get '?prop k) sym]
+                          [(list '>= sym start)]
+                          [(list '< sym end)]]})))))
 
        (and (= 'property fe)
             (= 3 (count e)))
-       (build-query-property-two-arg e current-filter counter)
+       {:query (build-query-property-two-arg e current-filter counter)}
 
        (and (= 'property fe)
             (= 2 (count e)))
-       (build-query-property-one-arg e)
+       {:query (build-query-property-one-arg e)}
 
        (or (= 'todo fe) (= 'task fe))
-       (build-query-todo e)
+       {:query (build-query-todo e)}
 
        (= 'priority fe)
-       (build-query-priority e)
+       {:query (build-query-priority e)}
 
        (= 'sort-by fe)
        (let [[k order] (rest e)
@@ -408,24 +411,24 @@
        (= 'page fe)
        (let [page-name (text/page-ref-un-brackets! (str (first (rest e))))
              page-name (util/page-name-sanity-lc page-name)]
-         [['?b :block/page [:block/name page-name]]])
+         {:query [['?b :block/page [:block/name page-name]]]})
 
        (and (= 'namespace fe)
             (= 2 (count e)))
        (let [page-name (text/page-ref-un-brackets! (str (first (rest e))))
              page (util/page-name-sanity-lc page-name)]
          (when-not (string/blank? page)
-           [['?p :block/namespace '?parent]
-            ['?parent :block/name page]]))
+           {:query [['?p :block/namespace '?parent]
+                    ['?parent :block/name page]]}))
 
        (= 'page-property fe)
        (build-page-property e)
 
        (= 'page-tags fe)
-       (build-query-page-tags e counter)
+       {:query (build-query-page-tags e counter)}
 
        (= 'all-page-tags fe)
-       [['?e :block/tags '?p]]
+       {:query [['?e :block/tags '?p]]}
 
        (= 'sample fe)
        (when-let [num (second e)]
@@ -505,10 +508,11 @@
             (let [sort-by (atom nil)
                   blocks? (atom nil)
                   sample (atom nil)
-                  result (when form (build-query repo form {:sort-by sort-by
-                                                            :blocks? blocks?
-                                                            :counter counter
-                                                            :sample sample}))]
+                  {result :query rules :rules}
+                  (when form (build-query repo form {:sort-by sort-by
+                                                     :blocks? blocks?
+                                                     :counter counter
+                                                     :sample sample}))]
               (cond
                 (and (nil? result) (string? form))
                 form
@@ -528,6 +532,7 @@
                                               result)]
                                  (add-bindings! form result)))]
                   {:query result
+                   :rules (mapv rules/query-dsl-rules rules)
                    :sort-by @sort-by
                    :blocks? (boolean @blocks?)
                    :sample sample})))))
@@ -541,7 +546,7 @@
    (when (string? query-string)
      (let [query-string (template/resolve-dynamic-template! query-string)]
        (when-not (string/blank? query-string)
-         (let [{:keys [query sort-by blocks? sample] :as result} (parse repo query-string)
+         (let [{:keys [query rules sort-by blocks? sample] :as result} (parse repo query-string)
                query (if (string? query) (string/trim query) query)
                full-text-query? (and (string? result)
                                      (not (string/includes? result " ")))]
@@ -558,6 +563,7 @@
                      transform-fn (comp sort-by random-samples)]
                  (react/react-query repo
                                     {:query query
+                                     :rules rules
                                      :query-string query-string}
                                     {:use-cache? false
                                      :transform-fn transform-fn}))))))))))

+ 6 - 3
src/main/frontend/db/query_react.cljs

@@ -112,17 +112,20 @@
          f)) query)))
 
 (defn react-query
-  [repo {:keys [query inputs] :as query'} query-opts]
+  [repo {:keys [query inputs rules] :as query'} query-opts]
   (let [pprint (if config/dev? (fn [_] nil) debug/pprint)]
     (pprint "================")
     (pprint "Use the following to debug your datalog queries:")
     (pprint query')
     (try
       (let [query (resolve-query query)
-            inputs (map resolve-input inputs)
+            resolved-inputs (mapv resolve-input inputs)
+            inputs (cond-> resolved-inputs
+                           rules
+                           (conj rules))
             repo (or repo (state/get-current-repo))
             k [:custom query']]
-        (pprint "inputs (post-resolution):" inputs)
+        (pprint "inputs (post-resolution):" resolved-inputs)
         (pprint "query-opts:" query-opts)
         (apply react/q repo k query-opts query inputs))
       (catch js/Error e

+ 15 - 0
src/main/frontend/db/rules.cljs

@@ -30,3 +30,18 @@
     ;;            (not-join [?e ?v]
     ;;                      [?e ?a ?v]))]
     ])
+
+(def query-dsl-rules
+  "Rules used by frontend.db.query-dsl"
+  {:page-property
+   '[(page-property ?p ?key ?val)
+     [?p :block/name]
+     [?p :block/properties ?prop]
+     [(get ?prop ?key) ?v]
+     (or [(= ?v ?val)] [(contains? ?v ?val)])]
+
+   :has-page-property
+   '[(has-page-property ?p ?key)
+     [?p :block/name]
+     [?p :block/properties ?prop]
+     [(get ?prop ?key)]]})

+ 9 - 3
src/test/frontend/db/query_dsl_test.cljs

@@ -168,9 +168,9 @@ prop-d:: nada"}])
                     {:file/path "pages/page2.md"
                      :file/content "foo:: bar"}
                     {:file/path "pages/page3.md"
-                     :file/content "parent:: [[child page 1]], child page 2"}
+                     :file/content "parent:: [[child page 1]], child page 2\nfoo:: bar"}
                     {:file/path "pages/page4.md"
-                     :file/content "parent:: child page 2"}])
+                     :file/content "parent:: child page 2\nfoo:: baz"}])
   (is (= ["page1" "page3" "page4"]
          (map :block/name (dsl-query "(page-property parent)")))
       "Pages have given property")
@@ -197,7 +197,13 @@ prop-d:: nada"}])
          (map
           :block/name
           (dsl-query "(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))")))
-      "Page property queries ORed"))
+      "Page property queries ORed")
+
+  (is (= ["page4"]
+         (map
+          :block/name
+          (dsl-query "(and (page-property parent [[child page 2]]) (not (page-property foo bar)))")))
+      "Page property queries NOTed"))
 
 (deftest ^:large-vars/cleanup-todo test-parse
   []