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,239 @@
1
+ (ns ^:no-doc datahike.writer
2
+ (:require [superv.async :refer [S thread-try <?- go-try]]
3
+ [taoensso.timbre :as log]
4
+ [datahike.core]
5
+ [datahike.writing :as w]
6
+ [datahike.gc :as gc]
7
+ [datahike.tools :as dt :refer [throwable-promise get-time-ms]]
8
+ [clojure.core.async :refer [chan close! promise-chan put! go go-loop <! >! poll! buffer timeout]]
9
+ #?(:cljs [cljs.core.async.impl.channels :refer [ManyToManyChannel]]))
10
+ #?(:clj (:import [clojure.core.async.impl.channels ManyToManyChannel])))
11
+
12
+ (defn chan? [x]
13
+ (instance? ManyToManyChannel x))
14
+
15
+ (defprotocol PWriter
16
+ (-dispatch! [_ arg-map] "Returns a channel that resolves when the transaction finalizes.")
17
+ (-shutdown [_] "Returns a channel that resolves when the writer has shut down.")
18
+ (-streaming? [_] "Returns whether the transactor is streaming updates directly into the connection, so it does not need to fetch from store on read."))
19
+
20
+ (defrecord LocalWriter [thread streaming? transaction-queue-size commit-queue-size
21
+ transaction-queue commit-queue]
22
+ PWriter
23
+ (-dispatch! [_ arg-map]
24
+ (let [p (promise-chan)]
25
+ (put! transaction-queue (assoc arg-map :callback p))
26
+ p))
27
+ (-shutdown [_]
28
+ (close! transaction-queue)
29
+ thread)
30
+ (-streaming? [_] streaming?))
31
+
32
+ (def ^:const DEFAULT_QUEUE_SIZE 120000)
33
+
34
+ ;; minimum wait time between commits in ms
35
+ ;; this reduces write pressure on the storage
36
+ ;; at the cost of higher latency
37
+ (def ^:const DEFAULT_COMMIT_WAIT_TIME 0) ;; in ms
38
+
39
+ (defn create-thread
40
+ "Creates new transaction thread"
41
+ [connection write-fn-map transaction-queue-size commit-queue-size commit-wait-time]
42
+ (let [transaction-queue-buffer (buffer transaction-queue-size)
43
+ transaction-queue (chan transaction-queue-buffer)
44
+ commit-queue-buffer (buffer commit-queue-size)
45
+ commit-queue (chan commit-queue-buffer)]
46
+ [transaction-queue commit-queue
47
+ (#?(:clj thread-try :cljs try)
48
+ S
49
+ (do
50
+ ;; processing loop
51
+ (go-try S
52
+ ;; delay processing until the writer we are part of in connection is set
53
+ (while (not (:writer @(:wrapped-atom connection)))
54
+ (<! (timeout 10)))
55
+ (loop [old @(:wrapped-atom connection)]
56
+ (if-let [{:keys [op args callback] :as invocation} (<?- transaction-queue)]
57
+ (do
58
+ (when (> (count transaction-queue-buffer) (* 0.9 transaction-queue-size))
59
+ (log/warn "Transaction queue buffer more than 90% full, "
60
+ (count transaction-queue-buffer) "of" transaction-queue-size " filled."
61
+ "Reduce transaction frequency."))
62
+ (let [;; TODO remove this after import is ported to writer API
63
+ old (if-not (= (:max-tx old) (:max-tx @(:wrapped-atom connection)))
64
+ (assoc old :max-tx (:max-tx @(:wrapped-atom connection)))
65
+ old)
66
+
67
+ op-fn (write-fn-map op)
68
+ res (try
69
+ (apply op-fn old args)
70
+ ;; Catch all Throwables to handle AssertionError and other Errors
71
+ ;; These should crash the writer, but we deliver to callback first to prevent hangs
72
+ (catch #?(:clj Throwable :cljs js/Error) e
73
+ (log/error "Error during invocation" invocation e args)
74
+ ;; take a guess that a NPE was triggered by an invalid connection
75
+ ;; short circuit on errors
76
+ #?(:cljs (put! callback e)
77
+ :clj
78
+ (put! callback
79
+ (if (= (type e) NullPointerException)
80
+ (ex-info "Null pointer encountered in invocation. Connection may have been invalidated, e.g. through db deletion, and needs to be released everywhere."
81
+ {:type :writer-error-during-invocation
82
+ :invocation invocation
83
+ :connection connection
84
+ :error e})
85
+ e)))
86
+ ;; Re-throw Errors (AssertionError, OutOfMemoryError, etc.) to crash the writer
87
+ ;; Only Exceptions should be handled and allow the writer to continue
88
+ #?(:clj (when (instance? Error e)
89
+ (throw e)))
90
+ :error))]
91
+ (cond (chan? res)
92
+ ;; async op, run in parallel in background, no sequential commit handling needed
93
+ (do
94
+ (go (>! callback (<! res)))
95
+ (recur old))
96
+
97
+ (not= res :error)
98
+ (do
99
+ (when (> (count commit-queue-buffer) (/ commit-queue-size 2))
100
+ (log/warn "Commit queue buffer more than 50% full, "
101
+ (count commit-queue-buffer) "of" commit-queue-size " filled."
102
+ "Throttling transaction processing. Reduce transaction frequency and check your storage throughput.")
103
+ (<! (timeout 50)))
104
+ (put! commit-queue [res callback])
105
+ (recur (:db-after res)))
106
+ :else
107
+ (recur old))))
108
+ (do
109
+ (close! commit-queue)
110
+ (log/debug "Writer thread gracefully closed")))))
111
+ ;; commit loop
112
+ (go-try S
113
+ (loop [tx (<?- commit-queue)]
114
+ (when tx
115
+ (let [txs (into [tx] (take-while some?) (repeatedly #(poll! commit-queue)))]
116
+ ;; empty channel of pending transactions
117
+ (log/trace "Batched transaction count: " (count txs))
118
+ ;; commit latest tx to disk
119
+ (let [db (:db-after (first (peek txs)))]
120
+ (try
121
+ (let [start-ts (get-time-ms)
122
+ {{:keys [datahike/commit-id]} :meta
123
+ :as commit-db} (<?- (w/commit! db nil false))
124
+ commit-time (- (get-time-ms) start-ts)]
125
+ (log/trace "Commit time (ms): " commit-time)
126
+ (reset! connection commit-db)
127
+ ;; notify all processes that transaction is complete
128
+ (doseq [[tx-report callback] txs]
129
+ (let [tx-report (-> tx-report
130
+ (assoc-in [:tx-meta :db/commitId] commit-id)
131
+ (assoc :db-after commit-db))]
132
+ (>! callback tx-report))))
133
+ (catch #?(:clj Throwable :cljs js/Error) e
134
+ (doseq [[_ callback] txs]
135
+ (put! callback e))
136
+ (log/error "Writer thread shutting down because of commit error." e)
137
+ (close! commit-queue)
138
+ (close! transaction-queue)
139
+ ;; Re-throw Errors (AssertionError, OutOfMemoryError, etc.) to crash the writer
140
+ #?(:clj (when (instance? Error e)
141
+ (throw e)))))
142
+ (<! (timeout commit-wait-time))
143
+ (recur (<?- commit-queue)))))))))]))
144
+
145
+ ;; public API to internal mapping
146
+ (def default-write-fn-map {'transact! w/transact!
147
+ 'load-entities w/load-entities
148
+ ;; async operations that run in background
149
+ 'gc-storage! gc/gc-storage!})
150
+
151
+ (defmulti create-writer
152
+ (fn [writer-config _]
153
+ (:backend writer-config)))
154
+
155
+ (defmethod create-writer :self
156
+ [{:keys [transaction-queue-size commit-queue-size write-fn-map commit-wait-time]} connection]
157
+ (let [transaction-queue-size (or transaction-queue-size DEFAULT_QUEUE_SIZE)
158
+ commit-queue-size (or commit-queue-size DEFAULT_QUEUE_SIZE)
159
+ commit-wait-time (or commit-wait-time DEFAULT_COMMIT_WAIT_TIME)
160
+ [transaction-queue commit-queue thread]
161
+ (create-thread connection
162
+ (merge default-write-fn-map
163
+ write-fn-map)
164
+ transaction-queue-size
165
+ commit-queue-size
166
+ commit-wait-time)]
167
+ (map->LocalWriter
168
+ {:transaction-queue transaction-queue
169
+ :transaction-queue-size transaction-queue-size
170
+ :commit-queue commit-queue
171
+ :commit-queue-size commit-queue-size
172
+ :thread thread
173
+ :streaming? true})))
174
+
175
+ ;; Note: :kabel backend is implemented in datahike.kabel.writer
176
+ ;; Require that namespace to register the defmethod
177
+
178
+ (defn dispatch! [writer arg-map]
179
+ (-dispatch! writer arg-map))
180
+
181
+ (defn shutdown [writer]
182
+ (-shutdown writer))
183
+
184
+ (defn streaming? [writer]
185
+ (-streaming? writer))
186
+
187
+ (defn backend-dispatch [& args]
188
+ (get-in (first args) [:writer :backend] :self))
189
+
190
+ (defmulti create-database backend-dispatch)
191
+
192
+ (defmethod create-database :self [& args]
193
+ (let [p (throwable-promise)]
194
+ (go
195
+ (#?(:clj deliver :cljs put!) p (<! (apply w/create-database args))))
196
+ p))
197
+
198
+ (defmulti delete-database backend-dispatch)
199
+
200
+ (defmethod delete-database :self [& args]
201
+ (let [p (throwable-promise)]
202
+ (go
203
+ (let [res (<! (apply w/delete-database args))]
204
+ #?(:clj (deliver p res) :cljs (if (nil? res) (close! p) (put! p res)))))
205
+ p))
206
+
207
+ (defn transact!
208
+ [connection arg-map]
209
+ (let [p (throwable-promise)
210
+ writer (:writer @(:wrapped-atom connection))]
211
+ (go
212
+ (let [tx-report (<! (dispatch! writer
213
+ {:op 'transact!
214
+ :args [arg-map]}))]
215
+ (when (map? tx-report) ;; not error
216
+ (doseq [[_ callback] (some-> (:listeners (meta connection)) (deref))]
217
+ (callback tx-report)))
218
+ (#?(:clj deliver :cljs put!) p tx-report)))
219
+ p))
220
+
221
+ (defn load-entities [connection entities]
222
+ (let [p (throwable-promise)
223
+ writer (:writer @(:wrapped-atom connection))]
224
+ (go
225
+ (let [tx-report (<! (dispatch! writer
226
+ {:op 'load-entities
227
+ :args [entities]}))]
228
+ (#?(:clj deliver :cljs put!) p tx-report)))
229
+ p))
230
+
231
+ (defn gc-storage! [conn & args]
232
+ (let [p (throwable-promise)
233
+ writer (:writer @(:wrapped-atom conn))]
234
+ (go
235
+ (let [result (<! (dispatch! writer
236
+ {:op 'gc-storage!
237
+ :args (vec args)}))]
238
+ (#?(:clj deliver :cljs put!) p result)))
239
+ p))
@@ -0,0 +1,362 @@
1
+ (ns datahike.writing
2
+ "Manage all state changes and access to state of durable store."
3
+ (:require [datahike.connections :refer [delete-connection! *connections*]]
4
+ [datahike.db :as db]
5
+ [datahike.db.utils :as dbu]
6
+ [datahike.index :as di]
7
+ [datahike.store :as ds]
8
+ [datahike.tools :as dt]
9
+ [datahike.core :as core]
10
+ [datahike.config :as dc]
11
+ [datahike.schema-cache :as sc]
12
+ [datahike.online-gc :as online-gc]
13
+ [konserve.core :as k]
14
+ [konserve.store :as ks]
15
+ [taoensso.timbre :as log]
16
+ [hasch.core :refer [uuid squuid]]
17
+ [hasch.platform]
18
+ [clojure.core.async :as async :refer [go put!]]
19
+ [superv.async #?(:clj :refer :cljs :refer-macros) [go-try- <?-]]
20
+ [konserve.utils :refer [#?(:clj async+sync) multi-key-capable? *default-sync-translation*]
21
+ #?@(:cljs [:refer-macros [async+sync]])]))
22
+
23
+ ;; mapping to storage
24
+
25
+ (defn stored-db? [obj]
26
+ ;; TODO use proper schema to match?
27
+ (let [keys-to-check [:eavt-key :aevt-key :avet-key :config
28
+ :max-tx :max-eid :op-count :hash :meta]]
29
+ (= (count (select-keys obj keys-to-check))
30
+ (count keys-to-check))))
31
+
32
+ (defn get-and-clear-pending-kvs!
33
+ "Retrieves and clears pending key-value pairs from the store's pending-writes atom.
34
+ Assumes :pending-writes in store's storage holds an atom of a collection of [key value] pairs."
35
+ [store]
36
+ (let [pending-writes-atom (-> store :storage :pending-writes) ; Assumes :storage key holds the CachedStorage
37
+ kvs-to-write (atom [])]
38
+ (when pending-writes-atom
39
+ ;; Atomically get current KVs and reset the pending-writes atom.
40
+ (swap! pending-writes-atom (fn [old-kvs] (reset! kvs-to-write old-kvs) [])))
41
+ @kvs-to-write))
42
+
43
+ (defn db->stored
44
+ "Maps memory db to storage layout. Index flushes will add [k v] pairs to pending-writes."
45
+ [db flush?]
46
+ (when-not (dbu/db? db)
47
+ (dt/raise "Argument is not a database."
48
+ {:type :argument-is-not-a-db
49
+ :argument db}))
50
+ (let [{:keys [eavt aevt avet temporal-eavt temporal-aevt temporal-avet
51
+ schema rschema system-entities ident-ref-map ref-ident-map config
52
+ max-tx max-eid op-count hash meta store]} db
53
+ schema-meta {:schema schema
54
+ :rschema rschema
55
+ :system-entities system-entities
56
+ :ident-ref-map ident-ref-map
57
+ :ref-ident-map ref-ident-map}
58
+ schema-meta-key (uuid schema-meta)
59
+ backend (di/konserve-backend (:index config) store)
60
+ not-in-memory? (not= :memory (-> config :store :backend))
61
+ flush! (and flush? not-in-memory?)
62
+ ;; Prepare schema meta KV pair for writing, but don't write it here.
63
+ schema-meta-kv-to-write (when-not (sc/write-cache-has? (:store config) schema-meta-key)
64
+ (sc/add-to-write-cache (:store config) schema-meta-key)
65
+ [schema-meta-key schema-meta])]
66
+ (when-not (sc/cache-has? schema-meta-key)
67
+ (sc/cache-miss schema-meta-key schema-meta))
68
+ [schema-meta-kv-to-write ;; Return [key value] pair or nil
69
+ (merge
70
+ {:schema-meta-key schema-meta-key
71
+ :config config
72
+ :meta meta
73
+ :hash hash
74
+ :max-tx max-tx
75
+ :max-eid max-eid
76
+ :op-count op-count
77
+ ;; di/-flush will now add [k v] to pending-writes via CachedStorage
78
+ :eavt-key (cond-> eavt flush! (di/-flush backend))
79
+ :aevt-key (cond-> aevt flush! (di/-flush backend))
80
+ :avet-key (cond-> avet flush! (di/-flush backend))}
81
+ (when (:keep-history? config)
82
+ {:temporal-eavt-key (cond-> temporal-eavt flush! (di/-flush backend))
83
+ :temporal-aevt-key (cond-> temporal-aevt flush! (di/-flush backend))
84
+ :temporal-avet-key (cond-> temporal-avet flush! (di/-flush backend))}))]))
85
+
86
+ (defn stored->db
87
+ "Constructs in-memory db instance from stored map value."
88
+ [stored-db store]
89
+ (let [{:keys [eavt-key aevt-key avet-key
90
+ temporal-eavt-key temporal-aevt-key temporal-avet-key
91
+ schema rschema system-entities ref-ident-map ident-ref-map
92
+ config max-tx max-eid op-count hash meta schema-meta-key]
93
+ :or {op-count 0}} stored-db
94
+ schema-meta (or (sc/cache-lookup schema-meta-key)
95
+ ;; not in store in case we load an old db where the schema meta data was inline
96
+ (when-let [schema-meta (k/get store schema-meta-key nil {:sync? true})]
97
+ (sc/cache-miss schema-meta-key schema-meta)
98
+ schema-meta))
99
+ empty (db/empty-db nil config store)]
100
+ (merge
101
+ (assoc empty
102
+ :max-tx max-tx
103
+ :max-eid max-eid
104
+ :config config
105
+ :meta meta
106
+ :schema schema
107
+ :hash hash
108
+ :op-count op-count
109
+ :eavt eavt-key
110
+ :aevt aevt-key
111
+ :avet avet-key
112
+ :temporal-eavt temporal-eavt-key
113
+ :temporal-aevt temporal-aevt-key
114
+ :temporal-avet temporal-avet-key
115
+ :rschema rschema
116
+ :system-entities system-entities
117
+ :ident-ref-map ident-ref-map
118
+ :ref-ident-map ref-ident-map
119
+ :store store)
120
+ schema-meta)))
121
+
122
+ (defn branch-heads-as-commits [store parents]
123
+ (set (doall (for [p parents]
124
+ (do
125
+ (when (nil? p)
126
+ (dt/raise "Parent cannot be nil." {:type :parent-cannot-be-nil
127
+ :parent p}))
128
+ (if-not (keyword? p) p
129
+ (let [{{:keys [datahike/commit-id]} :meta :as old-db}
130
+ (k/get store p nil {:sync? true})]
131
+ (when-not old-db
132
+ (dt/raise "Parent does not exist in store."
133
+ {:type :parent-does-not-exist-in-store
134
+ :parent p}))
135
+ commit-id)))))))
136
+
137
+ (defn create-commit-id [db]
138
+ (let [{:keys [hash max-tx max-eid meta config]} db
139
+ content-uuid (uuid [hash max-tx max-eid meta])]
140
+ (if (:crypto-hash? config)
141
+ content-uuid
142
+ (squuid content-uuid)))) ;; Sequential UUID for better index locality
143
+
144
+ (defn write-pending-kvs!
145
+ "Writes a collection of key-value pairs to the store.
146
+ Handles synchronous and asynchronous writes.
147
+ Assumes it's called within a go-try- block if sync? is false."
148
+ [store kvs sync?]
149
+ (if sync?
150
+ (doseq [[k v] kvs]
151
+ (k/assoc store k v {:sync? true}))
152
+ (let [pending-ops (mapv (fn [[k v]] (k/assoc store k v {:sync? false})) kvs)]
153
+ (go-try- (doseq [op pending-ops] (<?- op))))))
154
+
155
+ (defn commit!
156
+ ([db parents]
157
+ (commit! db parents true))
158
+ ([db parents sync?]
159
+ (async+sync sync? *default-sync-translation*
160
+ (go-try-
161
+ (let [{:keys [store config]} db
162
+ parents (or parents #{(get config :branch)})
163
+ parents (branch-heads-as-commits store parents)
164
+ cid (create-commit-id db)
165
+ db (-> db
166
+ (assoc-in [:meta :datahike/commit-id] cid)
167
+ (assoc-in [:meta :datahike/parents] parents))
168
+ ;; db->stored now returns [schema-meta-kv-to-write db-to-store]
169
+ ;; and index flushes will have populated pending-writes
170
+ [schema-meta-kv-to-write db-to-store] (db->stored db true)
171
+ ;; Get all pending [k v] pairs (e.g., from index flushes)
172
+ pending-kvs (get-and-clear-pending-kvs! store)]
173
+
174
+ (if (multi-key-capable? store)
175
+ (let [[meta-key meta-val] schema-meta-kv-to-write
176
+ writes-map (cond-> (into {} pending-kvs) ; Initialize with pending KVs
177
+ schema-meta-kv-to-write (assoc meta-key meta-val)
178
+ true (assoc cid db-to-store)
179
+ true (assoc (:branch config) db-to-store))]
180
+ (<?- (k/multi-assoc store writes-map {:sync? sync?})))
181
+ ;; Then write schema-meta, commit-log, branch
182
+ (let [[meta-key meta-val] schema-meta-kv-to-write
183
+ schema-meta-written (when schema-meta-kv-to-write
184
+ (k/assoc store meta-key meta-val {:sync? sync?}))
185
+
186
+ ;; Make sure all pointed to values are written before the commit log and branch
187
+ _ (when schema-meta-kv-to-write (<?- schema-meta-written))
188
+ _ (<?- (write-pending-kvs! store pending-kvs sync?))
189
+
190
+ commit-log-written (k/assoc store cid db-to-store {:sync? sync?})
191
+ branch-written (k/assoc store (:branch config) db-to-store {:sync? sync?})]
192
+ (when-not sync?
193
+ (<?- commit-log-written)
194
+ (<?- branch-written))))
195
+
196
+ ;; Online GC: delete freed addresses after writes are committed
197
+ (when (get-in config [:online-gc :enabled?])
198
+ (<?- (online-gc/online-gc! store (assoc (:online-gc config) :sync? false))))
199
+
200
+ db)))))
201
+
202
+ (defn complete-db-update [old tx-report]
203
+ (let [{:keys [writer]} old
204
+ {:keys [db-after]
205
+ {:keys [db/txInstant]} :tx-meta} tx-report
206
+ new-meta (assoc (:meta db-after) :datahike/updated-at txInstant)
207
+ db (assoc db-after :meta new-meta :writer writer)
208
+ tx-report (assoc tx-report :db-after db)]
209
+ tx-report))
210
+
211
+ (defprotocol PDatabaseManager
212
+ (-create-database [config opts])
213
+ (-delete-database [config])
214
+ (-database-exists? [config]))
215
+
216
+ (defn -database-exists?* [config]
217
+ (let [p (dt/throwable-promise)]
218
+ (go
219
+ (put! p (try
220
+ (let [config (dc/load-config config)
221
+ store-config (:store config)
222
+ ;; First check if store exists (avoids exception when store not in registry)
223
+ store-exists? (<?- (ks/store-exists? store-config {:sync? false}))]
224
+ (if store-exists?
225
+ ;; Store exists, now check if it contains a database
226
+ (let [raw-store (<?- (ks/connect-store store-config {:sync? false}))
227
+ store (ds/add-cache-and-handlers raw-store config)
228
+ stored-db (<?- (k/get store :db nil {:sync? false}))]
229
+ ;; Release store and await completion
230
+ (<?- (ks/release-store store-config store {:sync? false}))
231
+ (some? stored-db))
232
+ ;; Store doesn't exist, so database doesn't exist
233
+ false))
234
+ (catch #?(:clj Exception :cljs js/Error) e
235
+ e))))
236
+ p))
237
+
238
+ (defn -create-database* [config deprecated-config]
239
+ (go-try-
240
+ (let [opts {:sync? false}
241
+ {:keys [keep-history?] :as config} (dc/load-config config deprecated-config)
242
+ store-config (:store config)
243
+ store (ds/add-cache-and-handlers (<?- (ks/create-store store-config opts)) config)
244
+ stored-db (<?- (k/get store :db nil opts))
245
+ _ (when stored-db
246
+ (dt/raise "Database already exists."
247
+ {:type :db-already-exists :config store-config}))
248
+ {:keys [eavt aevt avet temporal-eavt temporal-aevt temporal-avet
249
+ schema rschema system-entities ref-ident-map ident-ref-map
250
+ config max-tx max-eid op-count hash meta] :as db}
251
+ (db/empty-db nil config store)
252
+ backend (di/konserve-backend (:index config) store)
253
+ cid (create-commit-id db)
254
+ meta (assoc meta :datahike/commit-id cid)
255
+ schema-meta {:schema schema
256
+ :rschema rschema
257
+ :system-entities system-entities
258
+ :ident-ref-map ident-ref-map
259
+ :ref-ident-map ref-ident-map}
260
+ schema-meta-key (uuid schema-meta)
261
+ ;; di/-flush calls will populate pending-writes via CachedStorage
262
+ db-to-store (merge {:max-tx max-tx
263
+ :max-eid max-eid
264
+ :op-count op-count
265
+ :hash hash
266
+ :schema-meta-key schema-meta-key
267
+ :config (update config :initial-tx (comp not empty?))
268
+ :meta meta
269
+ :eavt-key (di/-flush eavt backend)
270
+ :aevt-key (di/-flush aevt backend)
271
+ :avet-key (di/-flush avet backend)}
272
+ (when keep-history?
273
+ {:temporal-eavt-key (di/-flush temporal-eavt backend)
274
+ :temporal-aevt-key (di/-flush temporal-aevt backend)
275
+ :temporal-avet-key (di/-flush temporal-avet backend)}))]
276
+ ;;we just created the first data base in this store, so the write cache is empty
277
+ (<?- (k/assoc store schema-meta-key schema-meta opts))
278
+ (sc/add-to-write-cache (:store config) schema-meta-key)
279
+ (when-not (sc/cache-has? schema-meta-key)
280
+ (sc/cache-miss schema-meta-key schema-meta))
281
+
282
+ ;; Process pending KVs from index flushes synchronously
283
+ (let [pending-kvs (get-and-clear-pending-kvs! store)]
284
+ (<?- (write-pending-kvs! store pending-kvs false)))
285
+
286
+ (<?- (k/assoc store :branches #{:db} opts))
287
+ (<?- (k/assoc store cid db-to-store opts))
288
+ (<?- (k/assoc store :db db-to-store opts))
289
+ (ks/release-store store-config store)
290
+ config)))
291
+
292
+ (defn -delete-database* [config]
293
+ (go-try-
294
+ (let [config (dc/load-config config {})
295
+ config-store-id (ds/store-identity (:store config))
296
+ active-conns (filter (fn [[store-id _branch]]
297
+ (= store-id config-store-id))
298
+ (keys @*connections*))]
299
+ (sc/clear-write-cache (:store config))
300
+ (doseq [conn active-conns]
301
+ (log/warn "Deleting database without releasing all connections first: " conn "."
302
+ "All connections will be released now, but this cannot be ensured for remote readers.")
303
+ (delete-connection! conn))
304
+ (ks/delete-store (:store config)))))
305
+
306
+ (extend-protocol PDatabaseManager
307
+ #?(:clj String :cljs string)
308
+ (-create-database #?(:clj [uri & opts] :cljs [uri opts])
309
+ (-create-database (dc/uri->config uri) opts))
310
+
311
+ (-delete-database [uri]
312
+ (-delete-database (dc/uri->config uri)))
313
+
314
+ (-database-exists? [uri]
315
+ (-database-exists? (dc/uri->config uri)))
316
+
317
+ #?(:clj clojure.lang.IPersistentMap :cljs PersistentArrayMap)
318
+ (-database-exists? [config]
319
+ (-database-exists?* config))
320
+ (-create-database [config opts]
321
+ (-create-database* config opts))
322
+ (-delete-database [config]
323
+ (-delete-database* config))
324
+
325
+ #?(:cljs PersistentHashMap)
326
+ #?(:cljs
327
+ (-database-exists? [config]
328
+ (-database-exists?* config)))
329
+ #?(:cljs (-create-database [config opts] (-create-database* config opts)))
330
+ #?(:cljs (-delete-database [config] (-delete-database* config))))
331
+
332
+ ;; public API
333
+
334
+ (defn create-database
335
+ ([]
336
+ (-create-database {} nil))
337
+ ([config & opts]
338
+ (-create-database config opts)))
339
+
340
+ (defn delete-database
341
+ ([]
342
+ (-delete-database {}))
343
+ ;;deprecated
344
+ ([config]
345
+ ;; TODO log deprecation notice with #54
346
+ (-delete-database config)))
347
+
348
+ (defn database-exists?
349
+ ([]
350
+ (-database-exists? {}))
351
+ ([config]
352
+ ;; TODO log deprecation notice with #54
353
+ (-database-exists? config)))
354
+
355
+ (defn transact! [old {:keys [tx-data tx-meta]}]
356
+ (log/debug "Transacting" (count tx-data) "objects")
357
+ (log/trace "Transaction data" tx-data "with meta:" tx-meta)
358
+ (complete-db-update old (core/with old tx-data tx-meta)))
359
+
360
+ (defn load-entities [old entities]
361
+ (log/debug "Loading" (count entities) " entities.")
362
+ (complete-db-update old (core/load-entities-with old entities nil)))
package/src/deps.cljs ADDED
@@ -0,0 +1 @@
1
+ {:externs ["datahike/externs.js"]}
@@ -0,0 +1,76 @@
1
+ (ns ^:no-doc datahike.index.hitchhiker-tree.insert
2
+ (:require [hitchhiker.tree :as tree]
3
+ [hitchhiker.tree.op :as op]))
4
+
5
+ (defn mask [new indices]
6
+ (reduce (fn [mask pos]
7
+ (assoc mask pos (nth new pos)))
8
+ [nil nil nil nil]
9
+ indices))
10
+
11
+ (defn equals-at-indices?
12
+ "Returns true if 'k1' and 'k2' are equals at positions given in 'indices'."
13
+ [indices k1 k2]
14
+ (reduce (fn [_ i]
15
+ (if (= (nth k2 i) (nth k1 i))
16
+ true
17
+ (reduced false)))
18
+ true
19
+ indices))
20
+
21
+ (defn exists-old?
22
+ "Returns true if 'new' already exists in 'old-keys'."
23
+ [old-keys new]
24
+ (when (seq old-keys)
25
+ (let [indices [0 1 2]
26
+ mask (mask new indices)]
27
+ (when-let [candidates (subseq old-keys >= mask)]
28
+ (->> candidates
29
+ ffirst
30
+ (equals-at-indices? indices new))))))
31
+
32
+ (defrecord InsertOp [key op-count]
33
+ op/IOperation
34
+ (-insertion-ts [_] op-count)
35
+ (-affects-key [_] key)
36
+ (-apply-op-to-coll [_ kvs]
37
+ (if (exists-old? kvs key)
38
+ kvs
39
+ (assoc kvs key nil)))
40
+ (-apply-op-to-tree [_ tree]
41
+ (let [children (cond
42
+ (tree/data-node? tree) (:children tree)
43
+ :else (:children (peek (tree/lookup-path tree key))))]
44
+ (if (exists-old? children key)
45
+ tree
46
+ (tree/insert tree key nil)))))
47
+
48
+ (defrecord temporal-InsertOp [key op-count]
49
+ op/IOperation
50
+ (-insertion-ts [_] op-count)
51
+ (-affects-key [_] key)
52
+ (-apply-op-to-coll [_ kvs]
53
+ (assoc kvs key nil))
54
+ (-apply-op-to-tree [_ tree]
55
+ (tree/insert tree key nil)))
56
+
57
+ (defn new-InsertOp [key op-count]
58
+ (InsertOp. key op-count))
59
+
60
+ (defn new-temporal-InsertOp [key op-count]
61
+ (temporal-InsertOp. key op-count))
62
+
63
+ (defn add-insert-handler
64
+ "Tells the store how to deserialize insert related operations"
65
+ [store]
66
+ (swap! (:read-handlers store)
67
+ merge
68
+ {'datahike.index.hitchhiker_tree.insert.InsertOp
69
+ ;; TODO Remove ts when Wanderung is available.
70
+ (fn [{:keys [key op-count ts]}]
71
+ (map->InsertOp {:key key :op-count (or op-count ts)}))
72
+
73
+ 'datahike.index.hitchhiker_tree.insert.temporal_InsertOp
74
+ (fn [{:keys [key op-count ts]}]
75
+ (map->temporal-InsertOp {:key key :op-count (or op-count ts)}))})
76
+ store)