datahike-browser-tests 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/.circleci/config.yml +405 -0
  2. package/.circleci/scripts/gen_ci.clj +194 -0
  3. package/.cirrus.yml +60 -0
  4. package/.clj-kondo/babashka/sci/config.edn +1 -0
  5. package/.clj-kondo/babashka/sci/sci/core.clj +9 -0
  6. package/.clj-kondo/config.edn +95 -0
  7. package/.dir-locals.el +2 -0
  8. package/.github/FUNDING.yml +3 -0
  9. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +68 -0
  10. package/.github/ISSUE_TEMPLATE/2-feature-request.yml +28 -0
  11. package/.github/ISSUE_TEMPLATE/config.yml +6 -0
  12. package/.github/pull_request_template.md +24 -0
  13. package/.github/workflows/native-image.yml +84 -0
  14. package/LICENSE +203 -0
  15. package/README.md +273 -0
  16. package/bb/deps.edn +9 -0
  17. package/bb/resources/github-fingerprints +3 -0
  18. package/bb/resources/native-image-tests/run-bb-pod-tests.clj +162 -0
  19. package/bb/resources/native-image-tests/run-libdatahike-tests +12 -0
  20. package/bb/resources/native-image-tests/run-native-image-tests +74 -0
  21. package/bb/resources/native-image-tests/run-python-tests +22 -0
  22. package/bb/resources/native-image-tests/testconfig.attr-refs.edn +6 -0
  23. package/bb/resources/native-image-tests/testconfig.edn +5 -0
  24. package/bb/resources/template/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
  25. package/bb/resources/template/.settings/org.eclipse.jdt.core.prefs +9 -0
  26. package/bb/resources/template/.settings/org.eclipse.m2e.core.prefs +4 -0
  27. package/bb/resources/template/pom.xml +22 -0
  28. package/bb/src/tools/build.clj +132 -0
  29. package/bb/src/tools/clj_kondo.clj +32 -0
  30. package/bb/src/tools/deploy.clj +26 -0
  31. package/bb/src/tools/examples.clj +19 -0
  32. package/bb/src/tools/npm.clj +100 -0
  33. package/bb/src/tools/python.clj +14 -0
  34. package/bb/src/tools/release.clj +94 -0
  35. package/bb/src/tools/test.clj +148 -0
  36. package/bb/src/tools/version.clj +47 -0
  37. package/bb.edn +269 -0
  38. package/benchmark/src/benchmark/cli.clj +195 -0
  39. package/benchmark/src/benchmark/compare.clj +157 -0
  40. package/benchmark/src/benchmark/config.clj +316 -0
  41. package/benchmark/src/benchmark/measure.clj +187 -0
  42. package/benchmark/src/benchmark/store.clj +190 -0
  43. package/benchmark/test/benchmark/measure_test.clj +156 -0
  44. package/build.clj +30 -0
  45. package/config.edn +49 -0
  46. package/deps.edn +138 -0
  47. package/dev/sandbox.clj +82 -0
  48. package/dev/sandbox.cljs +127 -0
  49. package/dev/sandbox_benchmarks.clj +27 -0
  50. package/dev/sandbox_client.clj +87 -0
  51. package/dev/sandbox_transact_bench.clj +109 -0
  52. package/dev/user.clj +79 -0
  53. package/doc/README.md +96 -0
  54. package/doc/adl/README.md +6 -0
  55. package/doc/adl/adr-000-adr.org +28 -0
  56. package/doc/adl/adr-001-attribute-references.org +15 -0
  57. package/doc/adl/adr-002-build-tooling.org +54 -0
  58. package/doc/adl/adr-003-db-meta-data.md +52 -0
  59. package/doc/adl/adr-004-github-flow.md +40 -0
  60. package/doc/adl/adr-XYZ-template.md +30 -0
  61. package/doc/adl/index.org +3 -0
  62. package/doc/assets/datahike-logo.svg +3 -0
  63. package/doc/assets/datahiking-invoice.org +85 -0
  64. package/doc/assets/hhtree2.png +0 -0
  65. package/doc/assets/network_topology.svg +624 -0
  66. package/doc/assets/perf.png +0 -0
  67. package/doc/assets/schema_mindmap.mm +132 -0
  68. package/doc/assets/schema_mindmap.svg +970 -0
  69. package/doc/assets/temporal_index.mm +74 -0
  70. package/doc/backend-development.md +78 -0
  71. package/doc/bb-pod.md +89 -0
  72. package/doc/benchmarking.md +360 -0
  73. package/doc/bindings/edn-conversion.md +383 -0
  74. package/doc/cli.md +162 -0
  75. package/doc/cljdoc.edn +27 -0
  76. package/doc/cljs-support.md +133 -0
  77. package/doc/config.md +406 -0
  78. package/doc/contributing.md +114 -0
  79. package/doc/datalog-vs-sql.md +210 -0
  80. package/doc/datomic_differences.md +109 -0
  81. package/doc/development/pull-api-ns.md +186 -0
  82. package/doc/development/pull-frame-state-diagram.jpg +0 -0
  83. package/doc/distributed.md +566 -0
  84. package/doc/entity_spec.md +92 -0
  85. package/doc/gc.md +273 -0
  86. package/doc/java-api.md +808 -0
  87. package/doc/javascript-api.md +421 -0
  88. package/doc/libdatahike.md +86 -0
  89. package/doc/logging_and_error_handling.md +43 -0
  90. package/doc/norms.md +66 -0
  91. package/doc/schema-migration.md +85 -0
  92. package/doc/schema.md +287 -0
  93. package/doc/storage-backends.md +363 -0
  94. package/doc/store-id-refactoring.md +596 -0
  95. package/doc/time_variance.md +325 -0
  96. package/doc/unstructured.md +167 -0
  97. package/doc/versioning.md +261 -0
  98. package/examples/basic/README.md +19 -0
  99. package/examples/basic/deps.edn +6 -0
  100. package/examples/basic/docker-compose.yml +13 -0
  101. package/examples/basic/src/examples/core.clj +60 -0
  102. package/examples/basic/src/examples/schema.clj +155 -0
  103. package/examples/basic/src/examples/store.clj +60 -0
  104. package/examples/basic/src/examples/time_travel.clj +185 -0
  105. package/examples/java/.settings/org.eclipse.core.resources.prefs +3 -0
  106. package/examples/java/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
  107. package/examples/java/.settings/org.eclipse.jdt.core.prefs +9 -0
  108. package/examples/java/.settings/org.eclipse.m2e.core.prefs +4 -0
  109. package/examples/java/README.md +162 -0
  110. package/examples/java/pom.xml +62 -0
  111. package/examples/java/src/main/java/examples/QuickStart.java +115 -0
  112. package/examples/java/src/main/java/examples/SchemaExample.java +148 -0
  113. package/examples/java/src/main/java/examples/TimeTravelExample.java +121 -0
  114. package/flake.lock +27 -0
  115. package/flake.nix +27 -0
  116. package/http-server/datahike/http/middleware.clj +75 -0
  117. package/http-server/datahike/http/server.clj +269 -0
  118. package/java/src/datahike/java/Database.java +274 -0
  119. package/java/src/datahike/java/Datahike.java +281 -0
  120. package/java/src/datahike/java/DatahikeGeneratedTest.java +349 -0
  121. package/java/src/datahike/java/DatahikeTest.java +370 -0
  122. package/java/src/datahike/java/EDN.java +170 -0
  123. package/java/src/datahike/java/IEntity.java +11 -0
  124. package/java/src/datahike/java/Keywords.java +161 -0
  125. package/java/src/datahike/java/SchemaFlexibility.java +52 -0
  126. package/java/src/datahike/java/Util.java +219 -0
  127. package/karma.conf.js +19 -0
  128. package/libdatahike/compile-cpp +7 -0
  129. package/libdatahike/src/datahike/impl/LibDatahikeBase.java +203 -0
  130. package/libdatahike/src/datahike/impl/libdatahike.clj +59 -0
  131. package/libdatahike/src/test_cpp.cpp +61 -0
  132. package/npm-package/PUBLISHING.md +140 -0
  133. package/npm-package/README.md +226 -0
  134. package/npm-package/package.template.json +34 -0
  135. package/npm-package/test-isomorphic.ts +281 -0
  136. package/npm-package/test.js +557 -0
  137. package/npm-package/typescript-test.ts +70 -0
  138. package/package.json +16 -0
  139. package/pydatahike/README.md +569 -0
  140. package/pydatahike/pyproject.toml +91 -0
  141. package/pydatahike/setup.py +42 -0
  142. package/pydatahike/src/datahike/__init__.py +134 -0
  143. package/pydatahike/src/datahike/_native.py +250 -0
  144. package/pydatahike/src/datahike/_version.py +2 -0
  145. package/pydatahike/src/datahike/database.py +722 -0
  146. package/pydatahike/src/datahike/edn.py +311 -0
  147. package/pydatahike/src/datahike/py.typed +0 -0
  148. package/pydatahike/tests/conftest.py +17 -0
  149. package/pydatahike/tests/test_basic.py +170 -0
  150. package/pydatahike/tests/test_database.py +51 -0
  151. package/pydatahike/tests/test_edn_conversion.py +299 -0
  152. package/pydatahike/tests/test_query.py +99 -0
  153. package/pydatahike/tests/test_schema.py +55 -0
  154. package/resources/clj-kondo.exports/io.replikativ/datahike/config.edn +5 -0
  155. package/resources/example_server.edn +4 -0
  156. package/shadow-cljs.edn +56 -0
  157. package/src/data_readers.clj +7 -0
  158. package/src/datahike/api/impl.cljc +176 -0
  159. package/src/datahike/api/specification.cljc +633 -0
  160. package/src/datahike/api/types.cljc +261 -0
  161. package/src/datahike/api.cljc +41 -0
  162. package/src/datahike/array.cljc +99 -0
  163. package/src/datahike/cli.clj +166 -0
  164. package/src/datahike/cljs.cljs +6 -0
  165. package/src/datahike/codegen/cli.clj +406 -0
  166. package/src/datahike/codegen/clj_kondo.clj +291 -0
  167. package/src/datahike/codegen/java.clj +403 -0
  168. package/src/datahike/codegen/naming.cljc +33 -0
  169. package/src/datahike/codegen/native.clj +559 -0
  170. package/src/datahike/codegen/pod.clj +488 -0
  171. package/src/datahike/codegen/python.clj +838 -0
  172. package/src/datahike/codegen/report.clj +55 -0
  173. package/src/datahike/codegen/typescript.clj +262 -0
  174. package/src/datahike/codegen/validation.clj +145 -0
  175. package/src/datahike/config.cljc +294 -0
  176. package/src/datahike/connections.cljc +16 -0
  177. package/src/datahike/connector.cljc +265 -0
  178. package/src/datahike/constants.cljc +142 -0
  179. package/src/datahike/core.cljc +297 -0
  180. package/src/datahike/datom.cljc +459 -0
  181. package/src/datahike/db/interface.cljc +119 -0
  182. package/src/datahike/db/search.cljc +305 -0
  183. package/src/datahike/db/transaction.cljc +937 -0
  184. package/src/datahike/db/utils.cljc +338 -0
  185. package/src/datahike/db.cljc +956 -0
  186. package/src/datahike/experimental/unstructured.cljc +126 -0
  187. package/src/datahike/experimental/versioning.cljc +172 -0
  188. package/src/datahike/externs.js +31 -0
  189. package/src/datahike/gc.cljc +69 -0
  190. package/src/datahike/http/client.clj +188 -0
  191. package/src/datahike/http/writer.clj +79 -0
  192. package/src/datahike/impl/entity.cljc +218 -0
  193. package/src/datahike/index/interface.cljc +93 -0
  194. package/src/datahike/index/persistent_set.cljc +469 -0
  195. package/src/datahike/index/utils.cljc +44 -0
  196. package/src/datahike/index.cljc +32 -0
  197. package/src/datahike/js/api.cljs +172 -0
  198. package/src/datahike/js/api_macros.clj +22 -0
  199. package/src/datahike/js.cljs +163 -0
  200. package/src/datahike/json.cljc +209 -0
  201. package/src/datahike/lru.cljc +146 -0
  202. package/src/datahike/migrate.clj +39 -0
  203. package/src/datahike/norm/norm.clj +245 -0
  204. package/src/datahike/online_gc.cljc +252 -0
  205. package/src/datahike/pod.clj +155 -0
  206. package/src/datahike/pull_api.cljc +325 -0
  207. package/src/datahike/query.cljc +1945 -0
  208. package/src/datahike/query_stats.cljc +88 -0
  209. package/src/datahike/readers.cljc +62 -0
  210. package/src/datahike/remote.cljc +218 -0
  211. package/src/datahike/schema.cljc +228 -0
  212. package/src/datahike/schema_cache.cljc +42 -0
  213. package/src/datahike/spec.cljc +101 -0
  214. package/src/datahike/store.cljc +80 -0
  215. package/src/datahike/tools.cljc +308 -0
  216. package/src/datahike/transit.cljc +80 -0
  217. package/src/datahike/writer.cljc +239 -0
  218. package/src/datahike/writing.cljc +362 -0
  219. package/src/deps.cljs +1 -0
  220. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/insert.cljc +76 -0
  221. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/upsert.cljc +128 -0
  222. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree.cljc +213 -0
  223. package/test/datahike/backward_compatibility_test/src/backward_test.clj +37 -0
  224. package/test/datahike/integration_test/config_record_file_test.clj +14 -0
  225. package/test/datahike/integration_test/config_record_test.clj +14 -0
  226. package/test/datahike/integration_test/depr_config_uri_test.clj +15 -0
  227. package/test/datahike/integration_test/return_map_test.clj +62 -0
  228. package/test/datahike/integration_test.cljc +67 -0
  229. package/test/datahike/norm/norm_test.clj +124 -0
  230. package/test/datahike/norm/resources/naming-and-sorting-test/001-a1-example.edn +5 -0
  231. package/test/datahike/norm/resources/naming-and-sorting-test/002-a2-example.edn +5 -0
  232. package/test/datahike/norm/resources/naming-and-sorting-test/003-tx-fn-test.edn +1 -0
  233. package/test/datahike/norm/resources/naming-and-sorting-test/004-tx-data-and-tx-fn-test.edn +5 -0
  234. package/test/datahike/norm/resources/naming-and-sorting-test/01-transact-basic-characters.edn +2 -0
  235. package/test/datahike/norm/resources/naming-and-sorting-test/02 add occupation.edn +5 -0
  236. package/test/datahike/norm/resources/naming-and-sorting-test/checksums.edn +12 -0
  237. package/test/datahike/norm/resources/simple-test/001-a1-example.edn +5 -0
  238. package/test/datahike/norm/resources/simple-test/002-a2-example.edn +5 -0
  239. package/test/datahike/norm/resources/simple-test/checksums.edn +4 -0
  240. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/001-a1-example.edn +5 -0
  241. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/002-a2-example.edn +5 -0
  242. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/003-tx-fn-test.edn +1 -0
  243. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/checksums.edn +6 -0
  244. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/004-tx-data-and-tx-fn-test.edn +5 -0
  245. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/checksums.edn +2 -0
  246. package/test/datahike/norm/resources/tx-fn-test/first/001-a1-example.edn +5 -0
  247. package/test/datahike/norm/resources/tx-fn-test/first/002-a2-example.edn +5 -0
  248. package/test/datahike/norm/resources/tx-fn-test/first/checksums.edn +4 -0
  249. package/test/datahike/norm/resources/tx-fn-test/second/003-tx-fn-test.edn +1 -0
  250. package/test/datahike/norm/resources/tx-fn-test/second/checksums.edn +2 -0
  251. package/test/datahike/test/api_test.cljc +895 -0
  252. package/test/datahike/test/array_test.cljc +40 -0
  253. package/test/datahike/test/attribute_refs/datoms_test.cljc +140 -0
  254. package/test/datahike/test/attribute_refs/db_test.cljc +42 -0
  255. package/test/datahike/test/attribute_refs/differences_test.cljc +515 -0
  256. package/test/datahike/test/attribute_refs/entity_test.cljc +89 -0
  257. package/test/datahike/test/attribute_refs/pull_api_test.cljc +320 -0
  258. package/test/datahike/test/attribute_refs/query_find_specs_test.cljc +59 -0
  259. package/test/datahike/test/attribute_refs/query_fns_test.cljc +130 -0
  260. package/test/datahike/test/attribute_refs/query_interop_test.cljc +47 -0
  261. package/test/datahike/test/attribute_refs/query_not_test.cljc +193 -0
  262. package/test/datahike/test/attribute_refs/query_or_test.cljc +137 -0
  263. package/test/datahike/test/attribute_refs/query_pull_test.cljc +156 -0
  264. package/test/datahike/test/attribute_refs/query_rules_test.cljc +176 -0
  265. package/test/datahike/test/attribute_refs/query_test.cljc +241 -0
  266. package/test/datahike/test/attribute_refs/temporal_search.cljc +22 -0
  267. package/test/datahike/test/attribute_refs/transact_test.cljc +220 -0
  268. package/test/datahike/test/attribute_refs/utils.cljc +128 -0
  269. package/test/datahike/test/cache_test.cljc +38 -0
  270. package/test/datahike/test/components_test.cljc +92 -0
  271. package/test/datahike/test/config_test.cljc +158 -0
  272. package/test/datahike/test/core_test.cljc +105 -0
  273. package/test/datahike/test/datom_test.cljc +44 -0
  274. package/test/datahike/test/db_test.cljc +54 -0
  275. package/test/datahike/test/entity_spec_test.cljc +159 -0
  276. package/test/datahike/test/entity_test.cljc +103 -0
  277. package/test/datahike/test/explode_test.cljc +143 -0
  278. package/test/datahike/test/filter_test.cljc +75 -0
  279. package/test/datahike/test/gc_test.cljc +159 -0
  280. package/test/datahike/test/http/server_test.clj +192 -0
  281. package/test/datahike/test/http/writer_test.clj +86 -0
  282. package/test/datahike/test/ident_test.cljc +32 -0
  283. package/test/datahike/test/index_test.cljc +345 -0
  284. package/test/datahike/test/insert.cljc +125 -0
  285. package/test/datahike/test/java_bindings_test.clj +6 -0
  286. package/test/datahike/test/listen_test.cljc +41 -0
  287. package/test/datahike/test/lookup_refs_test.cljc +266 -0
  288. package/test/datahike/test/lru_test.cljc +27 -0
  289. package/test/datahike/test/migrate_test.clj +297 -0
  290. package/test/datahike/test/model/core.cljc +376 -0
  291. package/test/datahike/test/model/invariant.cljc +142 -0
  292. package/test/datahike/test/model/rng.cljc +82 -0
  293. package/test/datahike/test/model_test.clj +217 -0
  294. package/test/datahike/test/nodejs_test.cljs +262 -0
  295. package/test/datahike/test/online_gc_test.cljc +475 -0
  296. package/test/datahike/test/pod_test.clj +369 -0
  297. package/test/datahike/test/pull_api_test.cljc +474 -0
  298. package/test/datahike/test/purge_test.cljc +144 -0
  299. package/test/datahike/test/query_aggregates_test.cljc +101 -0
  300. package/test/datahike/test/query_find_specs_test.cljc +52 -0
  301. package/test/datahike/test/query_fns_test.cljc +523 -0
  302. package/test/datahike/test/query_interop_test.cljc +47 -0
  303. package/test/datahike/test/query_not_test.cljc +189 -0
  304. package/test/datahike/test/query_or_test.cljc +158 -0
  305. package/test/datahike/test/query_pull_test.cljc +147 -0
  306. package/test/datahike/test/query_rules_test.cljc +248 -0
  307. package/test/datahike/test/query_stats_test.cljc +218 -0
  308. package/test/datahike/test/query_test.cljc +984 -0
  309. package/test/datahike/test/schema_test.cljc +424 -0
  310. package/test/datahike/test/specification_test.cljc +30 -0
  311. package/test/datahike/test/store_test.cljc +78 -0
  312. package/test/datahike/test/stress_test.cljc +57 -0
  313. package/test/datahike/test/time_variance_test.cljc +518 -0
  314. package/test/datahike/test/tools_test.clj +134 -0
  315. package/test/datahike/test/transact_test.cljc +518 -0
  316. package/test/datahike/test/tuples_test.cljc +564 -0
  317. package/test/datahike/test/unstructured_test.cljc +291 -0
  318. package/test/datahike/test/upsert_impl_test.cljc +205 -0
  319. package/test/datahike/test/upsert_test.cljc +363 -0
  320. package/test/datahike/test/utils.cljc +110 -0
  321. package/test/datahike/test/validation_test.cljc +48 -0
  322. package/test/datahike/test/versioning_test.cljc +56 -0
  323. package/test/datahike/test.cljc +66 -0
  324. package/tests.edn +24 -0
@@ -0,0 +1,984 @@
1
+ (ns datahike.test.query-test
2
+ (:require
3
+ #?(:cljs [cljs.test :as t :refer-macros [is deftest testing]]
4
+ :clj [clojure.test :as t :refer [is deftest testing]])
5
+ [datahike.api :as d]
6
+ [datahike.db :as db]
7
+ [datahike.test.utils :as utils]
8
+ [datahike.query :as dq]
9
+ [taoensso.timbre :as log])
10
+ #?(:clj
11
+ (:import [clojure.lang ExceptionInfo])))
12
+
13
+ #?(:cljs (def Throwable js/Error))
14
+
15
+ (deftest test-joins
16
+ (let [db (-> (db/empty-db)
17
+ (d/db-with [{:db/id 1, :name "Ivan", :age 15}
18
+ {:db/id 2, :name "Petr", :age 37}
19
+ {:db/id 3, :name "Ivan", :age 37}
20
+ {:db/id 4, :age 15}]))]
21
+ (is (= (d/q '[:find ?e
22
+ :where [?e :name]] db)
23
+ #{[1] [2] [3]}))
24
+ (is (= (d/q '[:find ?e ?v
25
+ :where [?e :name "Ivan"]
26
+ [?e :age ?v]] db)
27
+ #{[1 15] [3 37]}))
28
+ (is (= (d/q '[:find ?e1 ?e2
29
+ :where [?e1 :name ?n]
30
+ [?e2 :name ?n]] db)
31
+ #{[1 1] [2 2] [3 3] [1 3] [3 1]}))
32
+ (is (= (d/q '[:find ?e ?e2 ?n
33
+ :where [?e :name "Ivan"]
34
+ [?e :age ?a]
35
+ [?e2 :age ?a]
36
+ [?e2 :name ?n]] db)
37
+ #{[1 1 "Ivan"]
38
+ [3 3 "Ivan"]
39
+ [3 2 "Petr"]}))))
40
+
41
+ (deftest test-mixed-age-types
42
+ (let [db (-> (db/empty-db)
43
+ (d/db-with [{:db/id 1, :name "Ivan", :age 15}
44
+ {:db/id 2, :name "Petr", :age "37"}
45
+ {:db/id 3, :name "Ivan", :age :thirtyseven}
46
+ {:db/id 4, :age 15}]))]
47
+ (is (= (d/q '[:find ?e ?name
48
+ :where
49
+ [?e :name ?name]
50
+ [?e :age "37"]] db)
51
+ #{[2 "Petr"]}))))
52
+
53
+ (deftest test-q-many
54
+ (let [db (-> (db/empty-db {:aka {:db/cardinality :db.cardinality/many}})
55
+ (d/db-with [[:db/add 1 :name "Ivan"]
56
+ [:db/add 1 :aka "ivolga"]
57
+ [:db/add 1 :aka "pi"]
58
+ [:db/add 2 :name "Petr"]
59
+ [:db/add 2 :aka "porosenok"]
60
+ [:db/add 2 :aka "pi"]]))]
61
+ (is (= (d/q '[:find ?n1 ?n2
62
+ :where
63
+ [?e1 :aka ?x]
64
+ [?e2 :aka ?x]
65
+ [?e1 :name ?n1]
66
+ [?e2 :name ?n2]] db)
67
+ #{["Ivan" "Ivan"]
68
+ ["Petr" "Petr"]
69
+ ["Ivan" "Petr"]
70
+ ["Petr" "Ivan"]}))))
71
+
72
+ (deftest test-q-coll
73
+ (let [db [[1 :name "Ivan"]
74
+ [1 :age 19]
75
+ [1 :aka "dragon_killer_94"]
76
+ [1 :aka "-=autobot=-"]]]
77
+ (is (= (d/q '[:find ?n ?a
78
+ :where [?e :aka "dragon_killer_94"]
79
+ [?e :name ?n]
80
+ [?e :age ?a]] db)
81
+ #{["Ivan" 19]})))
82
+
83
+ (testing "Query over long tuples"
84
+ (let [db [[1 :name "Ivan" 945 :db/add]
85
+ [1 :age 39 999 :db/retract]]]
86
+ (is (= (d/q '[:find ?e ?v
87
+ :where [?e :name ?v]] db)
88
+ #{[1 "Ivan"]}))
89
+ (is (= (d/q '[:find ?e ?a ?v ?t
90
+ :where [?e ?a ?v ?t :db/retract]] db)
91
+ #{[1 :age 39 999]})))))
92
+
93
+ (deftest test-q-in
94
+ (let [db (-> (db/empty-db)
95
+ (d/db-with [{:db/id 1, :name "Ivan", :age 15}
96
+ {:db/id 2, :name "Petr", :age 37}
97
+ {:db/id 3, :name "Ivan", :age 37}]))
98
+ query '{:find [?e]
99
+ :in [$ ?attr ?value]
100
+ :where [[?e ?attr ?value]]}]
101
+ (is (= (d/q query db :name "Ivan")
102
+ #{[1] [3]}))
103
+ (is (= (d/q query db :age 37)
104
+ #{[2] [3]}))
105
+
106
+ (testing "Named DB"
107
+ (is (= (d/q '[:find ?a ?v
108
+ :in $db ?e
109
+ :where [$db ?e ?a ?v]] db 1)
110
+ #{[:name "Ivan"]
111
+ [:age 15]})))
112
+
113
+ (testing "DB join with collection"
114
+ (is (= (d/q '[:find ?e ?email
115
+ :in $ $b
116
+ :where [?e :name ?n]
117
+ [$b ?n ?email]]
118
+ db
119
+ [["Ivan" "ivan@mail.ru"]
120
+ ["Petr" "petr@gmail.com"]])
121
+ #{[1 "ivan@mail.ru"]
122
+ [2 "petr@gmail.com"]
123
+ [3 "ivan@mail.ru"]})))
124
+
125
+ (testing "Query without DB"
126
+ (is (= (d/q '[:find ?a ?b
127
+ :in ?a ?b]
128
+ 10 20)
129
+ #{[10 20]})))))
130
+
131
+ (deftest test-bindings
132
+ (let [db (-> (db/empty-db)
133
+ (d/db-with [{:db/id 1, :name "Ivan", :age 15}
134
+ {:db/id 2, :name "Petr", :age 37}
135
+ {:db/id 3, :name "Ivan", :age 37}]))]
136
+ (testing "Relation binding"
137
+ (is (= (d/q '[:find ?e ?email
138
+ :in $ [[?n ?email]]
139
+ :where [?e :name ?n]]
140
+ db
141
+ [["Ivan" "ivan@mail.ru"]
142
+ ["Petr" "petr@gmail.com"]])
143
+ #{[1 "ivan@mail.ru"]
144
+ [2 "petr@gmail.com"]
145
+ [3 "ivan@mail.ru"]})))
146
+
147
+ (testing "Tuple binding"
148
+ (is (= (d/q '[:find ?e
149
+ :in $ [?name ?age]
150
+ :where [?e :name ?name]
151
+ [?e :age ?age]]
152
+ db ["Ivan" 37])
153
+ #{[3]})))
154
+
155
+ (testing "Collection binding"
156
+ (is (= (d/q '[:find ?attr ?value
157
+ :in $ ?e [?attr ...]
158
+ :where [?e ?attr ?value]]
159
+ db 1 [:name :age])
160
+ #{[:name "Ivan"] [:age 15]})))
161
+
162
+ (testing "Empty coll handling"
163
+ (is (= (d/q '[:find ?id
164
+ :in $ [?id ...]
165
+ :where [?id :age _]]
166
+ [[1 :name "Ivan"]
167
+ [2 :name "Petr"]]
168
+ [])
169
+ #{}))
170
+ (is (= (d/q '[:find ?id
171
+ :in $ [[?id]]
172
+ :where [?id :age _]]
173
+ [[1 :name "Ivan"]
174
+ [2 :name "Petr"]]
175
+ [])
176
+ #{})))
177
+
178
+ (testing "Placeholders"
179
+ (is (= (d/q '[:find ?x ?z
180
+ :in [?x _ ?z]]
181
+ [:x :y :z])
182
+ #{[:x :z]}))
183
+ (is (= (d/q '[:find ?x ?z
184
+ :in [[?x _ ?z]]]
185
+ [[:x :y :z] [:a :b :c]])
186
+ #{[:x :z] [:a :c]})))
187
+
188
+ (testing "Error reporting"
189
+ (is (thrown-with-msg? ExceptionInfo #"Cannot bind value :a to tuple \[\?a \?b\]"
190
+ (d/q '[:find ?a ?b :in [?a ?b]] :a)))
191
+ (is (thrown-with-msg? ExceptionInfo #"Cannot bind value :a to collection \[\?a \.\.\.\]"
192
+ (d/q '[:find ?a :in [?a ...]] :a)))
193
+ (is (thrown-with-msg? ExceptionInfo #"Not enough elements in a collection \[:a\] to bind tuple \[\?a \?b\]"
194
+ (d/q '[:find ?a ?b :in [?a ?b]] [:a]))))))
195
+
196
+ (deftest test-nested-bindings
197
+ (is (= (d/q '[:find ?k ?v
198
+ :in [[?k ?v] ...]
199
+ :where [(> ?v 1)]]
200
+ {:a 1, :b 2, :c 3})
201
+ #{[:b 2] [:c 3]}))
202
+
203
+ (is (= (d/q '[:find ?k ?min ?max
204
+ :in [[?k ?v] ...] ?minmax
205
+ :where [(?minmax ?v) [?min ?max]]
206
+ [(> ?max ?min)]]
207
+ {:a [1 2 3 4]
208
+ :b [5 6 7]
209
+ :c [3]}
210
+ #(vector (reduce min %) (reduce max %)))
211
+ #{[:a 1 4] [:b 5 7]}))
212
+
213
+ (is (= (d/q '[:find ?k ?x
214
+ :in [[?k [?min ?max]] ...] ?range
215
+ :where [(?range ?min ?max) [?x ...]]
216
+ [(even? ?x)]]
217
+ {:a [1 7]
218
+ :b [2 4]}
219
+ range)
220
+ #{[:a 2] [:a 4] [:a 6]
221
+ [:b 2]})))
222
+
223
+ (deftest test-offset
224
+ (let [db (-> (db/empty-db)
225
+ (d/db-with [{:db/id 1, :name "Alice", :age 15}
226
+ {:db/id 2, :name "Bob", :age 37}
227
+ {:db/id 3, :name "Charlie", :age 37}]))]
228
+ (is (= 3 (count (d/q {:query '[:find ?e :where [?e :name _]]
229
+ :args [db]
230
+ :limit -1}))))
231
+ (is (= 3 (count (d/q {:query '[:find ?e :where [?e :name _]]
232
+ :args [db]}))))
233
+ (is (= 3 (count (d/q {:query '[:find ?e :where [?e :name _]]
234
+ :args [db]
235
+ :limit nil}))))
236
+ (is (= 3 (count (d/q {:query '[:find ?e :where [?e :name _]]
237
+ :args [db]
238
+ :offset -1}))))
239
+ (is (= 3 (count (d/q {:query '[:find ?e :where [?e :name _]]
240
+ :args [db]
241
+ :offset nil}))))
242
+ (is (= 1 (count (d/q {:query '[:find ?e :where [?e :name _]]
243
+ :args [db]
244
+ :offset 1
245
+ :limit 1}))))
246
+ (is (= 2 (count (d/q {:query '[:find ?e :where [?e :name _]]
247
+ :args [db]
248
+ :limit 2}))))
249
+ (is (= 2 (count (d/q {:query '[:find ?e :where [?e :name _]]
250
+ :args [db]
251
+ :offset 1
252
+ :limit 2}))))
253
+ (is (= 1 (count (d/q {:query '[:find ?e :where [?e :name _]]
254
+ :args [db]
255
+ :offset 2
256
+ :limit 2}))))
257
+ (is (not (= (d/q {:query '[:find ?e :where [?e :name _]]
258
+ :args [db]
259
+ :limit 2})
260
+ (d/q {:query '[:find ?e :where [?e :name _]]
261
+ :args [db]
262
+ :offset 1
263
+ :limit 2}))))
264
+ (is (= (d/q {:query '[:find ?e :where [?e :name _]]
265
+ :args [db]
266
+ :offset 4})
267
+ #{}))
268
+ (is (= (d/q {:query '[:find ?e :where [?e :name _]]
269
+ :args [db]
270
+ :offset 10
271
+ :limit 5})
272
+ #{}))
273
+ (is (= (d/q {:query '[:find ?e :where [?e :name _]]
274
+ :args [db]
275
+ :offset 1
276
+ :limit 0})
277
+ #{}))))
278
+
279
+ (deftest test-return-maps
280
+ (let [db (-> (db/empty-db)
281
+ (d/db-with [{:db/id 1, :name "Alice", :age 15}
282
+ {:db/id 2, :name "Bob", :age 37}
283
+ {:db/id 3, :name "Charlie", :age 37}]))]
284
+ (testing "returns map"
285
+ (is (map? (first (d/q {:query '[:find ?e :keys name :where [?e :name _]]
286
+ :args [db]})))))
287
+ (testing "returns set without return-map"
288
+ (is (= #{["Charlie"] ["Alice"] ["Bob"]}
289
+ (d/q {:query '[:find ?name :where [_ :name ?name]]
290
+ :args [db]}))))
291
+ (testing "returns map with key return-map"
292
+ (is (= [{:foo 3} {:foo 2} {:foo 1}]
293
+ (d/q {:query '[:find ?e :keys foo :where [?e :name _]]
294
+ :args [db]}))))
295
+ (testing "returns map with string return-map"
296
+ (is (= [{"foo" "Charlie"} {"foo" "Alice"} {"foo" "Bob"}]
297
+ (d/q {:query '[:find ?name :strs foo :where [?e :name ?name]]
298
+ :args [db]}))))
299
+ (testing "return map with keys using multiple find vars"
300
+ (is (= #{["Bob" {:age 37 :db/id 2}]
301
+ ["Charlie" {:age 37 :db/id 3}]
302
+ ["Alice" {:age 15 :db/id 1}]}
303
+ (into #{} (d/q {:find '[?name (pull ?e ?p)]
304
+ :args [db '[:age :db/id]]
305
+ :in '[$ ?p]
306
+ :where '[[?e :name ?name]]})))))))
307
+
308
+ (deftest test-memoized-parse-query
309
+ (testing "no map return"
310
+ (is (= nil
311
+ (:qreturnmap (dq/memoized-parse-query '[:find ?e :where [?e :name]])))))
312
+ (testing "key map return"
313
+ (is (= '#datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}
314
+ (:qreturnmaps (dq/memoized-parse-query '[:find ?e :keys foo :where [?e :name]])))))
315
+ (testing "key map return multiple"
316
+ (is (= '#datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo}, #datalog.parser.type.MappingKey{:mapping-key bar})}
317
+ (:qreturnmaps (dq/memoized-parse-query '[:find ?e ?f :keys foo bar :where [?e :name ?f]])))))
318
+ (testing "string map return multiple"
319
+ (is (= '#datalog.parser.type.ReturnMaps{:mapping-type :strs, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo}, #datalog.parser.type.MappingKey{:mapping-key bar})}
320
+ (:qreturnmaps (dq/memoized-parse-query '[:find ?e ?f :strs foo bar :where [?e :name ?f]])))))
321
+ (testing "symbol map return multiple"
322
+ (is (= '#datalog.parser.type.ReturnMaps{:mapping-type :syms, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo}, #datalog.parser.type.MappingKey{:mapping-key bar})}
323
+ (:qreturnmaps (dq/memoized-parse-query '[:find ?e ?f :syms foo bar :where [?e :name ?f]]))))))
324
+
325
+ (deftest test-convert-to-return-maps
326
+ (testing "converting keys"
327
+ (is (= [{:foo 3} {:foo 2} {:foo 1}]
328
+ (dq/convert-to-return-maps '#datalog.parser.type.ReturnMaps{:mapping-type :keys,
329
+ :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}
330
+ #{[1] [2] [3]}))))
331
+ (testing "converting strs"
332
+ (is (= [{"foo" 3} {"foo" 2} {"foo" 1}]
333
+ (dq/convert-to-return-maps '#datalog.parser.type.ReturnMaps{:mapping-type :strs,
334
+ :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}
335
+ #{[1] [2] [3]}))))
336
+ (testing "converting syms"
337
+ (is (= [{'foo 3} {'foo 2} {'foo 1}]
338
+ (dq/convert-to-return-maps '#datalog.parser.type.ReturnMaps{:mapping-type :syms,
339
+ :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}
340
+ #{[1] [2] [3]}))))
341
+ (testing "converting keys"
342
+ (is (= '[{:foo 1, :bar 11, :baz "Ivan"} {:foo 3, :bar 21, :baz "Petr"} {:foo 3, :bar 31, :baz "Ivan"}]
343
+ (dq/convert-to-return-maps '#datalog.parser.type.ReturnMaps{:mapping-type :keys,
344
+ :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo},
345
+ #datalog.parser.type.MappingKey{:mapping-key bar},
346
+ #datalog.parser.type.MappingKey{:mapping-key baz})}
347
+ #{[1 11 "Ivan"]
348
+ [3 31 "Ivan"]
349
+ [3 21 "Petr"]}))))
350
+ (testing "converting strs"
351
+ (is (= '[{"foo" 1, "bar" 11, "baz" "Ivan"} {"foo" 3, "bar" 21, "baz" "Petr"} {"foo" 3, "bar" 31, "baz" "Ivan"}]
352
+ (dq/convert-to-return-maps '#datalog.parser.type.ReturnMaps{:mapping-type :strs,
353
+ :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo},
354
+ #datalog.parser.type.MappingKey{:mapping-key bar},
355
+ #datalog.parser.type.MappingKey{:mapping-key baz})}
356
+ #{[1 11 "Ivan"]
357
+ [3 31 "Ivan"]
358
+ [3 21 "Petr"]}))))
359
+ (testing "converting syms"
360
+ (is (= '[{foo 1, bar 11, baz "Ivan"} {foo 3, bar 21, baz "Petr"} {foo 3, bar 31, baz "Ivan"}]
361
+ (dq/convert-to-return-maps '#datalog.parser.type.ReturnMaps{:mapping-type :syms,
362
+ :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo},
363
+ #datalog.parser.type.MappingKey{:mapping-key bar},
364
+ #datalog.parser.type.MappingKey{:mapping-key baz})}
365
+ #{[1 11 "Ivan"]
366
+ [3 31 "Ivan"]
367
+ [3 21 "Petr"]})))))
368
+
369
+ #_(deftest test-clause-order-invariance ;; TODO: this is what should happen after rewirite of query engine
370
+ (let [db (-> (db/empty-db)
371
+ (d/db-with [{:db/id 1, :name "Ivan", :age 15}
372
+ {:db/id 2, :name "Petr", :age 37}
373
+ {:db/id 3, :name "Ivan", :age 37}
374
+ {:db/id 4, :age 15}]))]
375
+ (testing "Clause order does not matter for predicates"
376
+ (is (= (d/q {:query '{:find [?e]
377
+ :where [[?e :age ?age]
378
+ [(= ?age 37)]]}
379
+ :args [db]})
380
+ #{[2] [3]}))
381
+ (is (= (d/q {:query '{:find [?e]
382
+ :where [[(= ?age 37)]
383
+ [?e :age ?age]]}
384
+ :args [db]})
385
+ #{[2] [3]})))))
386
+
387
+ (deftest test-clause-order
388
+ (let [db (-> (db/empty-db)
389
+ (d/db-with [{:db/id 1, :name "Ivan", :age 15}
390
+ {:db/id 2, :name "Petr", :age 37}
391
+ {:db/id 3, :name "Ivan", :age 37}
392
+ {:db/id 4, :age 15}]))]
393
+ (testing "Predicate clause before variable binding throws exception"
394
+ (is (= (d/q {:query '{:find [?e]
395
+ :where [[?e :age ?age]
396
+ [(= ?age 37)]]}
397
+ :args [db]})
398
+ #{[2] [3]}))
399
+ (is (thrown-with-msg? Throwable #"Insufficient bindings: #\{\?age\} not bound"
400
+ (d/q {:query '{:find [?e]
401
+ :where [[(= ?age 37)]
402
+ [?e :age ?age]]}
403
+ :args [db]}))))))
404
+
405
+ (deftest test-zeros-in-pattern
406
+ (let [cfg {:store {:backend :memory
407
+ :id #uuid "b0000000-0000-0000-0000-00000000000b"}
408
+ :schema-flexibility :write
409
+ :attribute-refs? false}
410
+ conn (do
411
+ (d/delete-database cfg)
412
+ (d/create-database cfg)
413
+ (d/connect cfg))]
414
+ (d/transact conn [{:db/ident :version/id
415
+ :db/valueType :db.type/long
416
+ :db/cardinality :db.cardinality/one
417
+ :db/unique :db.unique/identity}
418
+ {:version/id 0}
419
+ {:version/id 1}])
420
+ (is (= 1
421
+ (count (d/q '[:find ?t :in $ :where
422
+ [?t :version/id 0]]
423
+ @conn))))
424
+ (d/release conn)))
425
+
426
+ ;; https://github.com/replikativ/datahike/issues/471
427
+ (deftest keyword-keys-test
428
+ (let [schema [{:db/ident :name
429
+ :db/cardinality :db.cardinality/one
430
+ :db/index true
431
+ :db/unique :db.unique/identity
432
+ :db/valueType :db.type/string}
433
+ {:db/ident :parents
434
+ :db/cardinality :db.cardinality/many
435
+ :db/valueType :db.type/ref}
436
+ {:db/ident :age
437
+ :db/cardinality :db.cardinality/one
438
+ :db/valueType :db.type/long}]
439
+ cfg {:store {:backend :memory
440
+ :id #uuid "c0000000-0000-0000-0000-00000000000c"}
441
+ :schema-flexibility :write
442
+ :attribute-refs? true}
443
+ conn (utils/setup-db cfg)]
444
+ (d/transact conn schema)
445
+ (d/transact conn [{:name "Alice"
446
+ :age 25}
447
+ {:name "Bob"
448
+ :age 35}])
449
+ (d/transact conn [{:name "Charlie"
450
+ :age 5
451
+ :parents [[:name "Alice"] [:name "Bob"]]}])
452
+ (let [db @conn
453
+ keyword-result (into #{} (d/q '[:find ?n ?a
454
+ :keys :name :age
455
+ :where
456
+ [?e :name ?n]
457
+ [?e :age ?a]]
458
+ db))
459
+ symbol-result (into #{} (d/q '[:find ?n ?a
460
+ :keys name age
461
+ :where
462
+ [?e :name ?n]
463
+ [?e :age ?a]]
464
+ db))]
465
+ (testing "keyword result keys"
466
+ (is (= #{{:name "Alice" :age 25}
467
+ {:name "Charlie" :age 5}
468
+ {:name "Bob" :age 35}}
469
+ keyword-result)))
470
+ (testing "keyword equals symbol keys"
471
+ (is (= symbol-result
472
+ keyword-result))))
473
+ (d/release conn)))
474
+
475
+ (deftest test-normalize-q-input
476
+ (testing "query as vector"
477
+ (is (= {:query {:find '[?n]
478
+ :where '[[?e :name ?n]]}
479
+ :args :db}
480
+ (dq/normalize-q-input '[:find ?n
481
+ :where [?e :name ?n]]
482
+ :db))))
483
+
484
+ (testing "query in :query field"
485
+ (is (= {:query {:find '[?n]
486
+ :where '[[?e :name ?n]]}
487
+ :args [:db]}
488
+ (dq/normalize-q-input {:query '{:find [?n]
489
+ :where [[?e :name ?n]]}
490
+ :args [:db]}
491
+ [])))
492
+ (is (= {:query {:find '[?n], :where '[[?e :name ?n]]}
493
+ :args [:db]}
494
+ (dq/normalize-q-input {:query '{:find [?n]
495
+ :where [[?e :name ?n]]}}
496
+ [:db])))
497
+ (is (= {:query {:find '[?n], :where '[[?e :name ?n]]}
498
+ :args [:db]}
499
+ (dq/normalize-q-input {:query '{:find [?n]
500
+ :where [[?e :name ?n]]}
501
+ :args [:db]}
502
+ [:db2])))
503
+ (is (= {:query {:find '[?n]
504
+ :where '[[?e :name ?n]]}
505
+ :args [:db]
506
+ :limit 100
507
+ :offset 0}
508
+ (dq/normalize-q-input {:query '[:find ?n
509
+ :where [?e :name ?n]]
510
+ :offset 0
511
+ :limit 100
512
+ :args [:db]}
513
+ []))))
514
+
515
+ (testing "query in top-level map"
516
+ (is (= {:query {:find '[?e]
517
+ :where '[[?e :name ?value]]}
518
+ :args []
519
+ :limit 100
520
+ :offset 0}
521
+ (dq/normalize-q-input {:find '[?e]
522
+ :where '[[?e :name ?value]]
523
+ :offset 0
524
+ :limit 100} [])))))
525
+
526
+ (deftest test-distinct-tuples
527
+ (is (= [[3 4]] (dq/distinct-tuples [[3 4]])))
528
+ (let [arrays [(object-array [:a]) (object-array [:a])]
529
+ result (dq/distinct-tuples arrays)
530
+ object-array-type (type (object-array []))]
531
+ (is (every? #(= object-array-type (type %)) result))
532
+ (is (= [[:a]] (map vec result)))
533
+
534
+ ;; This is just to highlight the difference w.r.t. `distinct`:
535
+ (is (= [[:a] [:a]] (map vec (distinct arrays)))))
536
+ (is (= [[3 4]] (dq/distinct-tuples [[3 4] [3 4]])))
537
+ (is (= [[3 4]] (dq/distinct-tuples [[3 4]
538
+ (long-array [3 4])])))
539
+ (is (= [[3 4] [9 7]] (dq/distinct-tuples [[3 4] [9 7] [3 4]]))))
540
+
541
+ ;; A good one
542
+ (def ex1 '{:source {:max-tx 536926163},
543
+ :pattern1 [?r1 79 ?oc],
544
+ :context
545
+ {:rels
546
+ [{:attrs {?oc 0},
547
+ :tuples
548
+ [[5289]
549
+ [5294]
550
+ [5299]
551
+ [5304]
552
+ [5307]
553
+ [5310]
554
+ [5313]
555
+ [5317]
556
+ [5322]
557
+ [5325]],
558
+ :tuple-count 3654}
559
+ {:attrs {?__auto__1 0}, :tuples [], :tuple-count 0}],
560
+ :consts {?__auto__1 "narrow-match"}},
561
+ :clause [?r1 :relation/concept-1 ?oc],
562
+ :constrained-patterns
563
+ [[?r1 79 5289]
564
+ [?r1 79 5294]
565
+ [?r1 79 5299]
566
+ [?r1 79 5304]
567
+ [?r1 79 5307]
568
+ [?r1 79 5310]
569
+ [?r1 79 5313]
570
+ [?r1 79 5317]
571
+ [?r1 79 5322]
572
+ [?r1 79 5325]],
573
+ :constrained-pattern-count 3654})
574
+
575
+ (deftest test-new-search-strategy
576
+ (let [;; pattern1 = [?r1 79 ?oc]
577
+ {:keys [context pattern1]} ex1
578
+ rels (vec (:rels context))
579
+ bsm (dq/bound-symbol-map rels)
580
+
581
+ clean-pattern (dq/replace-unbound-symbols-by-nil bsm pattern1)
582
+
583
+ strategy0 [nil :substitute :substitute nil]
584
+ strategy1 [nil :substitute :filter nil]
585
+
586
+ subst-inds0 (dq/substitution-relation-indices
587
+ {:bsm bsm
588
+ :clean-pattern clean-pattern
589
+ :strategy-vec strategy0})
590
+ subst-inds1 (dq/substitution-relation-indices
591
+ {:bsm bsm
592
+ :clean-pattern clean-pattern
593
+ :strategy-vec strategy1})
594
+ filt-inds0 (dq/filtering-relation-indices
595
+ {:bsm bsm
596
+ :clean-pattern clean-pattern
597
+ :strategy-vec strategy0}
598
+ subst-inds0)
599
+ filt-inds1 (dq/filtering-relation-indices
600
+ {:bsm bsm
601
+ :clean-pattern clean-pattern
602
+ :strategy-vec strategy1}
603
+ subst-inds1)]
604
+ (is (seq rels))
605
+ (is (= '{?oc {:relation-index 0, :tuple-element-index 0},
606
+ ?__auto__1 {:relation-index 1, :tuple-element-index 0}}
607
+ bsm))
608
+ (is (= #{0} subst-inds0))
609
+ (is (= #{} subst-inds1))
610
+ (is (= #{} filt-inds0))
611
+ (is (= #{0} filt-inds1))))
612
+
613
+ (defn pack6 [step]
614
+ (fn
615
+ ([] (step))
616
+ ([dst] (step dst))
617
+ ([dst e a v tx added? filt]
618
+ (step dst [[e a v tx added?] filt]))))
619
+
620
+ (deftest test-substitution-plan
621
+ (let [-pattern1 '[?w ?x ?y]
622
+ context '{:rels [{:attrs {?x 0
623
+ ?y 1}
624
+ :tuples [[1 2]
625
+ [3 4]
626
+ [3 5]
627
+ [5 6]]}
628
+ {:attrs {?z 0}
629
+ :tuples [[9] [10] [11]]}]}
630
+ rels (vec (:rels context))
631
+ bsm (dq/bound-symbol-map rels)
632
+ clean-pattern (dq/replace-unbound-symbols-by-nil bsm -pattern1)
633
+ strategy [nil :substitute :filter nil]
634
+ subst-inds (dq/substitution-relation-indices
635
+ {:bsm bsm
636
+ :clean-pattern clean-pattern
637
+ :strategy-vec strategy
638
+ :rels rels})
639
+ filt-inds (dq/filtering-relation-indices
640
+ {:bsm bsm
641
+ :clean-pattern clean-pattern
642
+ :strategy-vec strategy
643
+ :rels rels}
644
+ subst-inds)
645
+ [init-coll subst-xform] (dq/initialization-and-substitution-xform
646
+ {:bsm bsm
647
+ :clean-pattern clean-pattern
648
+ :strategy-vec strategy
649
+ :rels rels}
650
+ subst-inds)
651
+
652
+ result (into []
653
+ (comp dq/unpack6
654
+ subst-xform
655
+ pack6)
656
+ init-coll)
657
+ [[_ p0] [_ p1] [_ p2] [_ p3]] result]
658
+ (is (= #{0} subst-inds))
659
+ (is (= #{} filt-inds))
660
+ (is (= {'?x {:relation-index 0 :tuple-element-index 0}
661
+ '?y {:relation-index 0 :tuple-element-index 1}
662
+ '?z {:relation-index 1 :tuple-element-index 0}}
663
+ bsm))
664
+ (is (= [[nil 1 nil nil nil]
665
+ [nil 3 nil nil nil]
666
+ [nil 3 nil nil nil]
667
+ [nil 5 nil nil nil]] (map first result)))
668
+ (is (p0 [1 2 2]))
669
+ (is (not (p0 [1 2 3])))
670
+ (is (p1 [1 2 4]))
671
+ (is (p2 [1 2 5]))
672
+ (is (not (p1 [1 2 6])))
673
+ (is (p3 [1 2 6]))
674
+ (is (not (p3 [1 2 5])))))
675
+
676
+ (deftest test-index-feature-extractor
677
+ (let [e (dq/index-feature-extractor [1] true)]
678
+ (is (= 3 (e [119 3])))
679
+ (is (= 4 (e [120 4 9 3]))))
680
+ (let [e (dq/index-feature-extractor [1 0] true)]
681
+ (is (= [3 119] (e [119 3])))
682
+ (is (= [4 120] (e [120 4 9 3]))))
683
+ (let [e (dq/index-feature-extractor [] true)]
684
+ (is (nil? (e [119 3])))
685
+ (is (nil? (e [120 4 9 3]))))
686
+ (is (nil? (dq/index-feature-extractor [] false))))
687
+
688
+ (deftest test-filtering-plan
689
+ (let [pattern1 '[?w ?x ?y]
690
+ context '{:rels [{:attrs {?x 0}
691
+ :tuples [[1]
692
+ [3]
693
+ [5]]}
694
+ {:attrs {?y 0}
695
+ :tuples [[2] [4] [6]]}
696
+ {:attrs {?z 0}
697
+ :tuples [[9] [10] [11]]}]}
698
+
699
+ rels (vec (:rels context))
700
+ bsm (dq/bound-symbol-map rels)
701
+ clean-pattern (dq/replace-unbound-symbols-by-nil bsm pattern1)
702
+ strategy [nil :substitute :filter nil]
703
+ subst-inds (dq/substitution-relation-indices
704
+ {:bsm bsm
705
+ :clean-pattern pattern1
706
+ :strategy-vec strategy})
707
+ filt-inds (dq/filtering-relation-indices
708
+ {:bsm bsm
709
+ :clean-pattern clean-pattern
710
+ :strategy-vec strategy}
711
+ subst-inds)
712
+ [init-coll subst-xform] (dq/initialization-and-substitution-xform
713
+ {:bsm bsm
714
+ :clean-pattern clean-pattern
715
+ :strategy-vec strategy
716
+ :rels rels}
717
+ subst-inds)
718
+
719
+ subst-result (into []
720
+ (comp dq/unpack6
721
+ subst-xform
722
+ pack6)
723
+ init-coll)
724
+ [[_ p0]] subst-result]
725
+ (is (nil? p0))
726
+ (is (= '[nil ?x ?y nil nil] clean-pattern))
727
+ (is (= #{0} subst-inds))
728
+ (is (= #{1} filt-inds))
729
+ (is (= {'?x {:relation-index 0 :tuple-element-index 0}
730
+ '?y {:relation-index 1 :tuple-element-index 0}
731
+ '?z {:relation-index 2 :tuple-element-index 0}}
732
+ bsm))
733
+ (is (= '([nil 1 nil nil nil]
734
+ [nil 3 nil nil nil]
735
+ [nil 5 nil nil nil])
736
+ (map first subst-result)))))
737
+
738
+ (defn pcmp [x y]
739
+ (or (nil? x) (= x y)))
740
+
741
+ (defn mock-backend-fn [datoms]
742
+ (fn [e0 a0 v0 t0 added0]
743
+ (filter (fn [[e1 a1 v1 t1 added1]]
744
+ (and (pcmp e0 e1)
745
+ (pcmp a0 a1)
746
+ (pcmp v0 v1)
747
+ (pcmp t0 t1)
748
+ (pcmp added0 added1)))
749
+ datoms)))
750
+
751
+ (deftest test-full-lookup-pipeline
752
+ (let [pattern1 '[?x ?w ?y]
753
+ context '{:rels [{:attrs {?x 0}
754
+ :tuples [[1] [3] [5]]}
755
+ {:attrs {?y 0}
756
+ :tuples [[4] [5] [6]]}]}
757
+ strategy-vec [:substitute nil :filter nil]
758
+ rels (vec (:rels context))
759
+ bsm (dq/bound-symbol-map rels)
760
+ clean-pattern (dq/replace-unbound-symbols-by-nil bsm pattern1)
761
+ sfn (dq/search-batch-fn {:bsm bsm
762
+ :clean-pattern clean-pattern
763
+ :rels rels})
764
+ result (sfn strategy-vec
765
+ (mock-backend-fn [[0 :abc 5]
766
+ [5 :xyz 6]
767
+ [1 :k 4]
768
+ [5 :p 7]])
769
+ identity)]
770
+ (is (= #{[1 :k 4] [5 :xyz 6]} (set result)))))
771
+
772
+ (defn concept-id [index]
773
+ (let [s (format "%010d" index)]
774
+ (str (subs s 0 4) "_" (subs s 4 7) "_" (subs s 7 10))))
775
+
776
+ (defn temp-id [x]
777
+ (str "tmp-" x))
778
+
779
+ (defn make-forest
780
+ "This function constructs tx-data for a forest of concepts in a terminology,
781
+ for example a labor market terminology of occupations. The edges in the
782
+ forest point toward the root and are labeled `:concept/broader` because the
783
+ closer to the root we get, the broader the concept is. For instance, we could
784
+ have a concept for the occupation name 'Software Engineer' and an edge from
785
+ that concept pointing at a broader concept 'Occuptions in Computer Science'.
786
+
787
+ This function takes as input a concatenated list of pairs of `m` and `t`
788
+ on the form `[m1 t1 m2 t2 ... mN tN]` that specifies how many sub nodes
789
+ should be generated at each level from the root and the type. For example,
790
+ `[5 'ssyk-level-1' 3 'ssyk-level-2']` means that we will construct 5 trees
791
+ of root node type 'ssyk-level-1' and each one of them will have 3 children
792
+ of node type 'ssyk-level-2'."
793
+ ([tree-spec] (make-forest tree-spec 0))
794
+ ([[root-count & tree-spec] init-counter]
795
+ {:pre [(number? root-count)]}
796
+ (loop [[tos & stack] (repeat root-count [nil tree-spec])
797
+ tx-data []
798
+ counter init-counter
799
+ concept-map {}]
800
+ (if (nil? tos)
801
+ {:tx-data tx-data
802
+ :concept-map concept-map}
803
+ (let [[parent-id [this-type child-count & remaining-pairs]] tos
804
+ concept-id (concept-id counter)
805
+ tid (temp-id concept-id)
806
+ parent-tid (temp-id parent-id)]
807
+ (assert (or (nil? parent-id) (string? parent-id)))
808
+ (recur (into stack
809
+ (when (seq remaining-pairs)
810
+ (repeat child-count [concept-id remaining-pairs])))
811
+ (into tx-data
812
+ cat
813
+ [[[:db/add tid :concept/id concept-id]
814
+ [:db/add tid :concept/type this-type]]
815
+ (when parent-id
816
+ [[:db/add tid :concept/broader parent-tid]])])
817
+ (inc counter)
818
+ (cond-> concept-map
819
+ true (update concept-id
820
+ merge {:parent-id parent-id
821
+ :type this-type
822
+ :id concept-id})
823
+ parent-id (update-in [parent-id :child-ids] #(conj (or % []) concept-id)))))))))
824
+
825
+ (def schema [#:db{:ident :concept/id,
826
+ :valueType :db.type/string,
827
+ :cardinality :db.cardinality/one,
828
+ :doc "Unique identifier for concepts",
829
+ :unique :db.unique/identity}
830
+ #:db{:ident :concept/type,
831
+ :valueType :db.type/string,
832
+ :cardinality :db.cardinality/one,
833
+ :doc "The concepts main type"}
834
+ #:db {:ident :concept/broader
835
+ :valueType :db.type/ref
836
+ :cardinality :db.cardinality/one
837
+ :doc "A broader concept. NOTE: This the JobTech Taxonomy, every relation between two concepts has an entity with attributes :relation/concept-1, :relation-concept-2 and :relation/type."}])
838
+
839
+ (defn initialize-test-db0 []
840
+ (let [conn (utils/setup-db {:store {:backend :memory :id (random-uuid)}
841
+ :schema-flexibility :write
842
+ :attribute-refs? false
843
+ :keep-history? true})]
844
+ (d/transact conn {:tx-data schema})
845
+ conn))
846
+
847
+ (defn group-concepts-by-type [concept-map]
848
+ (let [groups (update-vals (group-by (comp :type val)
849
+ concept-map)
850
+ (fn [kv-pairs]
851
+ (mapv first kv-pairs)))]
852
+ (doseq [[k v] (sort-by val (update-vals groups count))]
853
+ (log/info k v))
854
+ (log/info "Total count:" (count concept-map))
855
+ groups))
856
+
857
+ (deftest synthetic-ssyk-tree-test
858
+
859
+ "In this test we construct a labor market taxonomy of occupations. Given
860
+ some concept ids, we look up broader concepts. The queries in this test will
861
+ include clauses with up to two unknown variables.
862
+
863
+ We perform two queries. In the first query, we only provide one input id and
864
+ look up concepts broader than that id.
865
+
866
+ In the second query, we provide two input ids."
867
+
868
+ (testing "Given some concepts, query concepts that are broader."
869
+ (let [conn (initialize-test-db0)
870
+ ssyk-data (make-forest
871
+ [3 "ssyk-level-1"
872
+ 5 "ssyk-level-2"
873
+ 30 "ssyk-level-3"
874
+ 5 "ssyk-level-4"
875
+ 2 "occupation-name"])
876
+ ssyk-concept-map (:concept-map ssyk-data)
877
+ _ (d/transact conn {:tx-data (:tx-data ssyk-data)})
878
+ concepts-per-type (group-concepts-by-type ssyk-concept-map)
879
+ ssyk-level-3-ids (concepts-per-type "ssyk-level-3")
880
+ expected-result-fn (fn [concept-ids]
881
+ (into #{}
882
+ (map (fn [cid]
883
+ {:from_id cid
884
+ :id (get-in ssyk-concept-map
885
+ [cid :parent-id])}))
886
+ concept-ids))
887
+
888
+ related-query '{:find [?from-id ?id],
889
+ :keys [from_id id],
890
+ :in [$ [?from-id ...]],
891
+ :where
892
+ [[?c :concept/id ?from-id]
893
+ [?c :concept/broader ?related-c]
894
+ [?related-c :concept/id ?id]]}]
895
+ (testing "Query for 1 input concept id."
896
+ (let [input-concept-id (first ssyk-level-3-ids)
897
+ _ (is (string? input-concept-id))
898
+ result (d/q {:query related-query
899
+ :args [(d/db conn) #{input-concept-id}]})]
900
+ (is (= (expected-result-fn [input-concept-id])
901
+ (set result)))))
902
+ (testing "Query for 2 input ids."
903
+ (let [input-ids (set (take 2 ssyk-level-3-ids))
904
+ result (d/q {:query related-query
905
+ :args [(d/db conn) input-ids]})]
906
+ (is (= (expected-result-fn input-ids)
907
+ (set result))))))))
908
+
909
+ (deftest synthetic-ssyk-tree-test2
910
+
911
+ "We construct a forest of four trees with each tree having 2000 subnodes each. We then pick the ids
912
+ of two of the root nodes of the trees and the ids from three of the children of one
913
+ trees. Then we we query for all (parent,child) pairs."
914
+
915
+ (let [conn (initialize-test-db0)
916
+ ssyk-data (make-forest [4 "ssyk-level-1" 2000 "ssyk-level-2"])
917
+ ssyk-concept-map (:concept-map ssyk-data)
918
+ _ (d/transact conn {:tx-data (:tx-data ssyk-data)})
919
+ concepts-per-type (group-concepts-by-type ssyk-concept-map)]
920
+ (testing "Query (parent,child) pairs from a *small* set of possible combinations in a labour market taxonomy."
921
+ (let [parent-ids (take 2 (concepts-per-type "ssyk-level-1"))
922
+ parent-id (first parent-ids)
923
+ child-ids (take 3 (get-in ssyk-concept-map [parent-id :child-ids]))
924
+ _ (is (= 2 (count parent-ids)))
925
+ _ (is (= 3 (count child-ids)))
926
+ result (d/q {:query '{:find [?parent-id ?child-id]
927
+ :keys [parent_id child_id]
928
+ :in [$
929
+ [?parent-id ...]
930
+ [?child-id ...]],
931
+ :where
932
+ [[?pc :concept/id ?parent-id]
933
+ [?cc :concept/id ?child-id]
934
+ [?cc :concept/broader ?pc]]}
935
+ :args [(d/db conn)
936
+ parent-ids
937
+ child-ids]})
938
+ expected-result (into #{}
939
+ (map (fn [child-id] {:parent_id parent-id :child_id child-id}))
940
+ child-ids)]
941
+ (is (= 3 (count expected-result)))
942
+ (is (= (set result)
943
+ expected-result))))))
944
+
945
+ (deftest synthetic-ssyk-tree-test3
946
+ "We construct a labor market taxonomy of 200 trees where each root node has one child. Then
947
+ we query all (parent, child) pairs."
948
+
949
+ (let [conn (initialize-test-db0)
950
+ ssyk-data (make-forest [200 "ssyk-level-1" 1 "ssyk-level-2"])
951
+ ssyk-concept-map (:concept-map ssyk-data)
952
+ _ (d/transact conn {:tx-data (:tx-data ssyk-data)})
953
+
954
+ ;; Adding some extra data here also makes `expand-once` perform better than
955
+ ;; `identity` and `select-simple`. If we remove these two lines, then
956
+ ;; `expand-once`, `identity` and `select-simple` will perform roughly the same.
957
+ extra-data (make-forest [100 "skill-headline" 100 "skill"] (count ssyk-concept-map))
958
+ _ (d/transact conn {:tx-data (:tx-data extra-data)})
959
+
960
+ _concepts-per-type (group-concepts-by-type ssyk-concept-map)]
961
+ (testing "Query (parent,child) pairs from a *large* set of possible combinations in a labour market taxonomy."
962
+ (let [result (d/q {:query '{:find [?parent-id ?child-id]
963
+ :keys [parent_id child_id]
964
+ :in [$ %],
965
+ :where
966
+ [[?pc :concept/type "ssyk-level-1"]
967
+ [?cc :concept/type "ssyk-level-2"]
968
+ [?cc :concept/broader ?pc]
969
+ [?pc :concept/id ?parent-id]
970
+ [?cc :concept/id ?child-id]]}
971
+ :args [(d/db conn)]})
972
+ expected-result (into #{}
973
+ (keep (fn [[child-id {:keys [parent-id]}]]
974
+ (when parent-id
975
+ {:child_id child-id :parent_id parent-id})))
976
+ ssyk-concept-map)]
977
+ (is (= 200 (count expected-result)))
978
+ (is (= expected-result
979
+ (set result)))))))
980
+
981
+ (deftest basic-index-selector-test
982
+ (let [f (dq/basic-index-selector 5)]
983
+ (is (= [10 7] ((f [1 3]) [9 10 4 7 1234])))
984
+ (is (= [7 10] ((f [3 1]) [9 10 4 7 1234])))))