query_dsl.cljs 15 KB

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