query_dsl.cljs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. (ns frontend.db.query-dsl
  2. (:require [cljs-time.coerce :as tc]
  3. [cljs-time.core :as t]
  4. [cljs.reader :as reader]
  5. [clojure.core]
  6. [clojure.set :as set]
  7. [clojure.string :as string]
  8. [clojure.walk :as walk]
  9. [frontend.date :as date]
  10. [frontend.db.model :as model]
  11. [frontend.db.query-react :as react]
  12. [frontend.db.utils :as db-utils]
  13. [frontend.template :as template]
  14. [frontend.text :as text]
  15. [frontend.util :as util]
  16. [frontend.state :as state]
  17. [frontend.util.property :as property]
  18. [lambdaisland.glogi :as log]))
  19. ;; Query fields:
  20. ;; and
  21. ;; or
  22. ;; not
  23. ;; between
  24. ;; Example: (between -7d +7d)
  25. ;; (between created-at -1d today)
  26. ;; (between last-modified-at -1d today)
  27. ;; [[page-ref]]
  28. ;; property (block)
  29. ;; task (block)
  30. ;; priority (block)
  31. ;; page
  32. ;; page-property (page)
  33. ;; page-tags (page)
  34. ;; all-page-tags
  35. ;; project (block, TBD)
  36. ;; Sort by (field, asc/desc):
  37. ;; created_at
  38. ;; last_modified_at
  39. ;; (sort-by last_modified_at asc)
  40. (defonce remove-nil? (partial remove nil?))
  41. (defn query-wrapper
  42. [where blocks?]
  43. (when where
  44. (let [q (if blocks? ; FIXME: it doesn't need to be either blocks or pages
  45. `[:find (~'pull ~'?b ~model/block-attrs)
  46. :where]
  47. '[:find (pull ?p [*])
  48. :where])
  49. result (if (coll? (first where))
  50. (apply conj q where)
  51. (conj q where))]
  52. (prn "Datascript query: " result)
  53. result)))
  54. ;; (between -7d +7d)
  55. (defn- ->journal-day-int [input]
  56. (let [input (string/lower-case (name input))]
  57. (cond
  58. (= "today" input)
  59. (db-utils/date->int (t/today))
  60. (= "yesterday" input)
  61. (db-utils/date->int (t/yesterday))
  62. (= "tomorrow" input)
  63. (db-utils/date->int (t/plus (t/today) (t/days 1)))
  64. (text/page-ref? input)
  65. (let [input (-> (text/page-ref-un-brackets! input)
  66. (string/replace ":" "")
  67. (string/capitalize))]
  68. (when (date/valid-journal-title? input)
  69. (date/journal-title->int input)))
  70. :else
  71. (let [duration (util/parse-int (subs input 0 (dec (count input))))
  72. kind (last input)
  73. tf (case kind
  74. "y" t/years
  75. "m" t/months
  76. "w" t/weeks
  77. t/days)]
  78. (db-utils/date->int (t/plus (t/today) (tf duration)))))))
  79. (defn- ->timestamp [input]
  80. (let [input (string/lower-case (name input))]
  81. (cond
  82. (= "now" input)
  83. (util/time-ms)
  84. (= "today" input)
  85. (tc/to-long (t/today))
  86. (= "yesterday" input)
  87. (tc/to-long (t/yesterday))
  88. (= "tomorrow" input)
  89. (tc/to-long (t/plus (t/today) (t/days 1)))
  90. (text/page-ref? input)
  91. (let [input (-> (text/page-ref-un-brackets! input)
  92. (string/replace ":" "")
  93. (string/capitalize))]
  94. (when (date/valid-journal-title? input)
  95. (date/journal-title->long input)))
  96. :else
  97. (let [duration (util/parse-int (subs input 0 (dec (count input))))
  98. kind (last input)
  99. tf (case kind
  100. "y" t/years
  101. "m" t/months
  102. "w" t/weeks
  103. "h" t/hours
  104. "n" t/minutes ; min
  105. t/days)]
  106. (tc/to-long (t/plus (t/today) (tf duration)))))))
  107. (defn uniq-symbol
  108. [counter prefix]
  109. (let [result (symbol (str prefix (when-not (zero? @counter)
  110. @counter)))]
  111. (swap! counter inc)
  112. result))
  113. (defn- collect-vars
  114. [l]
  115. (let [vars (atom #{})]
  116. (walk/postwalk
  117. (fn [f]
  118. (when (and (symbol? f) (= \? (first (name f))))
  119. (swap! vars conj f))
  120. f)
  121. l)
  122. @vars))
  123. (defn build-query
  124. ([repo e env]
  125. (build-query repo e (assoc env :vars (atom {})) 0))
  126. ([repo e {:keys [sort-by blocks? sample counter current-filter vars] :as env} level]
  127. ;; TODO: replace with multi-methods for extensibility.
  128. (let [fe (first e)
  129. fe (when fe (symbol (string/lower-case (name fe))))
  130. page-ref? (text/page-ref? e)]
  131. (when (or (and page-ref?
  132. (not (contains? #{'page-property 'page-tags} (:current-filter env))))
  133. (contains? #{'between 'property 'todo 'task 'priority 'sort-by 'page} fe))
  134. (reset! blocks? true))
  135. (cond
  136. (nil? e)
  137. nil
  138. page-ref?
  139. (let [page-name (-> (text/page-ref-un-brackets! e)
  140. (util/page-name-sanity-lc))]
  141. [['?b :block/path-refs [:block/name page-name]]])
  142. (string? e) ; block content full-text search, could be slow
  143. (do
  144. (reset! blocks? true)
  145. [['?b :block/content '?content]
  146. [(list 'clojure.string/includes? '?content e)]])
  147. (contains? #{'and 'or 'not} fe)
  148. (let [clauses (->> (map (fn [form]
  149. (build-query repo form (assoc env :current-filter fe) (inc level)))
  150. (rest e))
  151. remove-nil?
  152. (distinct))
  153. nested-and? (and (= fe 'and) (= current-filter 'and))]
  154. (when (seq clauses)
  155. (let [result (cond
  156. (= fe 'not)
  157. (let [clauses (if (coll? (first clauses))
  158. (apply concat clauses)
  159. clauses)
  160. clauses (if (and (= 1 (count clauses))
  161. (= 'and (ffirst clauses)))
  162. ;; unflatten
  163. (rest (first clauses))
  164. clauses)]
  165. (cons fe (seq clauses)))
  166. (coll? (first clauses))
  167. (cond
  168. (= current-filter 'not)
  169. (->> (apply concat clauses)
  170. (apply list fe))
  171. (or (= current-filter 'or)
  172. nested-and?)
  173. (apply concat clauses)
  174. :else
  175. (->> (map (fn [result]
  176. (let [result (if (vector? (ffirst result))
  177. (apply concat result)
  178. result)]
  179. (cons 'and (seq result)))) clauses)
  180. (apply list fe)))
  181. :else
  182. (apply list fe clauses))
  183. vars' (set/union (set @vars) (collect-vars result))]
  184. (reset! vars vars')
  185. (cond
  186. ;; TODO: more thoughts
  187. (and (= current-filter 'and)
  188. (= 'or fe)
  189. (= #{'?b} vars'))
  190. [(concat result [['?b]])]
  191. nested-and?
  192. result
  193. (and (zero? level) (= 'and fe))
  194. (distinct (apply concat clauses))
  195. (and (zero? level) (= 'or fe))
  196. result
  197. :else
  198. [result]))))
  199. (and (= 'between fe)
  200. (= 3 (count e)))
  201. (let [start (->journal-day-int (nth e 1))
  202. end (->journal-day-int (nth e 2))
  203. [start end] (sort [start end])]
  204. [['?b :block/page '?p]
  205. ['?p :block/journal? true]
  206. ['?p :block/journal-day '?d]
  207. [(list '>= '?d start)]
  208. [(list '<= '?d end)]])
  209. ;; (between created_at -1d today)
  210. (and (= 'between fe)
  211. (= 4 (count e)))
  212. (let [k (-> (second e)
  213. (name)
  214. (string/lower-case)
  215. (string/replace "-" "_"))]
  216. (when (contains? #{"created_at" "last_modified_at"} k)
  217. (let [start (->timestamp (nth e 2))
  218. end (->timestamp (nth e 3))]
  219. (when (and start end)
  220. (let [[start end] (sort [start end])
  221. sym '?v]
  222. [['?b :block/properties '?prop]
  223. [(list 'get '?prop k) sym]
  224. [(list '>= sym start)]
  225. [(list '< sym end)]])))))
  226. (and (= 'property fe)
  227. (= 3 (count e)))
  228. (let [k (string/replace (name (nth e 1)) "_" "-")
  229. v (nth e 2)
  230. v (if-not (nil? v)
  231. (property/parse-property k v)
  232. v)
  233. v (if (coll? v) (first v) v)
  234. sym (if (= current-filter 'or)
  235. '?v
  236. (uniq-symbol counter "?v"))]
  237. [['?b :block/properties '?prop]
  238. [(list 'missing? '$ '?b :block/name)]
  239. [(list 'get '?prop (keyword k)) sym]
  240. (list
  241. 'or
  242. [(list '= sym v)]
  243. [(list 'contains? sym v)])])
  244. (and (= 'property fe)
  245. (= 2 (count e)))
  246. (let [k (string/replace (name (nth e 1)) "_" "-")]
  247. [['?b :block/properties '?prop]
  248. [(list 'missing? '$ '?b :block/name)]
  249. [(list 'get '?prop (keyword k)) '?prop-v]
  250. [true]])
  251. (or (= 'todo fe) (= 'task fe))
  252. (let [markers (if (coll? (first (rest e)))
  253. (first (rest e))
  254. (rest e))]
  255. (when (seq markers)
  256. (let [markers (set (map (comp string/upper-case name) markers))]
  257. [['?b :block/marker '?marker]
  258. [(list 'contains? markers '?marker)]])))
  259. (= 'priority fe)
  260. (let [priorities (if (coll? (first (rest e)))
  261. (first (rest e))
  262. (rest e))]
  263. (when (seq priorities)
  264. (let [priorities (set (map (comp string/upper-case name) priorities))]
  265. [['?b :block/priority '?priority]
  266. [(list 'contains? priorities '?priority)]])))
  267. (= 'sort-by fe)
  268. (let [[k order] (rest e)
  269. order (if (and order (contains? #{:asc :desc}
  270. (keyword (string/lower-case (name order)))))
  271. (keyword (string/lower-case (name order)))
  272. :desc)
  273. k (-> (string/lower-case (name k))
  274. (string/replace "_" "-"))
  275. get-value (cond
  276. (= k "created-at")
  277. :block/created-at
  278. (= k "updated-at")
  279. :block/updated-at
  280. :else
  281. #(get-in % [:block/properties k]))
  282. comp (if (= order :desc) >= <=)]
  283. (reset! sort-by
  284. (fn [result]
  285. (->> result
  286. flatten
  287. (clojure.core/sort-by get-value comp))))
  288. nil)
  289. (= 'page fe)
  290. (let [page-name (text/page-ref-un-brackets! (str (first (rest e))))
  291. page-name (util/page-name-sanity-lc page-name)]
  292. [['?b :block/page [:block/name page-name]]])
  293. (and (= 'namespace fe)
  294. (= 2 (count e)))
  295. (let [page-name (text/page-ref-un-brackets! (str (first (rest e))))
  296. page (util/page-name-sanity-lc page-name)]
  297. (when-not (string/blank? page)
  298. [['?p :block/namespace '?parent]
  299. ['?parent :block/name page]]))
  300. (= 'page-property fe)
  301. (let [[k v] (rest e)
  302. k (string/replace (name k) "_" "-")]
  303. (if-not (nil? v)
  304. (let [v (property/parse-property k v)
  305. v (if (coll? v) (first v) v)
  306. sym '?v]
  307. [['?p :block/name]
  308. ['?p :block/properties '?prop]
  309. [(list 'get '?prop (keyword k)) sym]
  310. (list
  311. 'or
  312. [(list '= sym v)]
  313. [(list 'contains? sym v)])])
  314. [['?p :block/name]
  315. ['?p :block/properties '?prop]
  316. [(list 'get '?prop (keyword k)) '?prop-v]
  317. [true]]))
  318. (= 'page-tags fe)
  319. (let [tags (if (coll? (first (rest e)))
  320. (first (rest e))
  321. (rest e))
  322. tags (map (comp string/lower-case name) tags)]
  323. (when (seq tags)
  324. (let [tags (set (map (comp text/page-ref-un-brackets! string/lower-case name) tags))
  325. sym-1 (uniq-symbol counter "?t")
  326. sym-2 (uniq-symbol counter "?tag")]
  327. [['?p :block/tags sym-1]
  328. [sym-1 :block/name sym-2]
  329. [(list 'contains? tags sym-2)]])))
  330. (= 'all-page-tags fe)
  331. [['?e :block/tags '?p]]
  332. (= 'sample fe)
  333. (when-let [num (second e)]
  334. (when (integer? num)
  335. (reset! sample num))
  336. nil)
  337. :else
  338. nil))))
  339. (defn- pre-transform
  340. [s]
  341. (some-> s
  342. (string/replace text/page-ref-re "\"[[$1]]\"")
  343. (string/replace text/between-re (fn [[_ x]]
  344. (->> (string/split x #" ")
  345. (remove string/blank?)
  346. (map (fn [x]
  347. (if (or (contains? #{"+" "-"} (first x))
  348. (and (util/safe-re-find #"\d" (first x))
  349. (some #(string/ends-with? x %) ["y" "m" "d" "h" "min"])))
  350. (keyword (name x))
  351. x)))
  352. (string/join " ")
  353. (util/format "(between %s)"))))))
  354. (defn- add-bindings!
  355. [form q]
  356. (let [forms (set (flatten q))
  357. syms ['?b '?p 'not]
  358. [b? p? not?] (-> (set/intersection (set syms) forms)
  359. (map syms))
  360. or? (contains? (set (flatten form)) 'or)]
  361. (cond
  362. not?
  363. (cond
  364. (and b? p?)
  365. (concat [['?b :block/uuid] ['?p :block/name] ['?b :block/page '?p]] q)
  366. b?
  367. (concat [['?b :block/uuid]] q)
  368. p?
  369. (concat [['?p :block/name]] q)
  370. :else
  371. q)
  372. or?
  373. (cond
  374. (->> (flatten form)
  375. (remove text/page-ref?)
  376. (some string?)) ; block full-text search
  377. (concat [['?b :block/content '?content]] [q])
  378. :else
  379. q)
  380. (and b? p?)
  381. (concat [['?b :block/page '?p]] q)
  382. :else
  383. q)))
  384. (defn parse
  385. [repo s]
  386. (when (and (string? s)
  387. (not (string/blank? s)))
  388. (let [counter (atom 0)]
  389. (try
  390. (let [s (if (= \# (first s)) (util/format "[[%s]]" (subs s 1)) s)
  391. form (some-> s
  392. (pre-transform)
  393. (reader/read-string))]
  394. (if (symbol? form)
  395. (str form)
  396. (let [sort-by (atom nil)
  397. blocks? (atom nil)
  398. sample (atom nil)
  399. result (when form (build-query repo form {:sort-by sort-by
  400. :blocks? blocks?
  401. :counter counter
  402. :sample sample}))]
  403. (cond
  404. (and (nil? result) (string? form))
  405. form
  406. (string? result)
  407. result
  408. :else
  409. (let [result (when (seq result)
  410. (let [key (if (coll? (first result))
  411. (keyword (ffirst result))
  412. (keyword (first result)))
  413. result (case key
  414. :and
  415. (rest result)
  416. result)]
  417. (add-bindings! form result)))]
  418. {:query result
  419. :sort-by @sort-by
  420. :blocks? (boolean @blocks?)
  421. :sample sample})))))
  422. (catch js/Error e
  423. (log/error :query-dsl/parse-error e))))))
  424. (defn query
  425. ([query-string]
  426. (query (state/get-current-repo) query-string))
  427. ([repo query-string]
  428. (when (string? query-string)
  429. (let [query-string (template/resolve-dynamic-template! query-string)]
  430. (when-not (string/blank? query-string)
  431. (let [{:keys [query sort-by blocks? sample] :as result} (parse repo query-string)
  432. query (if (string? query) (string/trim query) query)
  433. full-text-query? (and (string? result)
  434. (not (string/includes? result " ")))]
  435. (if full-text-query?
  436. (if (= "\"" (first result) (last result))
  437. (subs result 1 (dec (count result)))
  438. result)
  439. (when-let [query (query-wrapper query blocks?)]
  440. (let [sort-by (or sort-by identity)
  441. random-samples (if @sample
  442. (fn [col]
  443. (take @sample (shuffle col)))
  444. identity)
  445. transform-fn (comp sort-by random-samples)]
  446. (react/react-query repo
  447. {:query query
  448. :query-string query-string}
  449. {:use-cache? false
  450. :transform-fn transform-fn}))))))))))
  451. (defn custom-query
  452. [repo query-m query-opts]
  453. (when (seq (:query query-m))
  454. (let [query-string (pr-str (:query query-m))
  455. query-string (template/resolve-dynamic-template! query-string)
  456. {:keys [query sort-by blocks?]} (parse repo query-string)
  457. query (if (string? query) (string/trim query) query)]
  458. (when query
  459. (when-let [query (query-wrapper query blocks?)]
  460. (react/react-query repo
  461. (merge
  462. query-m
  463. {:query query})
  464. (merge
  465. query-opts
  466. (when sort-by
  467. {:transform-fn sort-by}))))))))
  468. (comment
  469. ;; {{query (and (page-property foo bar) [[hello]])}}
  470. (query "(and [[foo]] [[bar]])")
  471. (query "(or [[foo]] [[bar]])")
  472. (query "(not (or [[foo]] [[bar]]))")
  473. (query "(between -7d +7d)")
  474. (query "(between -7d today)")
  475. (query "(between created_at yesterday today)")
  476. (query "(and [[some page]] (property foo bar))")
  477. (query "(and [[some page]] (task now later))")
  478. (query "(and [[some page]] (priority A))")
  479. ;; nested query
  480. (query "(and [[baz]] (or [[foo]] [[bar]]))")
  481. (query "(and [[some page]] (sort-by created-at))")
  482. (query "(and (page-property foo bar) [[hello]])"))