query_dsl_test.cljs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. (ns frontend.db.query-dsl-test
  2. (:require [frontend.db.query-dsl :as dsl]
  3. [frontend.db :as db]
  4. [frontend.db.conn :as conn]
  5. [frontend.db.config :refer [test-db] :as config]
  6. [datascript.core :as d]
  7. [frontend.db-schema :as schema]
  8. [frontend.handler.repo :as repo-handler]
  9. [promesa.core :as p]
  10. [cljs.test :refer [deftest is are testing use-fixtures run-tests async]]))
  11. ;; TODO: quickcheck
  12. ;; 1. generate query filters
  13. ;; 2. find illegal queries which can't be executed by datascript
  14. ;; 3. find filters combinations which might break the current query implementation
  15. (defn import-test-data!
  16. []
  17. (let [files [{:file/path "journals/2020_12_26.md"
  18. :file/content "---
  19. title: Dec 26th, 2020
  20. tags: [[page-tag-1]], page-tag-2
  21. parent: [[child page 1]]
  22. ---
  23. - DONE 26-b1 [[page 1]]
  24. created-at:: 1608968448113
  25. last-modified-at:: 1608968448113
  26. prop-a:: val-a
  27. prop-c:: [[page a]], [[page b]], [[page c]]
  28. - LATER 26-b2-modified-later [[page 2]] #tag1
  29. created-at:: 1608968448114
  30. last-modified-at:: 1608968448120
  31. prop-b:: val-b
  32. - DONE [#A] 26-b3 [[page 1]]
  33. created-at:: 1608968448115
  34. last-modified-at:: 1608968448115
  35. "}
  36. {:file/path "journals/2020_12_27.md"
  37. :file/content "---
  38. title: Dec 27th, 2020
  39. tags: page-tag-2, [[page-tag-3]]
  40. parent: [[child page 1]], child page 2
  41. ---
  42. - NOW [#A] b1 [[page 1]]
  43. created-at:: 1609052958714
  44. last-modified-at:: 1609052958714
  45. - LATER [#B] b2-modified-later [[page 2]]
  46. created-at:: 1609052959376
  47. last-modified-at:: 1609052974285
  48. - b3 [[page 1]]
  49. created-at:: 1609052959954
  50. last-modified-at:: 1609052959954
  51. prop-a:: val-a
  52. - b4 [[page 2]]
  53. created-at:: 1609052961569
  54. last-modified-at:: 1609052961569
  55. - b5
  56. created-at:: 1609052963089
  57. last-modified-at:: 1609052963089"}
  58. {:file/path "journals/2020_12_28.md"
  59. :file/content "---
  60. title: Dec 28th, 2020
  61. parent: child page 2
  62. ---
  63. - 28-b1 [[page 1]]
  64. created-at:: 1609084800000
  65. last-modified-at:: 1609084800000
  66. - 28-b2-modified-later [[page 2]]
  67. created-at:: 1609084800001
  68. last-modified-at:: 1609084800020
  69. - 28-b3 [[page 1]]
  70. created-at:: 1609084800002
  71. last-modified-at:: 1609084800002"}]]
  72. (repo-handler/parse-files-and-load-to-db! test-db files {:re-render? false})))
  73. (def parse (partial dsl/parse test-db))
  74. (defn- q
  75. [s]
  76. (db/clear-query-state!)
  77. (let [parse-result (parse s)
  78. query (:query parse-result)]
  79. {:query (if (seq query) (vec query) query)
  80. :result (dsl/query test-db s)}))
  81. (defn q-count
  82. [s]
  83. (let [{:keys [query result]} (q s)]
  84. {:query query
  85. :count (if result
  86. (count @result)
  87. 0)}))
  88. (defn count-only
  89. [s]
  90. (:count (q-count s)))
  91. (defonce empty-result {:query nil :result nil})
  92. (deftest test-parse
  93. []
  94. (testing "nil or blank strings should be ignored"
  95. (are [x y] (= (q x) y)
  96. nil empty-result
  97. "" empty-result
  98. " " empty-result))
  99. (testing "Non exists page should be ignored"
  100. (are [x y] (nil? (:result (q x)))
  101. "[[page-not-exist]]" empty-result
  102. "[[another-page-not-exist]]" empty-result))
  103. (testing "Single page query"
  104. (are [x y] (= (q-count x) y)
  105. "[[page 1]]"
  106. {:query '[[?b :block/path-refs [:block/name "page 1"]]]
  107. :count 6}
  108. "[[page 2]]"
  109. {:query '[[?b :block/path-refs [:block/name "page 2"]]]
  110. :count 4}))
  111. (testing "Block properties query"
  112. (are [x y] (= (q-count x) y)
  113. "(property prop-a val-a)"
  114. {:query '[[?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-a) ?v] (or [(= ?v "val-a")] [(contains? ?v "val-a")])]
  115. :count 2}
  116. "(property prop-b val-b)"
  117. {:query '[[?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-b) ?v] (or [(= ?v "val-b")] [(contains? ?v "val-b")])]
  118. :count 1}
  119. "(and (property prop-b val-b))"
  120. {:query '([?b :block/properties ?prop]
  121. [(missing? $ ?b :block/name)]
  122. [(get ?prop :prop-b) ?v]
  123. (or [(= ?v "val-b")] [(contains? ?v "val-b")]))
  124. :count 1}
  125. "(and (property prop-c \"page c\"))"
  126. {:query '[[?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-c) ?v] (or [(= ?v "page c")] [(contains? ?v "page c")])]
  127. :count 1}
  128. ;; TODO: optimize
  129. "(and (property prop-c \"page c\") (property prop-c \"page b\"))"
  130. {:query '[[?b :block/properties ?prop]
  131. [(missing? $ ?b :block/name)]
  132. [(get ?prop :prop-c) ?v]
  133. (or [(= ?v "page c")] [(contains? ?v "page c")])
  134. [(get ?prop :prop-c) ?v1]
  135. (or [(= ?v1 "page b")] [(contains? ?v1 "page b")])]
  136. :count 1}
  137. "(or (property prop-c \"page c\") (property prop-b val-b))"
  138. {:query '[or
  139. (and [?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-c) ?v] (or [(= ?v "page c")] [(contains? ?v "page c")]))
  140. (and [?b :block/properties ?prop] [(missing? $ ?b :block/name)] [(get ?prop :prop-b) ?v] (or [(= ?v "val-b")] [(contains? ?v "val-b")]))]
  141. :count 2}))
  142. (testing "task queries"
  143. (are [x y] (= (q-count x) y)
  144. "(task now)"
  145. {:query '[[?b :block/marker ?marker]
  146. [(contains? #{"NOW"} ?marker)]]
  147. :count 1}
  148. "(task NOW)"
  149. {:query '[[?b :block/marker ?marker]
  150. [(contains? #{"NOW"} ?marker)]]
  151. :count 1}
  152. "(task later)"
  153. {:query '[[?b :block/marker ?marker]
  154. [(contains? #{"LATER"} ?marker)]]
  155. :count 2}
  156. "(task now later)"
  157. {:query '[[?b :block/marker ?marker]
  158. [(contains? #{"NOW" "LATER"} ?marker)]]
  159. :count 3}
  160. "(task [now later])"
  161. {:query '[[?b :block/marker ?marker]
  162. [(contains? #{"NOW" "LATER"} ?marker)]]
  163. :count 3}))
  164. (testing "Priority queries"
  165. (are [x y] (= (q-count x) y)
  166. "(priority A)"
  167. {:query '[[?b :block/priority ?priority]
  168. [(contains? #{"A"} ?priority)]]
  169. :count 2}
  170. "(priority a)"
  171. {:query '[[?b :block/priority ?priority]
  172. [(contains? #{"A"} ?priority)]]
  173. :count 2}
  174. "(priority a b)"
  175. {:query '[[?b :block/priority ?priority]
  176. [(contains? #{"A" "B"} ?priority)]]
  177. :count 3}
  178. "(priority [a b])"
  179. {:query '[[?b :block/priority ?priority]
  180. [(contains? #{"A" "B"} ?priority)]]
  181. :count 3}
  182. "(priority a b c)"
  183. {:query '[[?b :block/priority ?priority]
  184. [(contains? #{"A" "B" "C"} ?priority)]]
  185. :count 3}))
  186. (testing "all-page-tags queries"
  187. (are [x y] (= (q-count x) y)
  188. "(all-page-tags)"
  189. {:query '[[?e :block/tags ?p]]
  190. :count 3}))
  191. (testing "page-tags queries"
  192. (are [x y] (= (q-count x) y)
  193. "(page-tags [[page-tag-1]])"
  194. {:query '[[?p :block/tags ?t]
  195. [?t :block/name ?tag1]
  196. [(contains? #{"page-tag-1"} ?tag1)]]
  197. :count 1}
  198. "(page-tags page-tag-2)"
  199. {:query '[[?p :block/tags ?t]
  200. [?t :block/name ?tag1]
  201. [(contains? #{"page-tag-2"} ?tag1)]]
  202. :count 2}
  203. "(page-tags page-tag-1 page-tag-2)"
  204. {:query '[[?p :block/tags ?t]
  205. [?t :block/name ?tag1]
  206. [(contains? #{"page-tag-1" "page-tag-2"} ?tag1)]]
  207. :count 2}
  208. "(page-tags page-TAG-1 page-tag-2)"
  209. {:query '[[?p :block/tags ?t]
  210. [?t :block/name ?tag1]
  211. [(contains? #{"page-tag-1" "page-tag-2"} ?tag1)]]
  212. :count 2}
  213. "(page-tags [page-tag-1 page-tag-2])"
  214. {:query '[[?p :block/tags ?t]
  215. [?t :block/name ?tag1]
  216. [(contains? #{"page-tag-1" "page-tag-2"} ?tag1)]]
  217. :count 2}))
  218. (testing "page-property queries"
  219. (are [x y] (= (q-count x) y)
  220. "(page-property parent)"
  221. {:query '[[?p :block/name]
  222. [?p :block/properties ?prop]
  223. [(get ?prop :parent) ?prop-v]
  224. [true]], :count 3}
  225. "(page-property parent [[child page 1]])"
  226. {:query '[[?p :block/name]
  227. [?p :block/properties ?prop]
  228. [(get ?prop :parent) ?v]
  229. (or [(= ?v "child page 1")] [(contains? ?v "child page 1")])]
  230. :count 2}
  231. "(page-property parent \"child page 1\")"
  232. {:query '[[?p :block/name]
  233. [?p :block/properties ?prop]
  234. [(get ?prop :parent) ?v]
  235. (or
  236. [(= ?v "child page 1")]
  237. [(contains? ?v "child page 1")])]
  238. :count 2}
  239. "(and (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
  240. {:query '([?p :block/name]
  241. [?p :block/properties ?prop]
  242. [(get ?prop :parent) ?v]
  243. (or [(= ?v "child page 1")] [(contains? ?v "child page 1")])
  244. (or [(= ?v "child page 2")] [(contains? ?v "child page 2")]))
  245. :count 1}
  246. "(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
  247. {:query '(or (and
  248. [?p :block/name]
  249. [?p :block/properties ?prop]
  250. [(get ?prop :parent) ?v]
  251. (or [(= ?v "child page 1")] [(contains? ?v "child page 1")]))
  252. (and
  253. [?p :block/name]
  254. [?p :block/properties ?prop]
  255. [(get ?prop :parent) ?v]
  256. (or [(= ?v "child page 2")] [(contains? ?v "child page 2")])))
  257. :count 3}))
  258. ;; boolean queries
  259. (testing "AND queries"
  260. (are [x y] (= (q-count x) y)
  261. "(and [[tag1]] [[page 2]])"
  262. {:query '([?b :block/path-refs [:block/name "tag1"]]
  263. [?b :block/path-refs [:block/name "page 2"]])
  264. :count 1})
  265. (are [x y] (= (q-count x) y)
  266. "(and [[tag1]] [[page 2]])"
  267. {:query '([?b :block/path-refs [:block/name "tag1"]]
  268. [?b :block/path-refs [:block/name "page 2"]])
  269. :count 1}))
  270. (testing "OR queries"
  271. (are [x y] (= (q-count x) y)
  272. "(or [[tag1]] [[page 2]])"
  273. {:query '(or
  274. (and [?b :block/path-refs [:block/name "tag1"]])
  275. (and [?b :block/path-refs [:block/name "page 2"]]))
  276. :count 4}))
  277. (testing "NOT queries"
  278. (are [x y] (= (q-count x) y)
  279. "(not [[page 1]])"
  280. {:query '([?b :block/uuid]
  281. (not [?b :block/path-refs [:block/name "page 1"]]))
  282. :count 33}))
  283. (testing "Between query"
  284. (are [x y] (= (count-only x) y)
  285. "(and (task now later done) (between [[Dec 26th, 2020]] tomorrow))"
  286. 5
  287. ;; between with journal pages
  288. "(and (task now later done) (between [[Dec 27th, 2020]] [[Dec 28th, 2020]]))"
  289. 2
  290. ;; ;; between with created-at
  291. ;; "(and (task now later done) (between created-at [[Dec 26th, 2020]] tomorrow))"
  292. ;; 5
  293. ;; ;; between with last-modified-at
  294. ;; "(and (task now later done) (between last-modified-at [[Dec 26th, 2020]] tomorrow))"
  295. ;; 5
  296. ))
  297. (testing "Nested boolean queries"
  298. (are [x y] (= (q-count x) y)
  299. "(and (todo done) (not [[page 1]]))"
  300. {:query '([?b :block/uuid]
  301. [?b :block/marker ?marker]
  302. [(contains? #{"DONE"} ?marker)]
  303. (not [?b :block/path-refs [:block/name "page 1"]]))
  304. :count 0})
  305. (are [x y] (= (q-count x) y)
  306. "(and (todo now later) (or [[page 1]] [[page 2]]))"
  307. {:query '([?b :block/marker ?marker]
  308. [(contains? #{"NOW" "LATER"} ?marker)]
  309. (or (and [?b :block/path-refs [:block/name "page 1"]])
  310. (and [?b :block/path-refs [:block/name "page 2"]])
  311. [?b]))
  312. :count 3})
  313. (are [x y] (= (q-count x) y)
  314. "(not (and (todo now later) (or [[page 1]] [[page 2]])))"
  315. {:query '([?b :block/uuid]
  316. (not
  317. [?b :block/marker ?marker]
  318. [(contains? #{"NOW" "LATER"} ?marker)]
  319. (or
  320. (and [?b :block/path-refs [:block/name "page 1"]])
  321. (and [?b :block/path-refs [:block/name "page 2"]])
  322. [?b])))
  323. :count 36})
  324. ;; FIXME: not working
  325. ;; (are [x y] (= (q-count x) y)
  326. ;; "(or (priority a) (not (priority a)))"
  327. ;; {:query '[(or-join [?b]
  328. ;; (and
  329. ;; [?b :block/priority ?priority]
  330. ;; [(contains? #{"A"} ?priority)])
  331. ;; (not-join [?b]
  332. ;; [?b :block/priority ?priority]
  333. ;; [(contains? #{"A"} ?priority)]))]
  334. ;; :count 5})
  335. (are [x y] (= (q-count x) y)
  336. "(and (todo now later done) (or [[page 1]] (not [[page 1]])))"
  337. {:query '([?b :block/uuid]
  338. [?b :block/marker ?marker]
  339. [(contains? #{"NOW" "LATER" "DONE"} ?marker)]
  340. (or
  341. (and [?b :block/path-refs [:block/name "page 1"]])
  342. (and (not [?b :block/path-refs [:block/name "page 1"]]))
  343. [?b]))
  344. :count 5}))
  345. ;; (testing "sort-by (created-at defaults to desc)"
  346. ;; (db/clear-query-state!)
  347. ;; (let [result (->> (q "(and (task now later done)
  348. ;; (sort-by created-at))")
  349. ;; :result
  350. ;; deref
  351. ;; (map #(get-in % [:block/properties "created-at"])))]
  352. ;; (is (= result
  353. ;; '(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
  354. ;; (testing "sort-by (created-at desc)"
  355. ;; (db/clear-query-state!)
  356. ;; (let [result (->> (q "(and (todo now later done)
  357. ;; (sort-by created-at desc))")
  358. ;; :result
  359. ;; deref
  360. ;; (map #(get-in % [:block/properties "created-at"])))]
  361. ;; (is (= result
  362. ;; '(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
  363. ;; (testing "sort-by (created-at asc)"
  364. ;; (db/clear-query-state!)
  365. ;; (let [result (->> (q "(and (todo now later done)
  366. ;; (sort-by created-at asc))")
  367. ;; :result
  368. ;; deref
  369. ;; (map #(get-in % [:block/properties "created-at"])))]
  370. ;; (is (= result
  371. ;; '(1608968448113 1608968448114 1608968448115 1609052958714 1609052959376)))))
  372. ;; (testing "sort-by (last-modified-at defaults to desc)"
  373. ;; (db/clear-query-state!)
  374. ;; (let [result (->> (q "(and (todo now later done)
  375. ;; (sort-by last-modified-at))")
  376. ;; :result
  377. ;; deref
  378. ;; (map #(get-in % [:block/properties "last-modified-at"])))]
  379. ;; (is (= result
  380. ;; '(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
  381. ;; (testing "sort-by (last-modified-at desc)"
  382. ;; (db/clear-query-state!)
  383. ;; (let [result (->> (q "(and (todo now later done)
  384. ;; (sort-by last-modified-at desc))")
  385. ;; :result
  386. ;; deref
  387. ;; (map #(get-in % [:block/properties "last-modified-at"])))]
  388. ;; (is (= result
  389. ;; '(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
  390. ;; (testing "sort-by (last-modified-at desc)"
  391. ;; (db/clear-query-state!)
  392. ;; (let [result (->> (q "(and (todo now later done)
  393. ;; (sort-by last-modified-at asc))")
  394. ;; :result
  395. ;; deref
  396. ;; (map #(get-in % [:block/properties "last-modified-at"])))]
  397. ;; (is (= result
  398. ;; '(1608968448113 1608968448115 1608968448120 1609052958714 1609052974285)))))
  399. )
  400. (use-fixtures :once
  401. {:before (fn []
  402. (async done
  403. (config/start-test-db!)
  404. (p/let [_ (import-test-data!)]
  405. (done))))
  406. :after config/destroy-test-db!})
  407. #_(run-tests)
  408. (comment
  409. (require '[clojure.pprint :as pprint])
  410. (config/start-test-db!)
  411. (import-test-data!)
  412. (dsl/query test-db "(all-page-tags)")
  413. ;; (or (priority a) (not (priority a)))
  414. ;; FIXME: Error: Insufficient bindings: #{?priority} not bound in [(contains? #{"A"} ?priority)]
  415. (pprint/pprint
  416. (d/q
  417. '[:find (pull ?b [*])
  418. :where
  419. [?b :block/uuid]
  420. (or (and [?b :block/priority ?priority] [(contains? #{"A"} ?priority)])
  421. (not [?b :block/priority #{"A"}]
  422. [(contains? #{"A"} ?priority)]))]
  423. (frontend.db/get-conn test-db))))