mirror of
https://github.com/logseq/logseq.git
synced 2026-04-25 22:54:48 +00:00
Simple query builder --------- Co-authored-by: Gabriel Horner <gabriel@logseq.com> Co-authored-by: charlie <xyhp915@qq.com>
198 lines
5.4 KiB
Clojure
198 lines
5.4 KiB
Clojure
(ns frontend.handler.query.builder
|
|
"DSL query builder handler"
|
|
(:require [clojure.walk :as walk]
|
|
[logseq.graph-parser.util.page-ref :as page-ref]
|
|
[lambdaisland.glogi :as log]
|
|
[frontend.db.query-dsl :as query-dsl]))
|
|
|
|
;; TODO: make it extensible for Datalog/SPARQL etc.
|
|
|
|
(def operators [:and :or :not])
|
|
(def operators-set (set operators))
|
|
(def page-filters ["all page tags"
|
|
"namespace"
|
|
"tags"
|
|
"property"
|
|
"sample"])
|
|
(def block-filters ["page reference"
|
|
"property"
|
|
"task"
|
|
"priority"
|
|
"page"
|
|
"full text search"
|
|
"between"
|
|
"sample"])
|
|
|
|
(defn- vec-dissoc-item
|
|
[vec idx]
|
|
(into (subvec vec 0 idx) (subvec vec (inc idx))))
|
|
|
|
(defn- vec-assoc-item
|
|
[vec idx item]
|
|
(into (conj (subvec vec 0 idx) item)
|
|
(subvec vec idx)))
|
|
|
|
(defn- vec-replace-item
|
|
[v idx item]
|
|
(into (if (and (coll? item)
|
|
(not (operators-set (first item))))
|
|
(vec (concat (subvec v 0 idx) item))
|
|
(conj (subvec v 0 idx) item))
|
|
(subvec v (inc idx))))
|
|
|
|
(defn add-element
|
|
[q loc x]
|
|
{:pre [(vector? loc) (some? x)]}
|
|
(cond
|
|
(and (seq loc) (= 1 (count loc)))
|
|
(vec-assoc-item q (first loc) x)
|
|
|
|
(seq loc)
|
|
(update-in q (vec (butlast loc))
|
|
(fn [v]
|
|
(vec-assoc-item v (last loc) x)))
|
|
|
|
(seq q)
|
|
(conj q x)
|
|
|
|
:else
|
|
[x]))
|
|
|
|
(defn append-element
|
|
[q loc x]
|
|
{:pre [(vector? loc) (some? x)]}
|
|
(let [idx (count (get-in q (vec (butlast loc))))
|
|
loc' (vec-replace-item loc (dec (count loc)) idx)]
|
|
(add-element q loc' x)))
|
|
|
|
(defn remove-element
|
|
[q loc]
|
|
(if (seq loc)
|
|
(let [idx (last loc)
|
|
ks (vec (butlast loc))
|
|
f #(vec-dissoc-item % idx)]
|
|
(if (seq ks)
|
|
(let [result (update-in q ks f)]
|
|
(if (seq (get-in result ks))
|
|
result
|
|
;; remove the wrapped empty vector
|
|
(remove-element result ks)))
|
|
(f q)))
|
|
;; default to AND operator
|
|
[:and]))
|
|
|
|
(defn replace-element
|
|
[q loc x]
|
|
{:pre [(vector? loc) (seq loc) (some? x)]}
|
|
(if (= 1 (count loc))
|
|
(vec-replace-item q (first loc) x)
|
|
(update-in q (vec (butlast loc))
|
|
(fn [v]
|
|
(vec-replace-item v (last loc) x)))))
|
|
|
|
(defn- fallback-to-default [result default-value failed-data]
|
|
(if (empty? result)
|
|
(do
|
|
(log/error :query-builder/wrap-unwrap-operator-failed failed-data)
|
|
default-value)
|
|
result))
|
|
|
|
(defn wrap-operator
|
|
[q loc operator]
|
|
{:pre [(seq q) (operators-set operator)]}
|
|
(let [result (if (or (= loc [0]) (empty? loc))
|
|
[operator q]
|
|
(when-let [x (get-in q loc)]
|
|
(let [x' [operator x]]
|
|
(replace-element q loc x'))))]
|
|
(fallback-to-default result q {:op "wrap-operator"
|
|
:q q
|
|
:loc loc
|
|
:operator operator})))
|
|
|
|
(defn unwrap-operator
|
|
[q loc]
|
|
{:pre [(seq q) (seq loc)]}
|
|
(let [result (if (and (= loc [0]) (operators-set (first q)))
|
|
(second q)
|
|
(when-let [x (get-in q loc)]
|
|
(when (and (operators-set (first x))
|
|
(seq (rest x)))
|
|
(let [x' (rest x)]
|
|
(replace-element q loc x')))))]
|
|
(fallback-to-default result q {:op "unwrap-operator"
|
|
:q q
|
|
:loc loc})))
|
|
|
|
(defn ->page-ref
|
|
[x]
|
|
(if (string? x)
|
|
(symbol (page-ref/->page-ref x))
|
|
(->page-ref (second x))))
|
|
|
|
(defn- ->dsl*
|
|
[f]
|
|
(cond
|
|
(and (vector? f) (= :priority (keyword (first f))))
|
|
(vec (cons (symbol :priority) (map symbol (rest f))))
|
|
|
|
(and (vector? f) (= :task (keyword (first f))))
|
|
(vec (cons (symbol :task) (map symbol (rest f))))
|
|
|
|
(and (vector? f) (= :page-ref (keyword (first f))))
|
|
(->page-ref (second f))
|
|
|
|
(and (vector? f) (= :page-tags (keyword (first f))))
|
|
[(symbol :page-tags) (->page-ref (second f))]
|
|
|
|
(and (vector? f) (= :between (keyword (first f))))
|
|
(into [(symbol :between)] (map ->page-ref (rest f)))
|
|
|
|
;; property key value
|
|
(and (vector? f) (= 3 (count f)) (contains? #{:page-property :property} (keyword (first f))))
|
|
(let [l (if (page-ref/page-ref? (str (last f)))
|
|
(symbol (last f))
|
|
(last f))]
|
|
(into [(symbol (first f))] [(second f) l]))
|
|
|
|
(and (vector? f) (contains? #{:page :namespace :tags} (keyword (first f))))
|
|
(into [(symbol (first f))] (map ->page-ref (rest f)))
|
|
|
|
:else f))
|
|
|
|
(defn ->dsl
|
|
[col]
|
|
(->
|
|
(walk/prewalk
|
|
(fn [f]
|
|
(let [f' (->dsl* f)]
|
|
(cond
|
|
(and (vector? f') (keyword (first f')))
|
|
(cons (symbol (first f')) (rest f'))
|
|
|
|
:else f')))
|
|
col)
|
|
(query-dsl/simplify-query)))
|
|
|
|
(defn from-dsl
|
|
[dsl-form]
|
|
(walk/prewalk
|
|
(fn [f]
|
|
(cond
|
|
(and (vector? f) (vector? (first f)))
|
|
[:page-ref (page-ref/get-page-name (str f))]
|
|
|
|
(and (string? f) (page-ref/get-page-name f))
|
|
[:page-ref (page-ref/get-page-name f)]
|
|
|
|
(and (list? f)
|
|
(symbol? (first f))
|
|
(operators-set (keyword (first f)))) ; operator
|
|
(into [(keyword (first f))] (rest f))
|
|
|
|
(list? f)
|
|
(vec f)
|
|
|
|
:else f))
|
|
dsl-form))
|