query_dsl_test.cljs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  1. (ns frontend.db.query-dsl-test
  2. (:require [cljs.test :refer [are deftest testing use-fixtures is]]
  3. [clojure.string :as string]
  4. [frontend.db :as db]
  5. [frontend.db.query-dsl :as query-dsl]
  6. [frontend.db.react :as react]
  7. [frontend.test.helper :as test-helper :include-macros true :refer [load-test-files load-test-files-for-db-graph]]
  8. [frontend.util :as util]
  9. [logseq.common.util.page-ref :as page-ref]))
  10. ;; TODO: quickcheck
  11. ;; 1. generate query filters
  12. ;; 2. find illegal queries which can't be executed by datascript
  13. ;; 3. find filters combinations which might break the current query implementation
  14. (use-fixtures :each {:before test-helper/start-test-db!
  15. :after test-helper/destroy-test-db!})
  16. ;; Test helpers
  17. ;; ============
  18. (def db-block-attrs
  19. ;; '*' needed as we need to pull user properties and don't know their names in advance
  20. '[*
  21. {:block/page [:db/id :block/name :block/title :block/journal-day]}
  22. {:block/_parent ...}])
  23. (def dsl-query*
  24. "Overrides dsl-query/query with ENV variables. When $EXAMPLE is set, prints query
  25. result of build query. This is useful for documenting examples and debugging.
  26. When $DB_QUERY_TYPE is set, runs query tests against other versions of simple query e.g.
  27. more basic property rules"
  28. (cond
  29. (some? js/process.env.EXAMPLE)
  30. (fn dsl-query-star [& args]
  31. (let [old-build-query query-dsl/build-query]
  32. (with-redefs [query-dsl/build-query
  33. (fn [& args']
  34. (let [res (apply old-build-query args')]
  35. (println "EXAMPLE:" (pr-str (:query res)))
  36. res))]
  37. (apply query-dsl/query args))))
  38. (some? js/process.env.DB_QUERY_TYPE)
  39. (fn dsl-query-star [& args]
  40. (let [old-build-property @#'query-dsl/build-property]
  41. (with-redefs [query-dsl/build-property
  42. (fn [& args']
  43. (let [m (apply old-build-property args')
  44. m' (cond
  45. (= (:rules m) [:simple-query-property])
  46. {:rules [:property]
  47. :query (apply list 'property (rest (:query m)))}
  48. (= (:rules m) [:has-simple-query-property])
  49. {:rules [:has-property]
  50. :query (apply list 'has-property (rest (:query m)))}
  51. :else
  52. m)]
  53. m'))
  54. query-dsl/db-block-attrs db-block-attrs]
  55. (apply query-dsl/query args))))
  56. :else
  57. (fn dsl-query-star [& args]
  58. (with-redefs [query-dsl/db-block-attrs db-block-attrs]
  59. (apply query-dsl/query args)))))
  60. (defn- ->smart-query
  61. "Updates to file version if js/process.env.DB_GRAPH is not set"
  62. [query]
  63. (if js/process.env.DB_GRAPH
  64. (some-> query
  65. (string/replace "(page-tags" "(tags"))
  66. query))
  67. (defn- dsl-query
  68. [s]
  69. (react/clear-query-state!)
  70. (when-let [result (dsl-query* test-helper/test-db (->smart-query s))]
  71. (map first (deref result))))
  72. (defn- custom-query
  73. [query]
  74. (react/clear-query-state!)
  75. (when-let [result (with-redefs [query-dsl/db-block-attrs db-block-attrs]
  76. (query-dsl/custom-query test-helper/test-db query {}))]
  77. (map first (deref result))))
  78. ;; Tests
  79. ;; =====
  80. (deftest pre-transform-test
  81. (testing "page references should be quoted and tags should be handled"
  82. (are [x y] (= (query-dsl/pre-transform x) y)
  83. "#foo"
  84. "#tag foo"
  85. "(and #foo)"
  86. "(and #tag foo)"
  87. "[[test #foo]]"
  88. "\"[[test #foo]]\""
  89. "(and [[test #foo]] (or #foo))"
  90. "(and \"[[test #foo]]\" (or #tag foo))"
  91. "\"for #clojure\""
  92. "\"for #clojure\""
  93. "(and \"for #clojure\")"
  94. "(and \"for #clojure\")"
  95. "(and \"for #clojure\" #foo)"
  96. "(and \"for #clojure\" #tag foo)")))
  97. (defn- testable-content
  98. "Only test :block/title up to page-ref as page-ref content varies between db and file graphs"
  99. [{:block/keys [title]}]
  100. (some->> title
  101. (re-find #"[^\[]+")
  102. string/trim))
  103. (defn- block-property-queries-test
  104. []
  105. (load-test-files [{:file/path "journals/2022_02_28.md"
  106. :file/content "a:: b
  107. - b1
  108. prop-a:: val-a
  109. prop-num:: 2000
  110. - b2
  111. prop-a:: val-a
  112. prop-b:: val-b
  113. - b3
  114. prop-d:: [[no-space-link]]
  115. prop-c:: [[page a]], [[page b]], [[page c]]
  116. prop-linked-num:: [[3000]]
  117. - b4
  118. prop-d:: [[nada]]"}])
  119. (testing "Blocks have given property value"
  120. (is (= #{"b1" "b2"}
  121. (set (map (comp first string/split-lines :block/title)
  122. (dsl-query "(property prop-a val-a)")))))
  123. (is (= ["b2"]
  124. (map (comp first string/split-lines :block/title)
  125. (dsl-query "(property prop-b val-b)")))))
  126. (is (= ["b2"]
  127. (map (comp first string/split-lines :block/title)
  128. (dsl-query "(and (property prop-b val-b))")))
  129. "Blocks have property value with empty AND")
  130. (is (= ["b3"]
  131. (map (comp first string/split-lines :block/title)
  132. (dsl-query "(and (property prop-c \"page c\"))")))
  133. "Blocks have property value from a set of values")
  134. (is (= ["b3"]
  135. (map (comp first string/split-lines :block/title)
  136. (dsl-query "(and (property prop-c \"page c\") (property prop-c \"page b\"))")))
  137. "Blocks have ANDed property values")
  138. (is (= #{"b2" "b3"}
  139. (set
  140. (map (comp first string/split-lines :block/title)
  141. (dsl-query "(or (property prop-c \"page c\") (property prop-b val-b))"))))
  142. "Blocks have ORed property values")
  143. (is (= ["b1"]
  144. (map (comp first string/split-lines :block/title)
  145. (dsl-query "(property prop-num 2000)")))
  146. "Blocks have integer property value")
  147. (is (= ["b3"]
  148. (map (comp first string/split-lines :block/title)
  149. (dsl-query "(property prop-linked-num 3000)")))
  150. "Blocks have property with integer page value")
  151. (is (= ["b3"]
  152. (map (comp first string/split-lines :block/title)
  153. (dsl-query "(property prop-d no-space-link)")))
  154. "Blocks have property value with no space")
  155. (is (= #{"b3" "b4"}
  156. (set (map (comp first string/split-lines :block/title)
  157. (dsl-query "(property prop-d)"))))
  158. "Blocks that have a property"))
  159. (deftest block-property-queries
  160. (testing "block property tests with default config"
  161. (test-helper/with-config {}
  162. (block-property-queries-test))))
  163. (when js/process.env.DB_GRAPH
  164. (deftest db-only-block-property-queries
  165. (load-test-files-for-db-graph
  166. {:properties
  167. {:zzz {:logseq.property/type :default
  168. :block/title "zzz name!"}}
  169. :pages-and-blocks
  170. [{:page {:block/title "page1"}
  171. :blocks [{:block/title "b1"
  172. :build/properties {:Foo "bar"}}
  173. {:block/title "b2"
  174. :build/properties {:foo "bar"}}
  175. {:block/title "b3"
  176. :build/properties {:zzz "bar"}}]}]})
  177. (is (= ["b1"]
  178. (map :block/title (dsl-query "(property Foo)")))
  179. "filter is case sensitive")
  180. (is (= ["b2"]
  181. (map :block/title (dsl-query "(property :user.property/foo)")))
  182. "filter can handle qualified keyword properties")
  183. (is (= ["b3"]
  184. (map :block/title (dsl-query "(property \"zzz name!\")")))
  185. "filter can handle property name")))
  186. (when (and js/process.env.DB_GRAPH (not js/process.env.DB_QUERY_TYPE))
  187. (deftest property-default-type-default-value-queries
  188. (load-test-files-for-db-graph
  189. {:properties
  190. {:default {:logseq.property/type :default
  191. :build/properties
  192. {:logseq.property/default-value "foo"}
  193. :build/properties-ref-types {:entity :number}}}
  194. :classes {:Class1 {:build/class-properties [:default]}}
  195. :pages-and-blocks
  196. [{:page {:block/title "page1"}
  197. :blocks [{:block/title "b1"
  198. :build/properties {:default "foo"}}
  199. {:block/title "b2"
  200. :build/properties {:default "bar"}}
  201. {:block/title "b3"
  202. :build/tags [:Class1]}]}]})
  203. (is (= (set ["b3" "b2" "b1"])
  204. (set (map :block/title (dsl-query "(property :user.property/default)"))))
  205. "Blocks with any :default property or tagged with a tag that has that default-value property")
  206. (is (= ["b1" "b3"]
  207. (map :block/title (dsl-query "(property :user.property/default \"foo\")")))
  208. "Blocks with :default property value or tagged with a tag that has that default-value property value")
  209. (is (= ["b2"]
  210. (map :block/title (dsl-query "(property :user.property/default \"bar\")")))
  211. "Blocks with :default property value and not tagged with a tag that has that default-value property value"))
  212. (deftest property-checkbox-type-default-value-queries
  213. (load-test-files-for-db-graph
  214. {:properties
  215. {:checkbox {:logseq.property/type :checkbox
  216. :build/properties
  217. {:logseq.property/scalar-default-value true}}}
  218. :classes {:Class1 {:build/class-properties [:checkbox]}}
  219. :pages-and-blocks
  220. [{:page {:block/title "page1"}
  221. :blocks [{:block/title "b1"
  222. :build/properties {:checkbox true}}
  223. {:block/title "b2"
  224. :build/properties {:checkbox false}}
  225. {:block/title "b3"
  226. :build/tags [:Class1]}]}]})
  227. (is (= (set ["b3" "b2" "b1"])
  228. (set (map :block/title (dsl-query "(property :user.property/checkbox)"))))
  229. "Blocks with any :checkbox property or tagged with a tag that has that default-value property")
  230. (is (= ["b1" "b3"]
  231. (map :block/title (dsl-query "(property :user.property/checkbox true)")))
  232. "Blocks with :checkbox property value or tagged with a tag that has that default-value property value")
  233. (is (= ["b2"]
  234. (map :block/title (dsl-query "(property :user.property/checkbox false)")))
  235. "Blocks with :checkbox property value and not tagged with a tag that has that default-value property value"))
  236. (deftest closed-property-default-value-queries
  237. (load-test-files-for-db-graph
  238. {:properties
  239. {:status {:logseq.property/type :default
  240. :build/closed-values
  241. [{:value "Todo" :uuid (random-uuid)}
  242. {:value "Doing" :uuid (random-uuid)}]
  243. :build/properties
  244. {:logseq.property/default-value "Todo"}
  245. :build/properties-ref-types {:entity :number}}}
  246. :classes {:Mytask {:build/class-properties [:status]}
  247. :Bug {:build/class-extends [:Mytask]}}
  248. :pages-and-blocks
  249. [{:page {:block/title "page1"}
  250. :blocks [{:block/title "task1"
  251. :build/properties {:status "Doing"}
  252. :build/tags [:Mytask]}
  253. {:block/title "task2"
  254. :build/tags [:Mytask]}
  255. {:block/title "bug1"
  256. :build/properties {:status "Doing"}
  257. :build/tags [:Bug]}
  258. {:block/title "bug2"
  259. :build/tags [:Bug]}]}]})
  260. (is (= ["task2" "bug2"]
  261. (map :block/title (dsl-query "(property status \"Todo\")")))
  262. "Blocks or tagged with or descended from a tag that has closed default-value property")
  263. (is (= ["task1" "bug1"]
  264. (map :block/title (dsl-query "(property status \"Doing\")")))
  265. "Blocks or tagged with or descended from a tag that don't have closed default-value property value")))
  266. (deftest block-property-query-performance
  267. (let [pages (->> (repeat 10 {:tags ["tag1" "tag2"]})
  268. (map-indexed (fn [idx {:keys [tags]}]
  269. {:file/path (str "pages/page" idx ".md")
  270. :file/content (if (seq tags)
  271. (str "page-prop:: b\n- block for page" idx
  272. "\ntagz:: " (string/join ", " (map page-ref/->page-ref tags)))
  273. "")})))
  274. _ (load-test-files pages)
  275. {:keys [result time]}
  276. (util/with-time (dsl-query "(and (property tagz tag1) (property tagz tag2))"))]
  277. ;; Specific number isn't as important as ensuring query doesn't take orders
  278. ;; of magnitude longer
  279. (is (> 40.0 time) "multi property query perf is reasonable")
  280. (is (= 10 (count result)))))
  281. (defn- page-property-queries-test
  282. []
  283. (load-test-files [{:file/path "pages/page1.md"
  284. :file/content "parent:: [[child page 1]], [[child-no-space]]\ninteresting:: true\nfoo:: baz"}
  285. {:file/path "pages/page2.md"
  286. :file/content "foo:: bar\ninteresting:: false"}
  287. {:file/path "pages/page3.md"
  288. :file/content "parent:: [[child page 1]], [[child page 2]]\nfoo:: bar\ninteresting:: false"}
  289. {:file/path "pages/page4.md"
  290. :file/content "parent:: [[child page 2]]\nfoo:: baz"}])
  291. (is (= ["page1" "page3" "page4"]
  292. (map :block/name (dsl-query "(page-property parent)")))
  293. "Pages have given property")
  294. (is (= #{"page1" "page3"}
  295. (set (map :block/name (dsl-query "(page-property parent [[child page 1]])"))))
  296. "Pages have property value that is a page and query is a page")
  297. (is (= #{"page1" "page3"}
  298. (set (map :block/name (dsl-query "(page-property parent \"child page 1\")"))))
  299. "Pages have property value that is a page and query is a string")
  300. (is (= ["page1"]
  301. (map :block/name (dsl-query "(page-property parent [[child-no-space]])")))
  302. "Pages have property value that is a page with no spaces")
  303. (is (= ["page3"]
  304. (map
  305. :block/name
  306. (dsl-query "(and (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))")))
  307. "Page property queries ANDed")
  308. (is (= #{"page1" "page3" "page4"}
  309. (set
  310. (map
  311. :block/name
  312. (dsl-query "(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"))))
  313. "Page property queries ORed")
  314. (is (= ["page1" "page3"]
  315. (map :block/name
  316. (dsl-query "(and (page-property parent [[child page 1]]) (or (page-property foo baz) (page-property parent [[child page 2]])))"))))
  317. (is (= ["page4"]
  318. (map
  319. :block/name
  320. (dsl-query "(and (page-property parent [[child page 2]]) (not (page-property foo bar)))")))
  321. "Page property queries nested NOT in second clause")
  322. (is (= ["page4"]
  323. (map
  324. :block/name
  325. (dsl-query "(and (not (page-property foo bar)) (page-property parent [[child page 2]]))")))
  326. "Page property queries nested NOT in first clause")
  327. (testing "boolean values"
  328. (is (= ["page1"]
  329. (map :block/name (dsl-query "(page-property interesting true)")))
  330. "Boolean true")
  331. (is (= #{"page2" "page3"}
  332. (set (map :block/name (dsl-query "(page-property interesting false)"))))
  333. "Boolean false")))
  334. (when-not js/process.env.DB_GRAPH
  335. (deftest page-property-queries
  336. (testing "page property tests with default config"
  337. (test-helper/with-config {}
  338. (page-property-queries-test)))))
  339. (deftest task-queries
  340. (load-test-files [{:file/path "pages/page1.md"
  341. :file/content "foo:: bar
  342. - DONE b1
  343. - TODO b2
  344. - DOING b3
  345. - DOING b4 [[A]]
  346. - DOING b5 [[B]]"}])
  347. (testing "Lowercase query"
  348. (is (= ["DONE b1"]
  349. (map testable-content (dsl-query "(task done)"))))
  350. (is (= #{"DOING b3" "DOING b4" "DOING b5"}
  351. (set (map testable-content (dsl-query "(task doing)"))))))
  352. (is (= #{"DOING b3" "DOING b4" "DOING b5"}
  353. (set (map testable-content (dsl-query "(task DOING)"))))
  354. "Uppercase query")
  355. (testing "Multiple specified tasks results in ORed results"
  356. (is (= #{"DONE b1" "DOING b3" "DOING b4" "DOING b5"}
  357. (set (map testable-content (dsl-query "(task done doing)")))))
  358. (is (= #{"DONE b1" "DOING b3" "DOING b4" "DOING b5"}
  359. (set (map testable-content (dsl-query "(task [done doing])"))))
  360. "Multiple arguments specified with vector notation"))
  361. (is (= ["DONE b1" "DOING b4"]
  362. (map testable-content
  363. (dsl-query "(or (task done) (and (task doing) [[A]]))")))
  364. "Multiple boolean operators with todo and priority operators")
  365. (is (= ["DOING b4" "DOING b5"]
  366. (map testable-content
  367. (dsl-query "(and (task doing) (or [[A]] [[B]]))")))))
  368. (when js/process.env.DB_GRAPH
  369. ;; Ensure some filters work when no data with relevant properties exist
  370. (deftest queries-with-no-data
  371. (load-test-files [])
  372. (is (= [] (dsl-query "(task todo)")))
  373. (is (= [] (dsl-query "(priority high)")))))
  374. (deftest sample-queries
  375. (load-test-files [{:file/path "pages/page1.md"
  376. :file/content "foo:: bar
  377. - TODO b1
  378. - TODO b2"}])
  379. (is (= 1
  380. (count (dsl-query "(and (task todo) (sample 1))")))
  381. "Correctly limits block results")
  382. (is (= 1
  383. (count (dsl-query (if js/process.env.DB_GRAPH "(and (property foo) (sample 1))" "(and (page-property foo) (sample 1))"))))
  384. "Correctly limits page results"))
  385. (deftest priority-queries
  386. (load-test-files (if js/process.env.DB_GRAPH
  387. [{:page {:block/title "page1"}
  388. :blocks [{:block/title "[#A] b1"
  389. :build/properties {:logseq.property/priority :logseq.property/priority.high}}
  390. {:block/title "[#B] b2"
  391. :build/properties {:logseq.property/priority :logseq.property/priority.medium}}
  392. {:block/title "[#A] b3"
  393. :build/properties {:logseq.property/priority :logseq.property/priority.high}}]}]
  394. [{:file/path "pages/page1.md"
  395. :file/content "foo:: bar
  396. - [#A] b1
  397. - [#B] b2
  398. - [#A] b3"}]))
  399. (testing "one arg queries"
  400. (is (= #{"[#A] b1" "[#A] b3"}
  401. (set (map :block/title
  402. (dsl-query (if js/process.env.DB_GRAPH "(priority high)" "(priority a)"))))))
  403. (is (= #{"[#A] b1" "[#A] b3"}
  404. (set (map :block/title
  405. (dsl-query (if js/process.env.DB_GRAPH "(priority high)" "(priority a)")))))))
  406. (testing "two arg queries"
  407. (is (= #{"[#A] b1" "[#B] b2" "[#A] b3"}
  408. (set (map :block/title
  409. (dsl-query (if js/process.env.DB_GRAPH "(priority high medium)" "(priority a b)"))))))
  410. (is (= #{"[#A] b1" "[#B] b2" "[#A] b3"}
  411. (set (map :block/title
  412. (dsl-query (if js/process.env.DB_GRAPH "(priority [high medium])" "(priority [a b])")))))
  413. "Arguments with vector notation"))
  414. (is (= #{"[#A] b1" "[#B] b2" "[#A] b3"}
  415. (set (map :block/title
  416. (dsl-query (if js/process.env.DB_GRAPH "(priority high medium low)" "(priority a b c)")))))
  417. "Three arg queries and args that have no match"))
  418. (deftest nested-boolean-queries
  419. (load-test-files [{:file/path "pages/page1.md"
  420. :file/content "foo:: bar
  421. - DONE b1 [[page 1]] [[page 3]]
  422. - DONE b2Z [[page 1]]"}
  423. {:file/path "pages/page2.md"
  424. :file/content "foo:: bar
  425. - NOW b3 [[page 1]]
  426. - LATER b4Z [[page 2]]
  427. "}])
  428. (let [task-filter (if js/process.env.DB_GRAPH "(task doing todo)" "(task now later)")]
  429. (is (= []
  430. (dsl-query "(and (task done) (not [[page 1]]))")))
  431. (is (= ["DONE b1"]
  432. (map testable-content
  433. (dsl-query "(and [[page 1]] (and [[page 3]] (not (task todo))))")))
  434. "Nested not")
  435. (is (= ["NOW b3" "LATER b4Z"]
  436. (map testable-content
  437. (dsl-query (str "(and " task-filter " (or [[page 1]] [[page 2]]))")))))
  438. (is (= #{"NOW b3"
  439. "LATER b4Z"
  440. "DONE b1"
  441. "DONE b2Z"}
  442. (set (map testable-content
  443. (dsl-query (str "(and "
  444. (if js/process.env.DB_GRAPH "(task doing todo done)" "(task now later done)")
  445. " (or [[page 1]] (not [[page 1]])))"))))))
  446. (is (= (if js/process.env.DB_GRAPH #{"bar" "DONE b1" "DONE b2Z"} #{"foo:: bar" "DONE b1" "DONE b2Z"})
  447. (->> (dsl-query (str "(not (and " task-filter " (or [[page 1]] [[page 2]])))"))
  448. (keep testable-content)
  449. (remove (fn [s] (db/page? (db/get-page s))))
  450. set)))
  451. (is (= #{"DONE b2Z" "LATER b4Z"}
  452. (->> (dsl-query "(and \"Z\" (or \"b2\" \"b4\"))")
  453. (keep testable-content)
  454. set))
  455. "AND-OR with full text search"))
  456. ;; FIXME: not working
  457. ;; Requires or-join and not-join which aren't supported yet
  458. ; (is (= []
  459. ; (dsl-query "(or (priority a) (not (priority c)))")))
  460. )
  461. (deftest tags-queries
  462. (load-test-files
  463. [{:file/path "pages/page1.md"
  464. :file/content "tags:: [[page-tag-1]], [[page-tag-2]]"}
  465. {:file/path "pages/page2.md"
  466. :file/content "tags:: [[page-tag-2]], [[page-tag-3]]"}
  467. {:file/path "pages/page3.md"
  468. :file/content "tags:: [[other]]"}])
  469. (are [x y] (= (set y) (set (map :block/name (dsl-query x))))
  470. "(page-tags [[page-tag-1]])"
  471. ["page1"]
  472. "(page-tags page-tag-2)"
  473. ["page1" "page2"]
  474. "(page-tags page-tag-1 page-tag-2)"
  475. ["page1" "page2"]
  476. "(page-tags page-TAG-1 page-tag-2)"
  477. ["page1" "page2"]
  478. "(page-tags [page-tag-1 page-tag-2])"
  479. ["page1" "page2"]))
  480. (deftest block-content-query
  481. (load-test-files [{:file/path "pages/page1.md"
  482. :file/content "- b1 Hit\n- b2 Another"}])
  483. (is (= ["b1 Hit"]
  484. (map :block/title (dsl-query "\"Hit\""))))
  485. (is (= []
  486. (map :block/title (dsl-query "\"miss\"")))
  487. "Correctly returns no results"))
  488. (deftest page-queries
  489. (load-test-files [{:file/path "pages/page1.md"
  490. :file/content "foo"}
  491. {:file/path "pages/page2.md"
  492. :file/content "bar"}])
  493. (is (= ["page1"]
  494. (map #(get-in % [:block/page :block/name])
  495. (dsl-query "(page page1)"))))
  496. (is (= []
  497. (map #(get-in % [:block/page :block/name])
  498. (dsl-query "(page nope)")))
  499. "Correctly returns no results"))
  500. (deftest empty-queries
  501. (testing "nil or blank strings should be ignored"
  502. (are [x] (nil? (dsl-query x))
  503. nil
  504. ""
  505. " "
  506. "\"\"")))
  507. (deftest page-ref-and-boolean-queries
  508. (load-test-files [{:file/path "pages/page1.md"
  509. :file/content "foo:: bar
  510. - b1 [[page 1]] [[tag2]]
  511. - b2 [[page 2]] [[tag1]]
  512. - b3"}])
  513. (testing "page-ref queries"
  514. (is (= ["b2"]
  515. (map testable-content (dsl-query "[[page 2]]")))
  516. "Page ref arg")
  517. (is (= ["b2"]
  518. (map testable-content (dsl-query "#tag1")))
  519. "Tag arg")
  520. (is (= []
  521. (dsl-query "[[blarg]]"))
  522. "Nonexistent page returns no results"))
  523. (testing "basic boolean queries"
  524. (is (= ["b2"]
  525. (map testable-content
  526. (dsl-query "(and [[tag1]] [[page 2]])")))
  527. "AND query")
  528. (is (= ["b1" "b2"]
  529. (map testable-content
  530. (dsl-query "(or [[tag2]] [[page 2]])")))
  531. "OR query")
  532. (is (= ["b1"]
  533. (map testable-content
  534. (dsl-query "(or [[tag2]] [[page 3]])")))
  535. "OR query with nonexistent page should return meaningful results")
  536. (is (= (if js/process.env.DB_GRAPH #{"b1" "bar" "b3"} #{"b1" "foo:: bar" "b3"})
  537. (->> (dsl-query "(not [[page 2]])")
  538. ;; Only filter to page1 to get meaningful results
  539. (filter #(= "page1" (get-in % [:block/page :block/name])))
  540. (map testable-content)
  541. (set)))
  542. "NOT query")))
  543. (deftest nested-page-ref-queries
  544. (load-test-files (if js/process.env.DB_GRAPH
  545. [{:page {:block/title "page1"}
  546. :blocks [{:block/title "p1 [[Parent page]]"
  547. :build/children [{:block/title "[[Child page]]"}]}
  548. {:block/title "p2 [[Parent page]]"
  549. :build/children [{:block/title "Non linked content"}]}]}]
  550. [{:file/path "pages/page1.md"
  551. :file/content "foo:: bar
  552. - p1 [[Parent page]]
  553. - [[Child page]]
  554. - p2 [[Parent page]]
  555. - Non linked content"}]))
  556. (is (= (set
  557. ["Non linked content"
  558. "p2"
  559. "p1"])
  560. (set
  561. (map testable-content
  562. (dsl-query "(and [[Parent page]] (not [[Child page]]))"))))))
  563. (deftest between-queries
  564. (load-test-files [{:file/path "journals/2020_12_26.md"
  565. :file/content "foo::bar
  566. - DONE 26-b1
  567. created-at:: 1608968448113
  568. - LATER 26-b2-modified-later
  569. created-at:: 1608968448114
  570. - DONE 26-b3
  571. created-at:: 1608968448115
  572. - 26-b4
  573. created-at:: 1608968448116
  574. "}])
  575. (let [task-filter (if js/process.env.DB_GRAPH "(task todo done)" "(task later done)")]
  576. (are [x y] (= (count (dsl-query x)) y)
  577. (str "(and " task-filter " (between [[Dec 26th, 2020]] tomorrow))")
  578. 3
  579. ;; between with journal pages
  580. (str "(and " task-filter " (between [[Dec 26th, 2020]] [[Dec 27th, 2020]]))")
  581. 3)
  582. (when js/process.env.DB_GRAPH
  583. (is (= 3 (count (dsl-query "(and (task todo done) (between created-at [[Dec 26th, 2020]]))"))))
  584. (is (= 3 (count (dsl-query "(and (task todo done) (between created-at [[Dec 26th, 2020]] +1d))")))))))
  585. (deftest custom-query-test
  586. (load-test-files [{:file/path "pages/page1.md"
  587. :file/content "foo:: bar
  588. - NOW b1
  589. - TODO b2
  590. - LATER b3
  591. - b3"}])
  592. (let [task-query (if js/process.env.DB_GRAPH
  593. '(task doing)
  594. '(task now))]
  595. (is (= ["NOW b1"]
  596. (map :block/title (custom-query {:query task-query}))))
  597. (is (= ["NOW b1"]
  598. (map :block/title (custom-query {:query (list 'and task-query "b")})))
  599. "Query with rule that can't be derived from the form itself")))
  600. (def get-property-value #(get-in %1 [:block/properties %2]))
  601. (when-not js/process.env.DB_GRAPH
  602. (deftest sort-by-queries
  603. (load-test-files [{:file/path "journals/2020_02_25.md"
  604. :file/content "rating:: 10"}
  605. {:file/path "journals/2020_12_26.md"
  606. :file/content "rating:: 8
  607. - DONE 26-b1
  608. created-at:: 1608968448113
  609. fruit:: plum
  610. - LATER 26-b2-modified-later
  611. created-at:: 1608968448114
  612. fruit:: apple
  613. - DONE 26-b3 has no fruit to test sorting of absent property value
  614. created-at:: 1608968448115
  615. - 26-b4
  616. created-at:: 1608968448116
  617. "}])
  618. (let [task-filter "(task later done)"]
  619. (testing "sort-by user block property fruit"
  620. (let [result (->> (dsl-query (str "(and " task-filter " (sort-by fruit))"))
  621. (map #(get-property-value % :fruit)))]
  622. (is (= ["plum" "apple" nil]
  623. result)
  624. "sort-by correctly defaults to desc"))
  625. (let [result (->> (dsl-query (str "(and " task-filter " (sort-by fruit desc))"))
  626. (map #(get-property-value % :fruit)))]
  627. (is (= ["plum" "apple" nil]
  628. result)
  629. "sort-by desc"))
  630. (let [result (->> (dsl-query (str "(and " task-filter " (sort-by fruit asc))"))
  631. (map #(get-property-value % :fruit)))]
  632. (is (= [nil "apple" "plum"]
  633. result)
  634. "sort-by asc")))
  635. (testing "sort-by hidden, built-in block property created-at"
  636. (let [result (->> (dsl-query (str "(and " task-filter " (sort-by created-at desc))"))
  637. (map #(get-property-value % :created-at)))]
  638. (is (= [1608968448115 1608968448114 1608968448113]
  639. result))
  640. "sorted-by desc")
  641. (let [result (->> (dsl-query (str "(and " task-filter " (sort-by created-at asc))"))
  642. (map #(get-property-value % :created-at)))]
  643. (is (= [1608968448113 1608968448114 1608968448115]
  644. result)
  645. "sorted-by asc")))
  646. (testing "user page property rating"
  647. (is (= [10 8]
  648. (->> (dsl-query "(and (page-property rating) (sort-by rating))")
  649. (map #(get-property-value % :rating)))))))))
  650. (deftest simplify-query
  651. (are [x y] (= (query-dsl/simplify-query x) y)
  652. '(and [[foo]])
  653. '[[foo]]
  654. '(and (and [[foo]]))
  655. '[[foo]]
  656. '(and (or [[foo]]))
  657. '[[foo]]
  658. '(and (not [[foo]]))
  659. '(not [[foo]])
  660. '(and (or (and [[foo]])))
  661. '[[foo]]
  662. '(not (or [[foo]]))
  663. '(not [[foo]])))
  664. (comment
  665. (require '[clojure.pprint :as pprint])
  666. (test-helper/start-test-db!)
  667. (query-dsl/query test-helper/test-db "(task done)")
  668. ;; Useful for debugging
  669. (prn
  670. (datascript.core/q
  671. '[:find (pull ?b [*])
  672. :where
  673. [?b :block/name]]
  674. (frontend.db/get-db test-helper/test-db)))
  675. ;; (or (priority a) (not (priority a)))
  676. ;; FIXME: Error: Insufficient bindings: #{?priority} not bound in [(contains? #{"A"} ?priority)]
  677. (pprint/pprint
  678. (d/q
  679. '[:find (pull ?b [*])
  680. :where
  681. [?b :block/uuid]
  682. (or (and [?b :block/priority ?priority] [(contains? #{"A"} ?priority)])
  683. (not [?b :block/priority #{"A"}]
  684. [(contains? #{"A"} ?priority)]))]
  685. (frontend.db/get-db test-helper/test-db))))
  686. (when-not js/process.env.DB_GRAPH
  687. (deftest namespace-queries
  688. (load-test-files [{:file/path "pages/ns1.page1.md"
  689. :file/content "foo"}
  690. {:file/path "pages/ns1.page2.md"
  691. :file/content "bar"}
  692. {:file/path "pages/ns2.page1.md"
  693. :file/content "baz"}])
  694. (is (= #{"ns1/page1" "ns1/page2"}
  695. (set (map :block/name (dsl-query "(namespace ns1)")))))
  696. (is (= #{}
  697. (set (map :block/name (dsl-query "(namespace blarg)"))))
  698. "Correctly returns no results")))