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,1945 @@
1
+ (ns ^:no-doc datahike.query
2
+ #?(:cljs (:require-macros [datahike.query :refer [basic-index-selector make-vec-lookup-ref-replacer some-of substitution-expansion]]))
3
+ (:require
4
+ [#?(:cljs cljs.reader :clj clojure.edn) :as edn]
5
+ [clojure.set :as set]
6
+ #?(:clj [clojure.string :as str])
7
+ [clojure.walk :as walk]
8
+ [datahike.datom :as datom]
9
+ [datahike.db.interface :as dbi]
10
+ [datahike.db.utils :as dbu]
11
+ [datahike.array :refer [wrap-comparable]]
12
+ [datahike.impl.entity :as de]
13
+ [datahike.lru]
14
+ [datahike.pull-api :as dpa]
15
+ [datahike.query-stats :as dqs]
16
+ [datahike.tools :as dt]
17
+ [datalog.parser :refer [parse]]
18
+ [datalog.parser.impl :as dpi]
19
+ [datalog.parser.impl.proto :as dpip]
20
+ [datalog.parser.pull :as dpp]
21
+ #?(:cljs [datalog.parser.type :refer [Aggregate BindColl BindIgnore BindScalar BindTuple Constant
22
+ FindColl FindRel FindScalar FindTuple PlainSymbol Pull
23
+ RulesVar SrcVar Variable]])
24
+ [org.replikativ.persistent-sorted-set.arrays :as da]
25
+ [taoensso.timbre :as log])
26
+ (:refer-clojure :exclude [seqable?])
27
+
28
+ #?(:clj (:import [clojure.lang Reflector Seqable]
29
+ [datahike.datom Datom]
30
+ [datalog.parser.type Aggregate BindColl BindIgnore BindScalar BindTuple Constant
31
+ FindColl FindRel FindScalar FindTuple PlainSymbol Pull
32
+ RulesVar SrcVar Variable]
33
+ [java.lang.reflect Method]
34
+ [java.util Date Map HashSet HashSet])))
35
+
36
+ #?(:clj (set! *warn-on-reflection* true))
37
+
38
+ ;; ----------------------------------------------------------------------------
39
+
40
+ (def ^:const lru-cache-size 100)
41
+
42
+ (declare -collect -resolve-clause resolve-clause raw-q)
43
+
44
+ ;; Records
45
+
46
+ (defrecord Context [rels sources rules consts settings])
47
+ (defrecord StatContext [rels sources rules consts stats settings])
48
+
49
+ ;; attrs:
50
+ ;; {?e 0, ?v 1} or {?e2 "a", ?age "v"}
51
+ ;; tuples:
52
+ ;; [ #js [1 "Ivan" 5 14] ... ]
53
+ ;; or [ (Datom. 2 "Oleg" 1 55) ... ]
54
+ (defrecord Relation [attrs tuples])
55
+
56
+ ;; Main functions
57
+
58
+ (defn normalize-q-input
59
+ "Turns input to q into a map with :query and :args fields.
60
+ Also normalizes the query into a map representation."
61
+ [query-input arg-inputs]
62
+ (let [query (-> query-input
63
+ (#(or (and (map? %) (:query %)) %))
64
+ (#(if (string? %) (edn/read-string %) %))
65
+ (#(if (= 'quote (first %)) (second %) %))
66
+ (#(if (sequential? %) (dpi/query->map %) %)))
67
+ args (if (and (map? query-input) (contains? query-input :args))
68
+ (do (when (seq arg-inputs)
69
+ (log/warn (str "Query-map '" query "' already defines query input."
70
+ " Additional arguments to q will be ignored!")))
71
+ (:args query-input))
72
+ arg-inputs)
73
+ extra-ks [:offset :limit :stats? :settings]]
74
+ (cond-> {:query (apply dissoc query extra-ks)
75
+ :args args}
76
+ (map? query-input)
77
+ (merge (select-keys query-input extra-ks)))))
78
+
79
+ (defn q [query & inputs]
80
+ (raw-q (normalize-q-input query inputs)))
81
+
82
+ (defn query-stats [query & inputs]
83
+ (-> query
84
+ (normalize-q-input inputs)
85
+ (assoc :stats? true)
86
+ q))
87
+
88
+ ;; Utilities
89
+
90
+ (defn distinct-tuples
91
+ "Remove duplicates just like `distinct` but with the difference that it only works on values on which `vec` can be applied and two different objects are considered equal if and only if their results after `vec` has been applied are equal. This means that two different Java arrays are considered equal if and only if their elements are equal."
92
+ ([tuples]
93
+ (into [] (distinct-tuples) tuples))
94
+ ([]
95
+ (let [step ((distinct) (fn [_ _] true))]
96
+ (filter #(step false (vec %))))))
97
+
98
+ (defn seqable?
99
+ #?@(:clj [^Boolean [x]]
100
+ :cljs [^boolean [x]])
101
+ (and (not (string? x))
102
+ #?(:cljs (or (cljs.core/seqable? x)
103
+ (da/array? x))
104
+ :clj (or (seq? x)
105
+ (instance? Seqable x)
106
+ (nil? x)
107
+ (instance? Iterable x)
108
+ (da/array? x)
109
+ (instance? Map x)))))
110
+
111
+ (defn intersect-keys [attrs1 attrs2]
112
+ (set/intersection (set (keys attrs1))
113
+ (set (keys attrs2))))
114
+
115
+ (defn concatv [& xs]
116
+ (into [] cat xs))
117
+
118
+ (defn same-keys? [a b]
119
+ (and (= (count a) (count b))
120
+ (every? #(contains? b %) (keys a))
121
+ (every? #(contains? b %) (keys a))))
122
+
123
+ (defn- looks-like? [pattern form]
124
+ (cond
125
+ (= '_ pattern)
126
+ true
127
+ (= '[*] pattern)
128
+ (sequential? form)
129
+ (symbol? pattern)
130
+ (= form pattern)
131
+ (sequential? pattern)
132
+ (if (= (last pattern) '*)
133
+ (and (sequential? form)
134
+ (every? (fn [[pattern-el form-el]] (looks-like? pattern-el form-el))
135
+ (map vector (butlast pattern) form)))
136
+ (and (sequential? form)
137
+ (= (count form) (count pattern))
138
+ (every? (fn [[pattern-el form-el]] (looks-like? pattern-el form-el))
139
+ (map vector pattern form))))
140
+ :else ;; (predicate? pattern)
141
+ (pattern form)))
142
+
143
+ (defn source? [sym]
144
+ (and (symbol? sym)
145
+ (= \$ (first (name sym)))))
146
+
147
+ (defn free-var? [sym]
148
+ (and (symbol? sym)
149
+ (= \? (first (name sym)))))
150
+
151
+ (defn attr? [form]
152
+ (or (keyword? form) (string? form)))
153
+
154
+ (defn lookup-ref? [form]
155
+ ;; Using looks-like? here is quite inefficient.
156
+ (and (vector? form)
157
+ (= 2 (count form))
158
+ (attr? (first form))))
159
+
160
+ (defn entid? [x] ;; See `dbu/entid for all forms that are accepted
161
+ (or (attr? x)
162
+ (lookup-ref? x)
163
+ (dbu/numeric-entid? x)
164
+ (keyword? x)))
165
+
166
+ ;; Relation algebra
167
+ (defn join-tuples [t1 #?(:cljs idxs1
168
+ :clj ^{:tag "[[Ljava.lang.Object;"} idxs1)
169
+ t2 #?(:cljs idxs2
170
+ :clj ^{:tag "[[Ljava.lang.Object;"} idxs2)]
171
+ (let [l1 (alength idxs1)
172
+ l2 (alength idxs2)
173
+ res (da/make-array (+ l1 l2))]
174
+ (dotimes [i l1]
175
+ (aset res i (#?(:cljs da/aget :clj get) t1 (aget idxs1 i)))) ;; FIXME aget
176
+ (dotimes [i l2]
177
+ (aset res (+ l1 i) (#?(:cljs da/aget :clj get) t2 (aget idxs2 i)))) ;; FIXME aget
178
+ res))
179
+
180
+ (defn sum-rel [a b]
181
+ (let [{attrs-a :attrs, tuples-a :tuples} a
182
+ {attrs-b :attrs, tuples-b :tuples} b]
183
+ (cond
184
+ (= attrs-a attrs-b)
185
+ (Relation. attrs-a (into (vec tuples-a) tuples-b))
186
+
187
+ (not (same-keys? attrs-a attrs-b))
188
+ (dt/raise "Can't sum relations with different attrs: " attrs-a " and " attrs-b
189
+ {:error :query/where})
190
+
191
+ (every? number? (vals attrs-a)) ;; can’t conj into BTSetIter
192
+ (let [idxb->idxa (vec (for [[sym idx-b] attrs-b]
193
+ [idx-b (attrs-a sym)]))
194
+ tlen (->> (vals attrs-a) (reduce max) (inc))
195
+ tuples' (persistent!
196
+ (reduce
197
+ (fn [acc tuple-b]
198
+ (let [tuple' (da/make-array tlen)]
199
+ (doseq [[idx-b idx-a] idxb->idxa]
200
+ (aset tuple' idx-a (#?(:cljs da/aget :clj get) tuple-b idx-b)))
201
+ (conj! acc tuple')))
202
+ (transient (vec tuples-a))
203
+ tuples-b))]
204
+ (Relation. attrs-a tuples'))
205
+
206
+ :else
207
+ (let [all-attrs (zipmap (keys (merge attrs-a attrs-b)) (range))]
208
+ (-> (Relation. all-attrs [])
209
+ (sum-rel a)
210
+ (sum-rel b))))))
211
+
212
+ (defn simplify-rel [rel]
213
+ (Relation. (:attrs rel) (distinct-tuples (:tuples rel))))
214
+
215
+ (defn prod-rel
216
+ ([] (Relation. {} [(da/make-array 0)]))
217
+ ([rel1 rel2]
218
+ (let [attrs1 (keys (:attrs rel1))
219
+ attrs2 (keys (:attrs rel2))
220
+ idxs1 (to-array (map (:attrs rel1) attrs1))
221
+ idxs2 (to-array (map (:attrs rel2) attrs2))]
222
+ (Relation.
223
+ (zipmap (concat attrs1 attrs2) (range))
224
+ (persistent!
225
+ (reduce
226
+ (fn [acc t1]
227
+ (reduce (fn [acc t2]
228
+ (conj! acc (join-tuples t1 idxs1 t2 idxs2)))
229
+ acc (:tuples rel2)))
230
+ (transient []) (:tuples rel1)))))))
231
+
232
+ ;; built-ins
233
+
234
+ (defn- translate-for [db a]
235
+ (if (and (-> db dbi/-config :attribute-refs?) (keyword? a))
236
+ (dbi/-ref-for db a)
237
+ a))
238
+
239
+ (defn- -differ? [& xs]
240
+ (let [l (count xs)]
241
+ (not= (take (/ l 2) xs) (drop (/ l 2) xs))))
242
+
243
+ (defn- -get-else
244
+ [db e a else-val]
245
+ (when (nil? else-val)
246
+ (dt/raise "get-else: nil default value is not supported" {:error :query/where}))
247
+ (if-some [datom (first (dbi/search db [e (translate-for db a)]))]
248
+ (:v datom)
249
+ else-val))
250
+
251
+ (defn- -get-some
252
+ [db e & as]
253
+ (reduce
254
+ (fn [_ a]
255
+ (when-some [datom (first (dbi/search db [e (translate-for db a)]))]
256
+ (let [a-ident (if (keyword? (:a datom))
257
+ (:a datom)
258
+ (dbi/-ident-for db (:a datom)))]
259
+ (reduced [a-ident (:v datom)]))))
260
+ nil
261
+ as))
262
+
263
+ (defn- -missing?
264
+ [db e a]
265
+ (nil? (get (de/entity db e) a)))
266
+
267
+ (defn- and-fn [& args]
268
+ (reduce (fn [_a b]
269
+ (if b b (reduced b))) true args))
270
+
271
+ (defn- or-fn [& args]
272
+ (reduce (fn [_a b]
273
+ (if b (reduced b) b)) nil args))
274
+
275
+ (defprotocol CollectionOrder
276
+ (-strictly-decreasing? [x more])
277
+ (-decreasing? [x more])
278
+ (-strictly-increasing? [x more])
279
+ (-increasing? [x more]))
280
+
281
+ (extend-protocol CollectionOrder
282
+
283
+ #?(:clj Number :cljs number)
284
+ (-strictly-decreasing? [x more] (apply < x more))
285
+ (-decreasing? [x more] (apply <= x more))
286
+ (-strictly-increasing? [x more] (apply > x more))
287
+ (-increasing? [x more] (apply >= x more))
288
+
289
+ #?(:clj Date :cljs js/Date)
290
+ (-strictly-decreasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.before ^Date d1 ^Date d2)
291
+ res
292
+ (reduced false)))
293
+ true (map vector (cons x more) more))
294
+ :cljs (apply < x more)))
295
+ (-decreasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.after ^Date d1 ^Date d2)
296
+ (reduced false)
297
+ res))
298
+ true (map vector (cons x more) more))
299
+ :cljs (apply <= x more)))
300
+ (-strictly-increasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.after ^Date d1 ^Date d2)
301
+ res
302
+ (reduced false)))
303
+ true (map vector (cons x more) more))
304
+ :cljs (apply > x more)))
305
+ (-increasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.before ^Date d1 ^Date d2)
306
+ (reduced false)
307
+ res))
308
+ true (map vector (cons x more) more))
309
+ :cljs (apply >= x more)))
310
+
311
+ #?(:clj Object :cljs object) ;; default
312
+ (-strictly-decreasing? [x more] (reduce (fn [res [v1 s2]] (if (neg? (compare v1 s2))
313
+ res
314
+ (reduced false)))
315
+ true (map vector (cons x more) more)))
316
+
317
+ (-decreasing? [x more] (reduce (fn [res [v1 v2]] (if (pos? (compare v1 v2))
318
+ (reduced false)
319
+ res))
320
+ true (map vector (cons x more) more)))
321
+
322
+ (-strictly-increasing? [x more] (reduce (fn [res [v1 v2]] (if (pos? (compare v1 v2))
323
+ res
324
+ (reduced false)))
325
+ true (map vector (cons x more) more)))
326
+
327
+ (-increasing? [x more] (reduce (fn [res [v1 v2]] (if (neg? (compare v1 v2))
328
+ (reduced false)
329
+ res))
330
+ true (map vector (cons x more) more))))
331
+
332
+ (defn- lesser? [& args]
333
+ (-strictly-decreasing? (first args) (rest args)))
334
+
335
+ (defn- lesser-equal? [& args]
336
+ (-decreasing? (first args) (rest args)))
337
+
338
+ (defn- greater? [& args]
339
+ (-strictly-increasing? (first args) (rest args)))
340
+
341
+ (defn- greater-equal? [& args]
342
+ (-increasing? (first args) (rest args)))
343
+
344
+ (defn -min
345
+ ([coll] (reduce (fn [acc x]
346
+ (if (neg? (compare x acc))
347
+ x acc))
348
+ (first coll) (next coll)))
349
+ ([n coll]
350
+ (vec
351
+ (reduce (fn [acc x]
352
+ (cond
353
+ (< (count acc) n)
354
+ (sort compare (conj acc x))
355
+ (neg? (compare x (last acc)))
356
+ (sort compare (conj (butlast acc) x))
357
+ :else acc))
358
+ [] coll))))
359
+
360
+ (defn -max
361
+ ([coll] (reduce (fn [acc x]
362
+ (if (pos? (compare x acc))
363
+ x acc))
364
+ (first coll) (next coll)))
365
+ ([n coll]
366
+ (vec
367
+ (reduce (fn [acc x]
368
+ (cond
369
+ (< (count acc) n)
370
+ (sort compare (conj acc x))
371
+ (pos? (compare x (first acc)))
372
+ (sort compare (conj (next acc) x))
373
+ :else acc))
374
+ [] coll))))
375
+
376
+ (def built-ins {'= =, '== ==, 'not= not=, '!= not=, '< lesser?, '> greater?, '<= lesser-equal?, '>= greater-equal?, '+ +, '- -
377
+ '* *, '/ /, 'quot quot, 'rem rem, 'mod mod, 'inc inc, 'dec dec, 'max -max, 'min -min
378
+ 'zero? zero?, 'pos? pos?, 'neg? neg?, 'even? even?, 'odd? odd?, 'compare compare
379
+ 'rand rand, 'rand-int rand-int
380
+ 'true? true?, 'false? false?, 'nil? nil?, 'some? some?, 'not not, 'and and-fn, 'or or-fn
381
+ 'complement complement, 'identical? identical?
382
+ 'identity identity, 'meta meta, 'name name, 'namespace namespace, 'type type
383
+ 'vector vector, 'list list, 'set set, 'hash-map hash-map, 'array-map array-map
384
+ 'count count, 'range range, 'not-empty not-empty, 'empty? empty, 'contains? contains?
385
+ 'str str, 'pr-str pr-str, 'print-str print-str, 'println-str println-str, 'prn-str prn-str, 'subs subs
386
+ 're-find re-find, 're-matches re-matches, 're-seq re-seq
387
+ '-differ? -differ?, 'get-else -get-else, 'get-some -get-some, 'missing? -missing?, 'ground identity, 'before? lesser?, 'after? greater?
388
+ 'tuple vector, 'untuple identity
389
+ 'q q
390
+ 'datahike.query/q q
391
+ #'datahike.query/q q})
392
+
393
+ (def clj-core-built-ins
394
+ #?(:clj
395
+ (dissoc (ns-publics 'clojure.core)
396
+ 'eval)
397
+ :cljs {}))
398
+
399
+ (def built-in-aggregates
400
+ (letfn [(sum [coll] (reduce + 0 coll))
401
+ (avg [coll] (/ (sum coll) (count coll)))
402
+ (median
403
+ [coll]
404
+ (let [terms (sort coll)
405
+ size (count coll)
406
+ med (bit-shift-right size 1)]
407
+ (cond-> (nth terms med)
408
+ (even? size)
409
+ (-> (+ (nth terms (dec med)))
410
+ (/ 2)))))
411
+ (variance
412
+ [coll]
413
+ (let [mean (avg coll)
414
+ sum (sum (for [x coll
415
+ :let [delta (- x mean)]]
416
+ (* delta delta)))]
417
+ (/ sum (count coll))))
418
+ (stddev
419
+ [coll]
420
+ (#?(:cljs js/Math.sqrt :clj Math/sqrt) (variance coll)))]
421
+ {'avg avg
422
+ 'median median
423
+ 'variance variance
424
+ 'stddev stddev
425
+ 'distinct set
426
+ 'min -min
427
+ 'max -max
428
+ 'sum sum
429
+ 'rand (fn
430
+ ([coll] (rand-nth coll))
431
+ ([n coll] (vec (repeatedly n #(rand-nth coll)))))
432
+ 'sample (fn [n coll]
433
+ (vec (take n (shuffle coll))))
434
+ 'count count
435
+ 'count-distinct (fn [coll] (count (distinct coll)))}))
436
+
437
+ (defn parse-rules [rules]
438
+ (let [rules (if (string? rules) (edn/read-string rules) rules)] ;; for datahike.js interop
439
+ (group-by ffirst rules)))
440
+
441
+ (defn empty-rel [binding]
442
+ (let [vars (->> (dpi/collect-vars-distinct binding)
443
+ (map :symbol))]
444
+ (Relation. (zipmap vars (range)) [])))
445
+
446
+ (defprotocol IBinding
447
+ (in->rel [binding value]))
448
+
449
+ (extend-protocol IBinding
450
+ BindIgnore
451
+ (in->rel [_ _]
452
+ (prod-rel))
453
+
454
+ BindScalar
455
+ (in->rel [binding value]
456
+ (Relation. {(get-in binding [:variable :symbol]) 0} [(into-array [value])]))
457
+
458
+ BindColl
459
+ (in->rel [binding coll]
460
+ (cond
461
+ (not (seqable? coll))
462
+ (dt/raise "Cannot bind value " coll " to collection " (dpi/get-source binding)
463
+ {:error :query/binding, :value coll, :binding (dpi/get-source binding)})
464
+ (empty? coll)
465
+ (empty-rel binding)
466
+ :else
467
+ (->> coll
468
+ (map #(in->rel (:binding binding) %))
469
+ (reduce sum-rel))))
470
+
471
+ BindTuple
472
+ (in->rel [binding coll]
473
+ (cond
474
+ (not (seqable? coll))
475
+ (dt/raise "Cannot bind value " coll " to tuple " (dpi/get-source binding)
476
+ {:error :query/binding, :value coll, :binding (dpi/get-source binding)})
477
+ (< (count coll) (count (:bindings binding)))
478
+ (dt/raise "Not enough elements in a collection " coll " to bind tuple " (dpi/get-source binding)
479
+ {:error :query/binding, :value coll, :binding (dpi/get-source binding)})
480
+ :else
481
+ (reduce prod-rel
482
+ (map #(in->rel %1 %2) (:bindings binding) coll)))))
483
+
484
+ (defn resolve-in [context [binding value]]
485
+ (cond
486
+ (and (instance? BindScalar binding)
487
+ (instance? SrcVar (:variable binding)))
488
+ (update context :sources assoc (get-in binding [:variable :symbol]) value)
489
+ (and (instance? BindScalar binding)
490
+ (instance? RulesVar (:variable binding)))
491
+ (assoc context :rules (parse-rules value))
492
+ (and (instance? BindScalar binding)
493
+ (instance? Variable (:variable binding)))
494
+ (assoc-in context [:consts (get-in binding [:variable :symbol])] value)
495
+ #_(instance? BindColl binding) ;; TODO: later
496
+ :else
497
+ (update context :rels conj (in->rel binding value))))
498
+
499
+ (defn resolve-ins [context bindings values]
500
+ (reduce resolve-in context (zipmap bindings values)))
501
+
502
+ (def ^{:dynamic true
503
+ :doc "List of symbols in current pattern that might potentially be resolved to refs"}
504
+ *lookup-attrs* nil)
505
+
506
+ (def ^{:dynamic true
507
+ :doc "Default pattern source. Lookup refs, patterns, rules will be resolved with it"}
508
+ *implicit-source* nil)
509
+
510
+ (defn getter-fn [attrs attr]
511
+ (let [idx (attrs attr)]
512
+ (if (contains? *lookup-attrs* attr)
513
+ (fn [tuple]
514
+ (let [eid (#?(:cljs da/aget :clj get) tuple idx)]
515
+ (cond
516
+ (number? eid) eid ;; quick path to avoid fn call
517
+ (sequential? eid) (dbu/entid *implicit-source* eid)
518
+ (da/array? eid) (dbu/entid *implicit-source* eid)
519
+ :else eid)))
520
+ (fn [tuple]
521
+ (#?(:cljs da/aget :clj get) tuple idx)))))
522
+
523
+ (defn tuple-key-fn [getters]
524
+ (if (== (count getters) 1)
525
+ (first getters)
526
+ (let [getters (to-array getters)]
527
+ (fn [tuple]
528
+ (list* #?(:cljs (.map getters #(% tuple))
529
+ :clj (to-array (map #(% tuple) getters))))))))
530
+
531
+ (defn hash-attrs [key-fn tuples]
532
+ ;; Equivalent to group-by except that it uses a list instead of a vector.
533
+ (loop [tuples tuples
534
+ hash-table (transient {})]
535
+ (if-some [tuple (first tuples)]
536
+ (let [key (key-fn tuple)]
537
+ (recur (next tuples)
538
+ (assoc! hash-table key (conj (get hash-table key '()) tuple))))
539
+ (persistent! hash-table))))
540
+
541
+ (defn hash-join [rel1 rel2]
542
+ (let [tuples1 (:tuples rel1)
543
+ tuples2 (:tuples rel2)
544
+ attrs1 (:attrs rel1)
545
+ attrs2 (:attrs rel2)
546
+ common-attrs (vec (intersect-keys (:attrs rel1) (:attrs rel2)))
547
+ common-gtrs1 (map #(getter-fn attrs1 %) common-attrs)
548
+ common-gtrs2 (map #(getter-fn attrs2 %) common-attrs)
549
+ keep-attrs1 (keys attrs1)
550
+ keep-attrs2 (vec (set/difference (set (keys attrs2)) (set (keys attrs1))))
551
+ keep-idxs1 (to-array (map attrs1 keep-attrs1))
552
+ keep-idxs2 (to-array (map attrs2 keep-attrs2))
553
+ key-fn1 (tuple-key-fn common-gtrs1)
554
+ key-fn2 (tuple-key-fn common-gtrs2)]
555
+ (if (< (count tuples1) (count tuples2))
556
+ (let [hash (hash-attrs key-fn1 tuples1)
557
+ new-tuples (->>
558
+ (reduce (fn [acc tuple2]
559
+ (let [key (key-fn2 tuple2)]
560
+ (if-some [tuples1 (get hash key)]
561
+ (reduce (fn [acc tuple1]
562
+ (conj! acc (join-tuples tuple1 keep-idxs1 tuple2 keep-idxs2)))
563
+ acc tuples1)
564
+ acc)))
565
+ (transient []) tuples2)
566
+ (persistent!))]
567
+ (Relation. (zipmap (concat keep-attrs1 keep-attrs2) (range))
568
+ new-tuples))
569
+ (let [hash (hash-attrs key-fn2 tuples2)
570
+ new-tuples (->>
571
+ (reduce (fn [acc tuple1]
572
+ (let [key (key-fn1 tuple1)]
573
+ (if-some [tuples2 (get hash key)]
574
+ (reduce (fn [acc tuple2]
575
+ (conj! acc (join-tuples tuple1 keep-idxs1 tuple2 keep-idxs2)))
576
+ acc tuples2)
577
+ acc)))
578
+ (transient []) tuples1)
579
+ (persistent!))]
580
+ (Relation. (zipmap (concat keep-attrs1 keep-attrs2) (range))
581
+ new-tuples)))))
582
+
583
+ (defn subtract-rel [a b]
584
+ (let [{attrs-a :attrs, tuples-a :tuples} a
585
+ {attrs-b :attrs, tuples-b :tuples} b
586
+ attrs (intersect-keys attrs-a attrs-b)
587
+ getters-b (map #(getter-fn attrs-b %) attrs)
588
+ key-fn-b (tuple-key-fn getters-b)
589
+ hash (hash-attrs key-fn-b tuples-b)
590
+ getters-a (map #(getter-fn attrs-a %) attrs)
591
+ key-fn-a (tuple-key-fn getters-a)]
592
+ (assoc a
593
+ :tuples (filterv #(nil? (hash (key-fn-a %))) tuples-a))))
594
+
595
+ (defn var-mapping [pattern indices]
596
+ (->> (map vector pattern indices)
597
+ (filter (fn [[s _]] (free-var? s)))
598
+ (into {})))
599
+
600
+ (defn map-consts [context orig-pattern datoms]
601
+ (let [;; Create a map from free var to index
602
+ ;; for the positions in the pattern
603
+ attr->idx (var-mapping orig-pattern (range))
604
+ idx->const (reduce-kv (fn [m k v]
605
+ (if-let [c (k (:consts context))]
606
+ (if (= c (get (first datoms) v)) ;; All datoms have the same format and the same value at position v
607
+ m ;; -> avoid unnecessary translations
608
+ (assoc m v c))
609
+ m))
610
+ {}
611
+ attr->idx)]
612
+ (when (seq idx->const)
613
+ (Relation. attr->idx (map #(reduce (fn [datom [k v]]
614
+ (assoc datom k v))
615
+ (vec (seq %))
616
+ idx->const)
617
+ datoms)))))
618
+
619
+ (defn replace-symbols-by-nil [pattern]
620
+ (mapv #(if (symbol? %) nil %) pattern))
621
+
622
+ (defn resolve-pattern-eid [db search-pattern]
623
+ (let [first-p (first search-pattern)]
624
+ (if (and (some? first-p)
625
+ (not (symbol? first-p)))
626
+ (when-let [eid (dbu/entid db first-p)]
627
+ (assoc search-pattern 0 eid))
628
+ search-pattern)))
629
+
630
+ (defn relation-from-datoms-xform []
631
+ (comp (map #?(:cljs (fn [datom]
632
+ (if (and (some? datom)
633
+ (or (instance? datahike.datom/Datom datom)
634
+ (.-e datom)))
635
+ (let [e (.-e ^datahike.datom.Datom datom)
636
+ a (.-a ^datahike.datom.Datom datom)
637
+ a-kw (keyword (.-fqn ^clj a)) ; Extract from Keyword object
638
+ v (.-v ^datahike.datom.Datom datom)
639
+ tx (.-tx ^datahike.datom.Datom datom)]
640
+ #js [e a-kw v tx (pos? tx)]) ; JS array for goog.array/get
641
+ datom))
642
+ :clj (fn [[e a v tx added?]]
643
+ [e a v tx added?])))
644
+ (distinct-tuples)))
645
+
646
+ (defn relation-from-datoms [context orig-pattern datoms]
647
+ (or (map-consts context orig-pattern datoms)
648
+ #?(:cljs (let [converted (mapv (fn [d]
649
+ (if (and (some? d)
650
+ (or (instance? datahike.datom/Datom d)
651
+ (.-e d)))
652
+ (let [a (.-a ^datahike.datom.Datom d)
653
+ a-kw (keyword (.-fqn ^clj a))]
654
+ #js [(.-e ^datahike.datom.Datom d)
655
+ a-kw
656
+ (.-v ^datahike.datom.Datom d)
657
+ (.-tx ^datahike.datom.Datom d)
658
+ (pos? (.-tx ^datahike.datom.Datom d))])
659
+ d))
660
+ datoms)]
661
+ (Relation. (var-mapping orig-pattern (range))
662
+ converted))
663
+ :clj (Relation. (var-mapping orig-pattern (range))
664
+ datoms))))
665
+
666
+ (defn matches-pattern? [pattern tuple]
667
+ (loop [tuple tuple
668
+ pattern pattern]
669
+ (if (and tuple pattern)
670
+ (let [t (first tuple)
671
+ p (first pattern)]
672
+ (if (or (symbol? p) (= t p))
673
+ (recur (next tuple) (next pattern))
674
+ false))
675
+ true)))
676
+
677
+ (defn lookup-pattern-coll [coll pattern orig-pattern]
678
+ (let [attr->idx (var-mapping orig-pattern (range))
679
+ data (filter #(matches-pattern? pattern %) coll)]
680
+ (Relation. attr->idx (mapv to-array data)))) ;; FIXME to-array
681
+
682
+ (defn collapse-rels [rels new-rel]
683
+ (loop [rels rels
684
+ new-rel new-rel
685
+ acc []]
686
+ (if-some [rel (first rels)]
687
+ (if (not-empty (intersect-keys (:attrs new-rel) (:attrs rel)))
688
+ (recur (next rels) (hash-join rel new-rel) acc)
689
+ (recur (next rels) new-rel (conj acc rel)))
690
+ (conj acc new-rel))))
691
+
692
+ (defn- rel-with-attr [context sym]
693
+ (some #(when (contains? (:attrs %) sym) %) (:rels context)))
694
+
695
+ (defn- context-resolve-val [context sym]
696
+ (if-let [replacement (get (:consts context) sym)]
697
+ replacement
698
+ (when-some [rel (rel-with-attr context sym)]
699
+ (when-some [tuple (first (:tuples rel))]
700
+ (#?(:cljs da/aget :clj get) tuple ((:attrs rel) sym))))))
701
+
702
+ (defn- rel-contains-attrs? [rel attrs]
703
+ (some #(contains? (:attrs rel) %) attrs))
704
+
705
+ (defn- rel-prod-by-attrs [context attrs]
706
+ (let [rels (filter #(rel-contains-attrs? % attrs) (:rels context))
707
+ production (reduce prod-rel rels)]
708
+ [(update context :rels #(remove (set rels) %)) production]))
709
+
710
+ (defn -call-fn [context rel f args]
711
+ (let [sources (:sources context)
712
+ attrs (:attrs rel)
713
+ len (count args)
714
+ static-args (da/make-array len)
715
+ tuples-args (da/make-array len)]
716
+ (dotimes [i len]
717
+ (let [arg (nth args i)]
718
+ (if (symbol? arg)
719
+ (if-let [const (get (:consts context) arg)]
720
+ (da/aset static-args i const)
721
+ (if-some [source (get sources arg)]
722
+ (da/aset static-args i source)
723
+ (da/aset tuples-args i (get attrs arg))))
724
+ (da/aset static-args i arg))))
725
+ (fn [tuple]
726
+ ;; TODO raise if not all args are bound
727
+ (dotimes [i len]
728
+ (when-some [tuple-idx (aget tuples-args i)]
729
+ (let [v (#?(:cljs da/aget :clj get) tuple tuple-idx)]
730
+ (da/aset static-args i v))))
731
+ (apply f static-args))))
732
+
733
+ (defn- resolve-sym [#?(:clj sym :cljs _)]
734
+ #?(:cljs nil
735
+ :clj (when (namespace sym)
736
+ (when-some [v (resolve sym)] @v))))
737
+
738
+ #?(:clj (def ^:private find-method
739
+ (memoize
740
+ (fn find-method-impl [^Class this-class method-name args-classes]
741
+ (or (->> this-class
742
+ .getMethods
743
+ (some (fn [^Method method]
744
+ (when (and (= method-name (.getName method))
745
+ (= (count args-classes)
746
+ (.getParameterCount method))
747
+ (every? true? (map #(Reflector/paramArgTypeMatch %1 %2)
748
+ (.getParameterTypes method)
749
+ args-classes)))
750
+ method))))
751
+ (throw (ex-info (str (.getName this-class) "."
752
+ method-name "("
753
+ (str/join "," (map #(.getName ^Class %) args-classes))
754
+ ") not found")
755
+ {:this-class this-class
756
+ :method-name method-name
757
+ :args-classes args-classes})))))))
758
+
759
+ (defn- resolve-method [#?(:clj method-sym :cljs _)]
760
+ #?(:cljs nil
761
+ :clj (let [method-str (name method-sym)]
762
+ (when (= \. (.charAt method-str 0))
763
+ (let [method-name (subs method-str 1)]
764
+ (fn [this & args]
765
+ (let [^Method method (find-method (class this) method-name (mapv class args))]
766
+ (Reflector/prepRet (.getReturnType method) (.invoke method this (into-array Object args))))))))))
767
+
768
+ (defn filter-by-pred [context clause]
769
+ (let [[[f & args]] clause
770
+ pred (or (get built-ins f)
771
+ (get clj-core-built-ins f)
772
+ (context-resolve-val context f)
773
+ (resolve-sym f)
774
+ (resolve-method f)
775
+ (when (nil? (rel-with-attr context f))
776
+ (dt/raise "Unknown predicate '" f " in " clause
777
+ {:error :query/where, :form clause, :var f})))
778
+ [context production] (rel-prod-by-attrs context (filter symbol? args))
779
+ new-rel (if pred
780
+ (let [tuple-pred (-call-fn context production pred args)]
781
+ (update production :tuples #(filter tuple-pred %)))
782
+ (assoc production :tuples []))]
783
+ (update context :rels conj new-rel)))
784
+
785
+ (defn bind-by-fn [context clause]
786
+ (let [[[f & args] out] clause
787
+ binding (dpi/parse-binding out)
788
+ fun (or (get built-ins f)
789
+ (get clj-core-built-ins f)
790
+ (context-resolve-val context f)
791
+ (resolve-sym f)
792
+ (resolve-method f)
793
+ (when (nil? (rel-with-attr context f))
794
+ (dt/raise "Unknown function '" f " in " clause
795
+ {:error :query/where, :form clause, :var f})))
796
+ attrs (filter symbol? args)
797
+ [context production] (rel-prod-by-attrs context attrs)
798
+ symbols-with-values (into #{}
799
+ (mapcat keys)
800
+ [(:attrs production)
801
+ (:consts context)
802
+ (:sources context)])]
803
+ ;; Currently, we can only evaluate this clause if all variables
804
+ ;; in the function call are bound. If not, we return nil which
805
+ ;; is handled by `datahike.tools/resolve-clauses`.
806
+ (when (every? symbols-with-values attrs)
807
+ (let [new-rel (if fun
808
+ (let [tuple-fn (-call-fn context production fun args)
809
+ rels (for [tuple (:tuples production)
810
+ :let [val (tuple-fn tuple)]
811
+ :when (not (nil? val))]
812
+ (prod-rel (Relation. (:attrs production) [tuple])
813
+ (in->rel binding val)))]
814
+ (if (empty? rels)
815
+ (prod-rel production (empty-rel binding))
816
+ (reduce sum-rel rels)))
817
+ (prod-rel (assoc production :tuples []) (empty-rel binding)))
818
+ idx->const (reduce-kv (fn [m k v]
819
+ (if-let [c (k (:consts context))]
820
+ (assoc m v c) ;; different value at v for each tuple
821
+ m))
822
+ {}
823
+ (:attrs new-rel))]
824
+ (if (empty? (:tuples new-rel))
825
+ (update context :rels collapse-rels new-rel)
826
+ (-> context ;; filter output binding
827
+ (update :rels collapse-rels
828
+ (update new-rel
829
+ :tuples
830
+ #(filter (fn [tuple]
831
+ (every? (fn [[ind c]]
832
+ (= c (get tuple ind)))
833
+ idx->const))
834
+ %)))))))))
835
+
836
+ ;;; RULES
837
+
838
+ (defn rule? [context clause]
839
+ (and (sequential? clause)
840
+ (contains? (:rules context)
841
+ (if (source? (first clause))
842
+ (second clause)
843
+ (first clause)))))
844
+
845
+ (def rule-seqid (atom 0))
846
+
847
+ #?(:clj
848
+ (defmacro some-of
849
+ ([] nil)
850
+ ([x] x)
851
+ ([x & more]
852
+ `(let [x# ~x] (if (nil? x#) (some-of ~@more) x#)))))
853
+
854
+ (defn expand-rule [clause context _used-args]
855
+ (let [[rule & call-args] clause
856
+ seqid (swap! rule-seqid inc)
857
+ branches (get (:rules context) rule)
858
+ call-args-new (map #(if (free-var? %) % (symbol (str "?__auto__" %2)))
859
+ call-args
860
+ (range))
861
+ consts (->> (map vector call-args-new call-args)
862
+ (filter (fn [[new old]] (not= new old)))
863
+ (into {}))]
864
+ [(for [branch branches
865
+ :let [[[_ & rule-args] & clauses] branch
866
+ replacements (zipmap rule-args call-args-new)]]
867
+ (walk/postwalk
868
+ #(if (free-var? %)
869
+ (some-of
870
+ (replacements %)
871
+ (symbol (str (name %) "__auto__" seqid)))
872
+ %)
873
+ clauses))
874
+ consts]))
875
+
876
+ (defn remove-pairs [xs ys]
877
+ (let [pairs (->> (map vector xs ys)
878
+ (remove (fn [[x y]] (= x y))))]
879
+ [(map first pairs)
880
+ (map second pairs)]))
881
+
882
+ (defn rule-gen-guards [rule-clause used-args]
883
+ (let [[rule & call-args] rule-clause
884
+ prev-call-args (get used-args rule)]
885
+ (for [prev-args prev-call-args
886
+ :let [[call-args prev-args] (remove-pairs call-args prev-args)]]
887
+ [(concat ['-differ?] call-args prev-args)])))
888
+
889
+ (defn walk-collect [form pred]
890
+ (let [res (atom [])]
891
+ (walk/postwalk #(do (when (pred %) (swap! res conj %)) %) form)
892
+ @res))
893
+
894
+ (defn collect-vars [clause]
895
+ (set (walk-collect clause free-var?)))
896
+
897
+ (defn split-guards [clauses guards]
898
+ (let [bound-vars (collect-vars clauses)
899
+ pred (fn [[[_ & vars]]] (every? bound-vars vars))]
900
+ [(filter pred guards)
901
+ (remove pred guards)]))
902
+
903
+ (defn solve-rule [context clause]
904
+ (let [final-attrs (filter free-var? clause)
905
+ final-attrs-map (zipmap final-attrs (range))
906
+ stats? (:stats context)
907
+ ;; clause-cache (atom {}) ;; TODO
908
+ solve (fn [prefix-context clause clauses]
909
+ (if stats?
910
+ (dqs/update-ctx-with-stats prefix-context clause
911
+ (fn [ctx]
912
+ (let [tmp-context (dt/resolve-clauses
913
+ -resolve-clause
914
+ (assoc ctx :stats [])
915
+ clauses)]
916
+ (assoc tmp-context
917
+ :stats (:stats ctx)
918
+ :tmp-stats {:type :solve
919
+ :clauses clauses
920
+ :branches (:stats tmp-context)}))))
921
+ (dt/resolve-clauses -resolve-clause prefix-context clauses)))
922
+ empty-rels? (fn [ctx]
923
+ (some #(empty? (:tuples %)) (:rels ctx)))]
924
+ (loop [stack (list {:prefix-clauses []
925
+ :prefix-context context
926
+ :clauses [clause]
927
+ :used-args {}
928
+ :pending-guards {}
929
+ :clause clause})
930
+ rel (Relation. final-attrs-map [])
931
+ tmp-stats []]
932
+ (if-some [frame (first stack)]
933
+ (let [[clauses [rule-clause & next-clauses]] (split-with #(not (rule? context %)) (:clauses frame))]
934
+ (if (nil? rule-clause)
935
+
936
+ ;; no rules -> expand, collect, sum
937
+ (let [prefix-context (solve (:prefix-context frame) (:clause frame) clauses)
938
+ tuples (-collect prefix-context final-attrs)
939
+ new-rel (Relation. final-attrs-map tuples)
940
+ new-stats (conj tmp-stats (last (:stats prefix-context)))]
941
+ (recur (next stack) (sum-rel rel new-rel) new-stats))
942
+
943
+ ;; has rule -> add guards -> check if dead -> expand rule -> push to stack, recur
944
+ (let [[rule & call-args] rule-clause
945
+ guards (rule-gen-guards rule-clause (:used-args frame))
946
+ [active-gs pending-gs] (split-guards (concat (:prefix-clauses frame) clauses)
947
+ (concat guards (:pending-guards frame)))]
948
+ (if (some #(= % '[(-differ?)]) active-gs) ;; trivial always false case like [(not= [?a ?b] [?a ?b])]
949
+
950
+ ;; this branch has no data, just drop it from stack
951
+ (recur (next stack) rel tmp-stats)
952
+
953
+ (let [prefix-clauses (concat clauses active-gs)
954
+ prefix-context (solve (:prefix-context frame) (:clause frame) prefix-clauses)
955
+ new-stats (conj tmp-stats (last (:stats prefix-context)))]
956
+ (if (empty-rels? prefix-context)
957
+
958
+ ;; this branch has no data, just drop it from stack
959
+ (recur (next stack) rel new-stats)
960
+
961
+ ;; need to expand rule to branches
962
+ (let [used-args (assoc (:used-args frame) rule
963
+ (conj (get (:used-args frame) rule []) call-args))
964
+ [branches rule-consts] (expand-rule rule-clause context used-args)]
965
+ (recur (concat
966
+ (for [branch branches]
967
+ {:prefix-clauses prefix-clauses
968
+ :prefix-context (update prefix-context :consts merge rule-consts)
969
+ :clauses (concatv branch next-clauses)
970
+ :used-args used-args
971
+ :pending-guards pending-gs
972
+ :clause branch})
973
+ (next stack))
974
+ rel
975
+ new-stats))))))))
976
+ (cond-> (update context :rels collapse-rels rel)
977
+ stats? (assoc :tmp-stats {:type :rule
978
+ :branches tmp-stats}))))))
979
+
980
+ (defn resolve-pattern-lookup-entity-id [source e error-code]
981
+ (cond
982
+ (dbu/numeric-entid? e) e
983
+ (or (lookup-ref? e) (attr? e)) (dbu/entid-strict source e error-code)
984
+ ;(entid? e) e
985
+ (keyword? e) e
986
+ (symbol? e) e
987
+ :else (or error-code (dt/raise "Invalid entid" {:error :entity-id/syntax :entity-id e}))))
988
+
989
+ (defn resolve-pattern-lookup-refs
990
+ "Translate pattern entries before using pattern for database search"
991
+ ([source pattern] (resolve-pattern-lookup-refs source pattern nil))
992
+ ([source pattern error-code]
993
+ (if (dbu/db? source)
994
+ (dt/with-destructured-vector pattern
995
+ e (resolve-pattern-lookup-entity-id source e error-code)
996
+ a (if (and (:attribute-refs? (dbi/-config source)) (keyword? a))
997
+ (dbi/-ref-for source a)
998
+ a)
999
+ v (if (and v (attr? a) (dbu/ref? source a) (or (lookup-ref? v) (attr? v)))
1000
+ (dbu/entid-strict source v error-code)
1001
+ v)
1002
+ tx (if (lookup-ref? tx)
1003
+ (dbu/entid-strict source tx error-code)
1004
+ tx)
1005
+ added added)
1006
+ pattern)))
1007
+
1008
+ (defn good-lookup-refs? [pattern]
1009
+ (if (coll? pattern)
1010
+ (not-any? #(= % ::error) pattern)
1011
+ (not= ::error pattern)))
1012
+
1013
+ (defn resolve-pattern-lookup-refs-or-nil
1014
+ "This function works just like `resolve-pattern-lookup-refs` but if there is an error it returns `nil` instead of throwing an exception. This is used to reject patterns with variables substituted for invalid values.
1015
+
1016
+ For instance, take the query
1017
+
1018
+ (d/q '[:find ?e
1019
+ :in $ [?e ...]
1020
+ :where [?e :friend 3]]
1021
+ db [1 2 3 \"A\"])
1022
+
1023
+ in the test `datahike.test.lookup-refs-test/test-lookup-refs-query`.
1024
+
1025
+ According to this query, the variable `?e` can be either `1`, `2`, `3` or `\"A\"`
1026
+ but \"A\" is not a valid entity.
1027
+
1028
+ The query engine will evaluate the pattern `[?e :friend 3]`. For the strategies
1029
+ `identity` and `select-simple`, no substitution will be performed in this pattern.
1030
+ Instead, they will ask for all tuples from the database and then filter them, so
1031
+ the fact that `?e` can be bound to an impossible entity id `\"A\"` is not a problem.
1032
+
1033
+ But with the strategy `select-all`, the substituted pattern will become
1034
+
1035
+ [\"A\" :friend 3]
1036
+
1037
+ and consequently, the `result` below will take the value `[::error :friend 3]`.
1038
+ The unit test is currently written to simply ignore illegal illegal entity ids
1039
+ such as \"A\" and therefore, we handle that by letting this function return nil
1040
+ in those cases.
1041
+ "
1042
+ [source pattern]
1043
+ (let [result (resolve-pattern-lookup-refs source pattern ::error)]
1044
+ (when (good-lookup-refs? result)
1045
+ result)))
1046
+
1047
+ (defn dynamic-lookup-attrs [source pattern]
1048
+ (let [[e a v tx] pattern]
1049
+ (cond-> #{}
1050
+ (free-var? e) (conj e)
1051
+ (free-var? tx) (conj tx)
1052
+ (and
1053
+ (free-var? v)
1054
+ (not (free-var? a))
1055
+ (dbu/ref? source a)) (conj v))))
1056
+
1057
+ (defn limit-rel [rel vars]
1058
+ (when-some [attrs' (not-empty (select-keys (:attrs rel) vars))]
1059
+ (assoc rel :attrs attrs')))
1060
+
1061
+ (defn limit-context [context vars]
1062
+ (assoc context
1063
+ :rels (->> (:rels context)
1064
+ (keep #(limit-rel % vars)))))
1065
+
1066
+ (defn check-all-bound [context vars form]
1067
+ (let [bound (set (concat (mapcat #(keys (:attrs %)) (:rels context))
1068
+ (keys (:consts context))))]
1069
+ (when-not (set/subset? vars bound)
1070
+ (let [missing (set/difference (set vars) bound)]
1071
+ (dt/raise "Insufficient bindings: " missing " not bound in " form
1072
+ {:error :query/where
1073
+ :form form
1074
+ :vars missing})))))
1075
+
1076
+ (defn check-some-bound [context vars form]
1077
+ (let [bound (set (concat (mapcat #(keys (:attrs %)) (:rels context))
1078
+ (keys (:consts context))))]
1079
+ (when (empty? (set/intersection vars bound))
1080
+ (dt/raise "Insufficient bindings: none of " vars " is bound in " form
1081
+ {:error :query/where
1082
+ :form form}))))
1083
+
1084
+ (defn resolve-context [context clauses]
1085
+ (dt/resolve-clauses resolve-clause context clauses))
1086
+
1087
+ (defn tuple-var-mapper [rel]
1088
+ (let [attrs (:attrs rel)
1089
+ key-fn-pairs (into []
1090
+ (map (juxt identity (partial getter-fn attrs)))
1091
+ (keys attrs))]
1092
+ (fn [tuple]
1093
+ (into {}
1094
+ (map (fn [[k f]] [k (f tuple)]))
1095
+ key-fn-pairs))))
1096
+
1097
+ (def rel-product-unit (Relation. {} [[]]))
1098
+
1099
+ (defn bound-symbol-map
1100
+ "Given a sequential collection of relations, return a map where every key is a symbol of a variable and every value is a map with the keys `:relation-index` and `:tuple-element-index`. The key `:relation-index` is associated with the index of the relation where the variable occurs and the key `:tuple-element-index` is associated with the index of the location in the clause where the symbol occurs."
1101
+ [rels]
1102
+ (into {} (for [[rel-index rel] (map-indexed vector rels)
1103
+ [sym tup-index] (:attrs rel)]
1104
+ [sym {:relation-index rel-index
1105
+ :tuple-element-index tup-index}])))
1106
+
1107
+ (defn normalize-pattern
1108
+ "Takes a pattern and returns a new pattern with exactly five elements, filling in any missing ones with nil."
1109
+ [[e a v tx added?]]
1110
+ [e a v tx added?])
1111
+
1112
+ (defn replace-unbound-symbols-by-nil [bsm pattern]
1113
+ (normalize-pattern
1114
+ (mapv #(when-not (and (symbol? %) (not (contains? bsm %)))
1115
+ %)
1116
+ pattern)))
1117
+
1118
+ (defn search-index-mapping
1119
+ "Returns a sequence of maps with index-information for a subset of e, a, v, tx. The `strategy-vec` argument is a vector of four elements corresponding to e, a, v, tx respectively. Every such element can be either `:substitute`, `:filter` or `nil` depending on how the corresponding element in the pattern should be used. The `clean-pattern` is argument is a vector with the elements corresponding to e, a, v, tx. The argument `selected-strategy-symbol` can be either `:substitute`, `:filter` or `nil` and is used to filter e, a, v, tx based on the value of `:strategy-vec`."
1120
+ [{:keys [strategy-vec clean-pattern bsm]}
1121
+ selected-strategy-symbol]
1122
+ {:pre [(= 4 (count strategy-vec))]}
1123
+ (let [pattern (normalize-pattern clean-pattern)]
1124
+ (for [[pattern-element-index
1125
+ pattern-var
1126
+ strategy-symbol] (map vector (range) pattern strategy-vec)
1127
+ :when (= selected-strategy-symbol strategy-symbol)
1128
+ :let [m (bsm pattern-var)]
1129
+ :when m]
1130
+ (assoc m :pattern-element-index pattern-element-index))))
1131
+
1132
+ (defn substitution-relation-indices
1133
+ "Returns the set of indices of relations that have symbols that are substituted for actual values in the pattern before index lookup."
1134
+ [context]
1135
+ (into #{}
1136
+ (map :relation-index)
1137
+ (search-index-mapping context :substitute)))
1138
+
1139
+ (defn filtering-relation-indices
1140
+ "Returns the set of indices of relations that have symbols that will be used for filtering the datoms returned from the inex lookup."
1141
+ [context subst-inds]
1142
+ (into #{}
1143
+ (comp (map :relation-index)
1144
+ (remove subst-inds))
1145
+ (search-index-mapping context :filter)))
1146
+
1147
+ (defn index-feature-extractor
1148
+ "Given a set of indices referring to elements in a sequential container such as a datom or vector, construct a function that returns a value computed from such a sequential container such that two different values returned from that function are equal if and only if their corresponding values at those indices are equal. Optionally takes a function that can remap the selected elements."
1149
+ ([inds include-empty?]
1150
+ (index-feature-extractor inds include-empty? (fn [_ x] x)))
1151
+ ([inds include-empty? replacer]
1152
+ (let [first-index (first inds)]
1153
+ (case (count inds)
1154
+ 0 (when include-empty?
1155
+ (fn
1156
+ ([] [nil])
1157
+ ([_] nil)))
1158
+ 1 (fn
1159
+ ([] [first-index])
1160
+ ([x] (wrap-comparable (replacer first-index (nth x first-index)))))
1161
+ (fn
1162
+ ([] inds)
1163
+ ([x]
1164
+ (mapv #(wrap-comparable (replacer % (nth x %))) inds)))))))
1165
+
1166
+ (defn extend-predicate1 [predicate feature-extractor ref-feature]
1167
+ (if (nil? feature-extractor)
1168
+ predicate
1169
+ (if predicate
1170
+ (fn [datom]
1171
+ (let [feature (feature-extractor datom)]
1172
+ (if (= ref-feature feature)
1173
+ (predicate datom)
1174
+ false)))
1175
+ (fn [datom]
1176
+ (= ref-feature (feature-extractor datom))))))
1177
+
1178
+ (defn predicate-from-set [s]
1179
+ (case (count s)
1180
+ 0 (fn [_] false)
1181
+ 1 (let [y (first s)]
1182
+ (fn [x] (= x y)))
1183
+ (fn [x] (contains? s x))))
1184
+
1185
+ (defn extend-predicate [predicate feature-extractor features]
1186
+ {:pre [#?(:clj (or (set? features) (instance? HashSet features))
1187
+ :cljs (set? features))]}
1188
+ (let [this-pred (predicate-from-set features)]
1189
+ (if (nil? feature-extractor)
1190
+ predicate
1191
+ (if predicate
1192
+ (fn
1193
+ ([] (conj (predicate) [(feature-extractor) features]))
1194
+ ([datom]
1195
+ (let [feature (feature-extractor datom)]
1196
+ (if (this-pred feature)
1197
+ (predicate datom)
1198
+ false))))
1199
+ (fn
1200
+ ([] [(feature-extractor) features])
1201
+ ([datom]
1202
+ (this-pred (feature-extractor datom))))))))
1203
+
1204
+ (defn resolve-pattern-lookup-ref-at-index
1205
+ [source clean-attribute pattern-index pattern-value error-code]
1206
+ (let [a clean-attribute]
1207
+ (case (int pattern-index)
1208
+ 0 (resolve-pattern-lookup-entity-id source pattern-value error-code)
1209
+ 1 (if (and (:attribute-refs? (dbi/-config source)) (keyword? pattern-value))
1210
+ (dbi/-ref-for source pattern-value)
1211
+ pattern-value)
1212
+ 2 (if (and pattern-value
1213
+ (attr? a)
1214
+ (dbu/ref? source a)
1215
+ (or (lookup-ref? pattern-value) (attr? pattern-value)))
1216
+ (dbu/entid-strict source pattern-value error-code)
1217
+ pattern-value)
1218
+ 3 (if (lookup-ref? pattern-value)
1219
+ (dbu/entid-strict source pattern-value error-code)
1220
+ pattern-value)
1221
+ 4 pattern-value)))
1222
+
1223
+ (defn lookup-ref-replacer
1224
+ ([context] (lookup-ref-replacer context ::error))
1225
+ ([{:keys [source clean-pattern]} error-value]
1226
+ (let [[_ attribute _ _] clean-pattern]
1227
+ (if source
1228
+ (if (dbu/db? source)
1229
+ (fn [index pattern-value]
1230
+ (resolve-pattern-lookup-ref-at-index source
1231
+ attribute
1232
+ index
1233
+ pattern-value
1234
+ error-value))
1235
+ (fn [_i x] x))
1236
+ (fn [_ x] x)))))
1237
+
1238
+ (defn- generate-substitution-xform-code [pred-expr
1239
+ datom-predicate-symbol
1240
+ filter-feature-symbol
1241
+ pmask
1242
+ substituted-pattern-and-filter-feature-pairs]
1243
+ (let [pattern-symbols (repeatedly 5 gensym)
1244
+ substitution-value-vector (gensym "substitution-value-vector")]
1245
+ `(fn [step#]
1246
+ (fn
1247
+ ([] (step#))
1248
+ ([dst-one#] (step# dst-one#))
1249
+
1250
+ ;; This is a higher-arity step function.
1251
+ ([dst# ~@pattern-symbols ~datom-predicate-symbol]
1252
+
1253
+ ;; This generates the code that substitutes some of the
1254
+ ;; incomping values by values from the relation and calls
1255
+ ;; the next step function in the transducer chain.
1256
+ (reduce
1257
+ (fn [dst-inner# [~substitution-value-vector ~filter-feature-symbol]]
1258
+ (step# dst-inner#
1259
+ ~@(map (fn [i sym]
1260
+ (if (nil? i)
1261
+ sym
1262
+ `(nth ~substitution-value-vector ~i)))
1263
+ pmask
1264
+ pattern-symbols)
1265
+ ~pred-expr))
1266
+ dst#
1267
+ ~substituted-pattern-and-filter-feature-pairs))))))
1268
+
1269
+ (defmacro substitution-expansion [substitution-pattern-element-inds
1270
+ filter-feature-extractor
1271
+ substituted-pattern-and-filter-feature-pairs]
1272
+ (let [datom-predicate-symbol (gensym)
1273
+ filter-feature-symbol (gensym)]
1274
+
1275
+ ;; This code generates a tree of `if`-forms for all ordered subsets of
1276
+ ;; the sequence `(range 5)` that `substitution-pattern-element-inds`.
1277
+ ;; can take. At each leaf of the tree, code is generated for that particular
1278
+ ;; subset.
1279
+ (dt/range-subset-tree
1280
+ 5
1281
+ substitution-pattern-element-inds
1282
+
1283
+ ;; This function is called at each leaf of the tree.
1284
+ ;; `pmast` is a boolean sequence
1285
+ (fn [_pinds pmask]
1286
+
1287
+ ;; `branch-expr` is a function that generates the actual
1288
+ ;; code given a predicate expression.
1289
+ (let [branch-expr (fn [pred-expr]
1290
+
1291
+ ;; This is the code for the transducer.
1292
+ (generate-substitution-xform-code
1293
+ pred-expr
1294
+ datom-predicate-symbol
1295
+ filter-feature-symbol
1296
+ pmask
1297
+ substituted-pattern-and-filter-feature-pairs))]
1298
+
1299
+ ;; Generate different code depending on whether or not there is a
1300
+ ;; `filt-extractor`, meaning that the resulting datoms have to be
1301
+ ;; filtered.
1302
+ `(if (nil? ~filter-feature-extractor)
1303
+ ~(branch-expr datom-predicate-symbol)
1304
+ ~(branch-expr `(extend-predicate1 ~datom-predicate-symbol
1305
+ ~filter-feature-extractor
1306
+ ~filter-feature-symbol))))))))
1307
+
1308
+ #_(instantiate-substitution-xform substitution-pattern-element-inds
1309
+ filter-feature-extractor
1310
+ substituted-pattern-and-filter-feature-pairs)
1311
+
1312
+ (defn instantiate-substitution-xform [substitution-pattern-element-inds
1313
+ filter-feature-extractor
1314
+ substituted-pattern-and-filter-feature-pairs]
1315
+
1316
+ ;; Returns a transducer based on the indices in `substitution-pattern-element-inds`
1317
+ (substitution-expansion substitution-pattern-element-inds
1318
+ filter-feature-extractor
1319
+ substituted-pattern-and-filter-feature-pairs))
1320
+
1321
+ ;; The performance improvement of using this macro has been measured,
1322
+ ;; see comment in single-substition-xform.
1323
+ (defmacro make-vec-lookup-ref-replacer [range-length]
1324
+ (let [inds (gensym)
1325
+ replacer (gensym)
1326
+ tuple (gensym)
1327
+ ex-sym# (if (get-in &env [:ns])
1328
+ 'js/Error
1329
+ Exception)]
1330
+ `(fn tree-fn# [~replacer ~inds]
1331
+ ~(dt/range-subset-tree
1332
+ range-length inds
1333
+ (fn replacer-fn# [pinds _mask]
1334
+ `(fn [~tuple]
1335
+ (try
1336
+ ~(mapv (fn [index i] `(~replacer ~index (nth ~tuple ~i)))
1337
+ pinds
1338
+ (range))
1339
+ (catch ~ex-sym# e# nil))))))))
1340
+
1341
+ (def vec-lookup-ref-replacer (make-vec-lookup-ref-replacer 5))
1342
+
1343
+ ;; The performance improvement of using this macro has been measured,
1344
+ ;; see comment in single-substition-xform.
1345
+ (defmacro basic-index-selector [max-length]
1346
+ (let [inds (gensym)
1347
+
1348
+ obj (gensym)]
1349
+ `(fn [~inds]
1350
+ (case (count ~inds)
1351
+ ~@(mapcat (fn [length]
1352
+ (let [index-symbols (vec (repeatedly length gensym))]
1353
+ [length `(let [~index-symbols ~inds]
1354
+ (fn [~obj]
1355
+ ~(mapv
1356
+ (fn [sym] `(nth ~obj ~sym))
1357
+ index-symbols)))]))
1358
+ (range (inc max-length)))))))
1359
+
1360
+ (def make-basic-index-selector (basic-index-selector 5))
1361
+
1362
+ (defn single-substitution-xform
1363
+ "Returns a transducer that substitutes the symbols for a single relation."
1364
+ [search-context
1365
+ relation-index
1366
+ substituted-vars-per-relation
1367
+ filtered-vars-per-relation]
1368
+ (let [;; This function maps the value at a pattern at a certain index to
1369
+ ;; a new value where the lookup-ref has been replaced. If there is an
1370
+ ;; error, it returns the `::error` value.
1371
+ lrr (lookup-ref-replacer search-context)
1372
+
1373
+ tuples (:tuples (nth (:rels search-context) relation-index))
1374
+ substituted-vars (substituted-vars-per-relation relation-index)
1375
+ filtered-vars (filtered-vars-per-relation relation-index)
1376
+ pattern-substitution-inds (map :tuple-element-index substituted-vars)
1377
+ pattern-filter-inds (map :tuple-element-index filtered-vars)
1378
+
1379
+ ;; This function returns a unique feature for the values at
1380
+ ;; `pattern-filter-inds` given a pattern.
1381
+ feature-extractor (index-feature-extractor pattern-filter-inds
1382
+ true
1383
+ lrr)
1384
+
1385
+ ;; These are the indices of the locations in the pattern that will be substituted
1386
+ ;; with values from the tuples in this relation.
1387
+ substitution-pattern-element-inds (map :pattern-element-index substituted-vars)
1388
+
1389
+ ;; This function maps the value at a pattern at a certain index to
1390
+ ;; a new value where the lookup-ref has been replaced. If there is an error,
1391
+ ;; an exception is thrown.
1392
+ lrr-ex (lookup-ref-replacer search-context nil)
1393
+
1394
+ ;; This constructs a new pattern given a tuple of values that will be inserted
1395
+ ;; at the `substitution-pattern-element-inds`.
1396
+ ;;
1397
+ ;; Precomputing this function moves some work out of the loop
1398
+ ;; and contributes to about 1½ seconds reduction in
1399
+ ;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
1400
+ pattern-from-tuple (vec-lookup-ref-replacer lrr-ex substitution-pattern-element-inds)
1401
+
1402
+ ;; This is a function that simply picks out a subset of the elements from a sequential
1403
+ ;; collection, at the indices `pattern-subsitution-inds`.
1404
+ ;;
1405
+ ;; Precomputing this function moves some work out of the loop
1406
+ ;; and contributes to about 2 seconds reduction in
1407
+ ;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
1408
+ select-pattern-substitution-inds (make-basic-index-selector pattern-substitution-inds)
1409
+
1410
+ ;; This is a list of pairs such that:
1411
+ ;;
1412
+ ;; * The first element is a pattern where variables for this relation have been substituted.
1413
+ ;; * The second element is a feature used for filtering the datoms after querying the backend
1414
+ ;;
1415
+ ;; Using a transducer here (with a transient vector under the hood)
1416
+ ;; is about ½ second faster than a doseq-loop that accumulates to
1417
+ ;; an ArrayList in the benchmark
1418
+ ;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
1419
+ ;; In other words, there is no use writing imperative code here
1420
+ ;; with Java mutable collections.
1421
+ substituted-pattern-and-filter-feature-pairs
1422
+ (into []
1423
+ (keep
1424
+ (fn [tuple]
1425
+ (let [feature (feature-extractor tuple)]
1426
+ (when (good-lookup-refs? feature)
1427
+ (when-let [k (-> tuple
1428
+ select-pattern-substitution-inds
1429
+ pattern-from-tuple)]
1430
+ [k feature])))))
1431
+ tuples)
1432
+
1433
+ filter-feature-extractor (index-feature-extractor
1434
+ (map :pattern-element-index filtered-vars)
1435
+ false
1436
+ lrr)]
1437
+
1438
+ ;; This expression will produce an `xform` that performs the substitutions for
1439
+ ;; this relation.
1440
+ (instantiate-substitution-xform substitution-pattern-element-inds
1441
+ filter-feature-extractor
1442
+ substituted-pattern-and-filter-feature-pairs)))
1443
+
1444
+ (defn search-context? [x]
1445
+ (assert (map? x))
1446
+ (let [{:keys [bsm clean-pattern rels strategy-vec]} x]
1447
+ (assert bsm)
1448
+ (assert clean-pattern)
1449
+ (assert rels)
1450
+ (assert strategy-vec))
1451
+ true)
1452
+
1453
+ (defn compute-per-rel-map [search-context rel-inds strat-symbol]
1454
+ {:pre [(search-context? search-context)]}
1455
+ (->> strat-symbol
1456
+ (search-index-mapping search-context)
1457
+ (filter (comp rel-inds :relation-index))
1458
+ (group-by :relation-index)))
1459
+
1460
+ (defn clean-pattern-before-substitution [pattern subst-map]
1461
+ (let [subst-pattern-positions (into #{}
1462
+ (comp cat (map :pattern-element-index))
1463
+ (vals subst-map))]
1464
+ (into []
1465
+ (map-indexed (fn [i x]
1466
+ (cond
1467
+ (subst-pattern-positions i) x
1468
+ (symbol? x) nil
1469
+ :else x)))
1470
+ pattern)))
1471
+
1472
+ (defn initialization-and-substitution-xform
1473
+ "Returns a transducer that performs all subsitutions possible given the relations with indices `rel-inds`."
1474
+ [search-context substituted-relation-inds]
1475
+ {:pre [(map? search-context)
1476
+ (set? substituted-relation-inds)]}
1477
+ (let [;; We refer to relations by their index in the vector in the context.
1478
+ substituted-vars-per-relation (compute-per-rel-map search-context
1479
+ substituted-relation-inds
1480
+ :substitute)
1481
+
1482
+ filtered-vars-per-relation (compute-per-rel-map search-context
1483
+ substituted-relation-inds
1484
+ :filter)
1485
+
1486
+ all-substitutions-xform (apply comp
1487
+ (map (fn [relation-index]
1488
+ (single-substitution-xform
1489
+ search-context
1490
+ relation-index
1491
+ substituted-vars-per-relation
1492
+ filtered-vars-per-relation))
1493
+ substituted-relation-inds))
1494
+ init-coll [[;; This is the initial pattern
1495
+ (clean-pattern-before-substitution
1496
+ (:clean-pattern search-context)
1497
+ substituted-vars-per-relation)
1498
+
1499
+ ;; This is the initial predicate (nil because there is no predicate)
1500
+ nil]]]
1501
+ [init-coll all-substitutions-xform]))
1502
+
1503
+ (defn datom-filter-predicate [filtered-relation-inds search-context]
1504
+ (let [filtered-vars-per-relation (compute-per-rel-map search-context filtered-relation-inds :filter)
1505
+ rels (:rels search-context)]
1506
+ (reduce (fn [predicate [relation-index filtered-vars]]
1507
+ (let [tuples (:tuples (nth rels relation-index))
1508
+ pos-inds (map :pattern-element-index filtered-vars)
1509
+ tup-inds (map :tuple-element-index filtered-vars)
1510
+ tuple-feature-extractor (index-feature-extractor tup-inds true)
1511
+ features (into #{}
1512
+ (map tuple-feature-extractor)
1513
+ tuples)
1514
+ datom-feature-extractor
1515
+ (index-feature-extractor pos-inds false)]
1516
+ (extend-predicate predicate
1517
+ datom-feature-extractor
1518
+ features)))
1519
+ nil
1520
+ filtered-vars-per-relation)))
1521
+
1522
+ (defn filter-from-predicate [pred]
1523
+ (if pred
1524
+ (filter pred)
1525
+ identity))
1526
+
1527
+ (defn backend-xform [backend-fn]
1528
+ (fn [step]
1529
+ (fn
1530
+ ([] (step))
1531
+ ([dst] (step dst))
1532
+ ([dst e a v tx added? datom-predicate]
1533
+ (let [inner-step (if datom-predicate
1534
+ (fn [dst datom]
1535
+ (if (datom-predicate datom)
1536
+ (step dst datom)
1537
+ dst))
1538
+ step)
1539
+ datoms (try
1540
+ (backend-fn e a v tx added?)
1541
+ (catch #?(:clj Exception :cljs js/Error) e
1542
+ (throw e)))]
1543
+ (reduce inner-step
1544
+ dst
1545
+ datoms))))))
1546
+
1547
+ (defn extend-predicate-for-pattern-constants
1548
+ [predicate {:keys [strategy-vec clean-pattern] :as search-context}]
1549
+ (let [inds (for [[i strategy pattern-value] (mapv vector (range)
1550
+ strategy-vec
1551
+ clean-pattern)
1552
+ :when (= :filter strategy)
1553
+ :when (and (some? pattern-value)
1554
+ (not (symbol? pattern-value)))]
1555
+ i)
1556
+ extractor (index-feature-extractor
1557
+ inds
1558
+ false
1559
+ (lookup-ref-replacer search-context))]
1560
+ (if extractor
1561
+ (extend-predicate predicate extractor #{(extractor clean-pattern)})
1562
+ predicate)))
1563
+
1564
+ (defn unpack6 [step]
1565
+ (fn
1566
+ ([] (step))
1567
+ ([dst] (step dst))
1568
+ ([dst [[e a v tx added?] filt]]
1569
+ (step dst e a v tx added? filt))))
1570
+
1571
+ (defn search-batch-fn
1572
+ "This function constructs a \"strategy function\" that gets called by `dbi/-batch-search.`"
1573
+ [search-context]
1574
+ (fn [strategy-vec backend-fn datom-xform]
1575
+ (let [search-context (merge search-context {:strategy-vec strategy-vec
1576
+ :backend-fn backend-fn})
1577
+
1578
+ ;; Relations with indices `substituted-relation-inds` are used for substituting variables
1579
+ ;; in the pattern.
1580
+ substituted-relation-inds (substitution-relation-indices search-context)
1581
+
1582
+ ;; Relations with indices `filtered-relation-inds` are used for filtering the datoms
1583
+ ;; returned by the search backend.
1584
+ filtered-relation-inds (filtering-relation-indices search-context substituted-relation-inds)
1585
+
1586
+ [init-coll substitution-xform] (initialization-and-substitution-xform
1587
+ search-context
1588
+ substituted-relation-inds)
1589
+
1590
+ filter-xform (-> filtered-relation-inds
1591
+ (datom-filter-predicate search-context)
1592
+ (extend-predicate-for-pattern-constants search-context)
1593
+ filter-from-predicate)
1594
+
1595
+ ;; This transduction will take the initial pattern,
1596
+ ;; perform all variable substitutions for all combinations
1597
+ ;; of relations and then look up the datoms in the index.
1598
+ ;; Finally, the datoms will be filtered for the variables
1599
+ ;; that were not substituted.
1600
+ result (into []
1601
+
1602
+ ;; From the output of `unpack6`
1603
+ ;; to the input of `backend-xform`
1604
+ ;; the transducers are higher-arity. That is,
1605
+ ;; instead of calling `(step acc [[e a v tx added?] pred])`,
1606
+ ;; they call `(step acc e a v tx added? pred)`. This avoids
1607
+ ;; the allocation of short-lived vectors and speeds up the
1608
+ ;; process by about 0.4 seconds in
1609
+ ;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
1610
+
1611
+ (comp
1612
+
1613
+ ;; Unpack the pattern as arguments to the next step function.
1614
+ unpack6
1615
+
1616
+ ;; Substitute variables with values
1617
+ ;; from tuples in the relations and accumulate the filter predicate.
1618
+ substitution-xform
1619
+
1620
+ ;; Perform the lookup in the search backend.
1621
+ (backend-xform backend-fn)
1622
+
1623
+ ;; Filter the datoms returned from the search backend.
1624
+ filter-xform
1625
+
1626
+ ;; Apply the provided datom-xform on the returned datoms
1627
+ datom-xform)
1628
+ init-coll)]
1629
+ result)))
1630
+
1631
+ (defn lookup-batch-search [source context orig-pattern pattern1]
1632
+ (let [new-rel (if (dbu/db? source)
1633
+ (let [rels (vec (:rels context))
1634
+ bsm (bound-symbol-map rels)
1635
+ clean-pattern (->> pattern1
1636
+ (replace-unbound-symbols-by-nil bsm)
1637
+ (resolve-pattern-eid source))
1638
+ search-context {:source source
1639
+ :bsm bsm
1640
+ :clean-pattern clean-pattern
1641
+ :rels rels}
1642
+
1643
+ datoms (if clean-pattern
1644
+
1645
+ ;; Make the call to the search backend
1646
+ (dbi/batch-search
1647
+ source clean-pattern
1648
+ (search-batch-fn search-context)
1649
+ (relation-from-datoms-xform))
1650
+
1651
+ [])
1652
+
1653
+ new-rel (relation-from-datoms
1654
+ context orig-pattern datoms)]
1655
+ new-rel)
1656
+ (lookup-pattern-coll source pattern1 orig-pattern))]
1657
+
1658
+ ;; This binding is needed for `collapse-rels` to work, and more specifically,
1659
+ ;; `hash-join` to work, that in turn depends on `getter-fn`.
1660
+ (binding [*lookup-attrs* (if (satisfies? dbi/IDB source)
1661
+ (dynamic-lookup-attrs source pattern1)
1662
+ *lookup-attrs*)]
1663
+ (cond-> (update context :rels collapse-rels new-rel)
1664
+ (:stats context) (assoc :tmp-stats {:type :lookup})))))
1665
+
1666
+ (defn -resolve-clause*
1667
+ ([context clause]
1668
+ (-resolve-clause* context clause clause))
1669
+ ([context clause orig-clause]
1670
+ (condp looks-like? clause
1671
+ [[symbol? '*]] ;; predicate [(pred ?a ?b ?c)]
1672
+ (do (check-all-bound context (identity (filter free-var? (first clause))) orig-clause)
1673
+ (filter-by-pred context clause))
1674
+
1675
+ [[symbol? '*] '_] ;; function [(fn ?a ?b) ?res]
1676
+ (bind-by-fn context clause)
1677
+
1678
+ [source? '*] ;; source + anything
1679
+ (let [[source-sym & rest] clause]
1680
+ (binding [*implicit-source* (get (:sources context) source-sym)]
1681
+ (-resolve-clause context rest clause)))
1682
+
1683
+ '[or *] ;; (or ...)
1684
+ (let [[_ & branches] clause
1685
+ context' (assoc context :stats [])
1686
+ contexts (mapv #(resolve-clause context' %) branches)
1687
+ sum-rel (->> contexts
1688
+ (map #(reduce hash-join (:rels %)))
1689
+ (reduce sum-rel))]
1690
+ (cond-> (assoc context :rels [sum-rel])
1691
+ (:stats context) (assoc :tmp-stats {:type :or
1692
+ :branches (mapv :stats contexts)})))
1693
+
1694
+ '[or-join [[*] *] *] ;; (or-join [[req-vars] vars] ...)
1695
+ (let [[_ [req-vars & vars] & branches] clause]
1696
+ (check-all-bound context req-vars orig-clause)
1697
+ (recur context (list* 'or-join (concat req-vars vars) branches) clause))
1698
+
1699
+ '[or-join [*] *] ;; (or-join [vars] ...)
1700
+ ;; TODO required vars
1701
+ (let [[_ vars & branches] clause
1702
+ vars (set vars)
1703
+ join-context (-> context
1704
+ (assoc :stats [])
1705
+ (limit-context vars))
1706
+ contexts (map #(-> join-context
1707
+ (resolve-clause %)
1708
+ (limit-context vars))
1709
+ branches)
1710
+ sum-rel (->> contexts
1711
+ (map #(reduce hash-join (:rels %)))
1712
+ (reduce sum-rel))]
1713
+ (cond-> (update context :rels collapse-rels sum-rel)
1714
+ (:stats context) (assoc :tmp-stats {:type :or-join
1715
+ :branches (mapv #(-> % :stats first) contexts)})))
1716
+
1717
+ '[and *] ;; (and ...)
1718
+ (let [[_ & clauses] clause]
1719
+ (if (:stats context)
1720
+ (let [and-context (-> context
1721
+ (assoc :stats [])
1722
+ (resolve-context clauses))]
1723
+ (assoc and-context
1724
+ :tmp-stats {:type :and
1725
+ :branches (:stats and-context)}
1726
+ :stats (:stats context)))
1727
+ (resolve-context context clauses)))
1728
+
1729
+ '[not *] ;; (not ...)
1730
+ (let [[_ & clauses] clause
1731
+ negation-vars (collect-vars clauses)
1732
+ _ (check-some-bound context negation-vars orig-clause)
1733
+ join-rel (reduce hash-join (:rels context))
1734
+ negation-context (-> context
1735
+ (assoc :rels [join-rel])
1736
+ (assoc :stats [])
1737
+ (resolve-context clauses))
1738
+ negation-join-rel (reduce hash-join (:rels negation-context))
1739
+ negation (subtract-rel join-rel negation-join-rel)]
1740
+ (cond-> (assoc context :rels [negation])
1741
+ (:stats context) (assoc :tmp-stats {:type :not
1742
+ :branches (:stats negation-context)})))
1743
+
1744
+ '[not-join [*] *] ;; (not-join [vars] ...)
1745
+ (let [[_ vars & clauses] clause
1746
+ _ (check-all-bound context vars orig-clause)
1747
+ join-rel (reduce hash-join (:rels context))
1748
+ negation-context (-> context
1749
+ (assoc :rels [join-rel])
1750
+ (assoc :stats [])
1751
+ (limit-context vars)
1752
+ (resolve-context clauses)
1753
+ (limit-context vars))
1754
+ negation-join-rel (reduce hash-join (:rels negation-context))
1755
+ negation (subtract-rel join-rel negation-join-rel)]
1756
+ (cond-> (assoc context :rels [negation])
1757
+ (:stats context) (assoc :tmp-stats {:type :not
1758
+ :branches (:stats negation-context)})))
1759
+
1760
+ '[*] ;; pattern
1761
+ (let [source *implicit-source*
1762
+ pattern0 (replace (:consts context) clause)
1763
+ pattern1 (resolve-pattern-lookup-refs source pattern0)]
1764
+ (lookup-batch-search source context clause pattern1)))))
1765
+
1766
+ (defn -resolve-clause
1767
+ ([context clause]
1768
+ (-resolve-clause context clause clause))
1769
+ ([context clause orig-clause]
1770
+ (dqs/update-ctx-with-stats context orig-clause
1771
+ (fn [context]
1772
+ (-resolve-clause* context clause orig-clause)))))
1773
+
1774
+ (defn resolve-clause [context clause]
1775
+ (if (rule? context clause)
1776
+ (if (source? (first clause))
1777
+ (binding [*implicit-source* (get (:sources context) (first clause))]
1778
+ (resolve-clause context (next clause)))
1779
+ (dqs/update-ctx-with-stats context clause
1780
+ (fn [context] (solve-rule context clause))))
1781
+ (-resolve-clause context clause)))
1782
+
1783
+ (defn -q [context clauses]
1784
+ (binding [*implicit-source* (get (:sources context) '$)]
1785
+ (dt/resolve-clauses resolve-clause context clauses)))
1786
+
1787
+ (defn -collect
1788
+ ([context symbols]
1789
+ (let [rels (:rels context)
1790
+ start-array (to-array (map #(get (:consts context) %) symbols))]
1791
+ (-collect [start-array] rels symbols)))
1792
+ ([acc rels symbols]
1793
+ (if-some [rel (first rels)]
1794
+ (let [keep-attrs (select-keys (:attrs rel) symbols)]
1795
+ (if (empty? keep-attrs)
1796
+ (recur acc (next rels) symbols)
1797
+ (let [copy-map (to-array (map #(get keep-attrs %) symbols))
1798
+ len (count symbols)]
1799
+ (recur (for [#?(:cljs t1
1800
+ :clj ^{:tag "[[Ljava.lang.Object;"} t1) acc
1801
+ t2 (:tuples rel)]
1802
+ (let [res (aclone t1)]
1803
+ (dotimes [i len]
1804
+ (when-some [idx (aget copy-map i)]
1805
+ (aset res i (get t2 idx))
1806
+ ;; TODO figure out why this array lookup does not work anymore (returns nil)
1807
+ #_(aset res i (#?(:cljs da/aget :clj get) t2 idx))))
1808
+ res))
1809
+ (next rels)
1810
+ symbols))))
1811
+ acc)))
1812
+
1813
+ (defprotocol IContextResolve
1814
+ (-context-resolve [var context]))
1815
+
1816
+ (extend-protocol IContextResolve
1817
+ Variable
1818
+ (-context-resolve [var context]
1819
+ (context-resolve-val context (.-symbol var)))
1820
+ SrcVar
1821
+ (-context-resolve [var context]
1822
+ (get-in context [:sources (.-symbol var)]))
1823
+ PlainSymbol
1824
+ (-context-resolve [var _]
1825
+ (or (get built-in-aggregates (.-symbol var))
1826
+ (resolve-sym (.-symbol var))))
1827
+ Constant
1828
+ (-context-resolve [var _]
1829
+ (.-value var)))
1830
+
1831
+ (defn -aggregate [find-elements context tuples]
1832
+ (mapv (fn [element fixed-value i]
1833
+ (if (instance? Aggregate element)
1834
+ (let [f (-context-resolve (:fn element) context)
1835
+ args (map #(-context-resolve % context) (butlast (:args element)))
1836
+ vals (map #(nth % i) tuples)]
1837
+ (apply f (concat args [vals])))
1838
+ fixed-value))
1839
+ find-elements
1840
+ (first tuples)
1841
+ (range)))
1842
+
1843
+ (defn- idxs-of [pred coll]
1844
+ (->> (map #(when (pred %1) %2) coll (range))
1845
+ (remove nil?)))
1846
+
1847
+ (defn aggregate [find-elements context resultset]
1848
+ (let [group-idxs (idxs-of (complement #(instance? Aggregate %)) find-elements)
1849
+ group-fn (fn [tuple]
1850
+ (map #(nth tuple %) group-idxs))
1851
+ grouped (group-by group-fn resultset)]
1852
+ (for [[_ tuples] grouped]
1853
+ (-aggregate find-elements context tuples))))
1854
+
1855
+ (defprotocol IPostProcess
1856
+ (-post-process [find tuples]))
1857
+
1858
+ (extend-protocol IPostProcess
1859
+ FindRel
1860
+ (-post-process [_ tuples]
1861
+ (if (seq? tuples) (vec tuples) tuples))
1862
+ FindColl
1863
+ (-post-process [_ tuples]
1864
+ (into [] (map first) tuples))
1865
+ FindScalar
1866
+ (-post-process [_ tuples]
1867
+ (ffirst tuples))
1868
+ FindTuple
1869
+ (-post-process [_ tuples]
1870
+ (first tuples)))
1871
+
1872
+ (defn- pull [find-elements context resultset]
1873
+ (let [resolved (for [find find-elements]
1874
+ (when (instance? Pull find)
1875
+ [(-context-resolve (:source find) context)
1876
+ (dpp/parse-pull
1877
+ (-context-resolve (:pattern find) context))]))]
1878
+ (for [tuple resultset]
1879
+ (mapv (fn [env el]
1880
+ (if env
1881
+ (let [[src spec] env]
1882
+ (dpa/pull-spec src spec [el] false))
1883
+ el))
1884
+ resolved
1885
+ tuple))))
1886
+
1887
+ (def ^:private query-cache (volatile! (datahike.lru/lru lru-cache-size)))
1888
+
1889
+ (defn memoized-parse-query [q]
1890
+ (if-some [cached (get @query-cache q nil)]
1891
+ cached
1892
+ (let [qp (parse q)]
1893
+ (vswap! query-cache assoc q qp)
1894
+ qp)))
1895
+
1896
+ (defn convert-to-return-maps [{:keys [mapping-type mapping-keys]} resultset]
1897
+ (let [mapping-keys (map #(get % :mapping-key) mapping-keys)
1898
+ convert-fn (fn [mkeys]
1899
+ (mapv #(zipmap mkeys %) resultset))]
1900
+ (condp = mapping-type
1901
+ :keys (convert-fn (map keyword mapping-keys))
1902
+ :strs (convert-fn (map str mapping-keys))
1903
+ :syms (convert-fn (map symbol mapping-keys)))))
1904
+
1905
+ (defn collect [context symbols]
1906
+ (->> (-collect context symbols)
1907
+ (map vec)))
1908
+
1909
+ (def default-settings {})
1910
+
1911
+ (defn raw-q [{:keys [query args offset limit stats? settings] :as _query-map}]
1912
+ (let [settings (merge default-settings settings)
1913
+ {:keys [qfind
1914
+ qwith
1915
+ qreturnmaps
1916
+ qin]} (memoized-parse-query query)
1917
+ context-in (-> (if stats?
1918
+ (StatContext. [] {} {} {} [] settings)
1919
+ (Context. [] {} {} {} settings))
1920
+ (resolve-ins qin args))
1921
+ ;; TODO utilize parser
1922
+
1923
+ all-vars (concat (dpi/find-vars qfind) (map :symbol qwith))
1924
+ context-out (-q context-in (:where query))
1925
+ resultset (collect context-out all-vars)
1926
+ find-elements (dpip/find-elements qfind)
1927
+ result-arity (count find-elements)]
1928
+ (cond->> (into #{}
1929
+ (comp (distinct)
1930
+ (if offset
1931
+ (drop offset)
1932
+ identity)
1933
+ (if (or (nil? limit) (neg? limit))
1934
+ identity
1935
+ (take limit)))
1936
+ resultset)
1937
+ (:with query) (mapv #(subvec % 0 result-arity))
1938
+ (some #(instance? Aggregate %) find-elements) (aggregate find-elements context-in)
1939
+ (some #(instance? Pull %) find-elements) (pull find-elements context-in)
1940
+ true (-post-process qfind)
1941
+ qreturnmaps (convert-to-return-maps qreturnmaps)
1942
+ stats? (#(-> context-out
1943
+ (dissoc :rels :sources :settings)
1944
+ (assoc :ret %
1945
+ :query query))))))