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,937 @@
1
+ (ns datahike.db.transaction
2
+ (:require
3
+ [clojure.spec.alpha :as s]
4
+ [clojure.string :as str]
5
+ [datahike.index :as di]
6
+ [datahike.datom :as dd :refer [datom datom-tx datom-added datom?]]
7
+ #?(:cljs [datahike.db :refer [HistoricalDB]])
8
+ [datahike.db.interface :as dbi]
9
+ [datahike.db.search :as dbs]
10
+ [datahike.db.utils :as dbu]
11
+ [datahike.constants :refer [tx0]]
12
+ [datahike.tools :refer [get-date raise]]
13
+ [datahike.schema :as ds]
14
+ [org.replikativ.persistent-sorted-set.arrays :as arrays])
15
+ #?(:cljs (:require-macros [datahike.datom :refer [datom]]
16
+ [datahike.tools :refer [raise]]))
17
+ #?(:clj (:import [clojure.lang ExceptionInfo]
18
+ [datahike.datom Datom]
19
+ [datahike.db HistoricalDB]
20
+ [java.util Date])))
21
+
22
+ (defn validate-datom [db ^Datom datom]
23
+ (when (and (datom-added datom)
24
+ (dbu/is-attr? db (.-a datom) :db/unique))
25
+ (when-let [found (not-empty (dbi/datoms db :avet [(.-a datom) (.-v datom)]))]
26
+ (raise "Cannot add " datom " because of unique constraint: " found
27
+ {:error :transact/unique :attribute (.-a datom) :datom datom}))))
28
+
29
+ (defn- validate-val [v [_ _ a _ _ :as at] {:keys [config schema ref-ident-map] :as db}]
30
+ (when (nil? v)
31
+ (raise "Cannot store nil as a value at " at
32
+ {:error :transact/syntax, :value v, :context at}))
33
+ (let [{:keys [attribute-refs? schema-flexibility]} config
34
+ a-ident (if (and attribute-refs? (number? a)) (dbi/-ident-for db a) a)
35
+ v-ident (if (and attribute-refs?
36
+ (contains? (dbi/-system-entities db) a)
37
+ (not (nil? (ref-ident-map v))))
38
+ (ref-ident-map v)
39
+ v)]
40
+
41
+ (when (= :write schema-flexibility)
42
+ (let [schema-spec (if (or (ds/meta-attr? a-ident) (ds/schema-attr? a-ident))
43
+ ds/implicit-schema-spec
44
+ schema)]
45
+ (when-not (ds/value-valid? a-ident v-ident schema)
46
+ (raise "Bad entity value " v-ident " at " at ", value does not match schema definition. Must be conform to: "
47
+ (ds/describe-type (get-in schema-spec [a-ident :db/valueType]))
48
+ {:error :transact/schema :value v-ident :attribute a-ident :schema (get-in db [:schema a-ident])}))))))
49
+
50
+ (defn- current-tx [report]
51
+ (inc (get-in report [:db-before :max-tx])))
52
+
53
+ (defn next-eid [db]
54
+ (inc (:max-eid db)))
55
+
56
+ (defn- #?@(:clj [^Boolean tx-id?]
57
+ :cljs [^boolean tx-id?])
58
+ [e]
59
+ (or (= e :db/current-tx)
60
+ (= e ":db/current-tx") ;; for datahike.js interop
61
+ (= e "datomic.tx")
62
+ (= e "datahike.tx")))
63
+
64
+ (defn- #?@(:clj [^Boolean tempid?]
65
+ :cljs [^boolean tempid?])
66
+ [x]
67
+ (or (and (number? x) (neg? x)) (string? x)))
68
+
69
+ (defn advance-max-eid [db eid]
70
+ (cond-> db
71
+ (and (> eid (:max-eid db))
72
+ (< eid tx0)) ;; do not trigger advance if transaction id was referenced
73
+ (assoc :max-eid eid)))
74
+
75
+ (defn- allocate-eid
76
+ ([report eid]
77
+ (update-in report [:db-after] advance-max-eid eid))
78
+ ([report e eid]
79
+ (cond-> report
80
+ (tx-id? e)
81
+ (assoc-in [:tempids e] eid)
82
+ (tempid? e)
83
+ (assoc-in [:tempids e] eid)
84
+ true
85
+ (update-in [:db-after] advance-max-eid eid))))
86
+
87
+ (defn update-schema [db ^Datom datom]
88
+ (let [schema (dbi/-schema db)
89
+ attribute-refs? (:attribute-refs? (dbi/-config db))
90
+ e (.-e datom)
91
+ a (.-a datom)
92
+ v (.-v datom)
93
+ a-ident (if attribute-refs? (dbi/-ident-for db a) a)
94
+ v-ident (if (and attribute-refs? (contains? (dbi/-system-entities db) v))
95
+ (dbi/-ident-for db v)
96
+ v)]
97
+ (when (and attribute-refs? (contains? (dbi/-system-entities db) e))
98
+ (raise "System schema entity cannot be changed"
99
+ {:error :transact/schema :entity-id e}))
100
+ (if (= a-ident :db/ident)
101
+ (if (schema v-ident)
102
+ (raise (str "Schema with attribute " v-ident " already exists")
103
+ {:error :transact/schema :attribute v-ident})
104
+ (-> (assoc-in db [:schema v-ident] (merge (or (schema e) {}) (hash-map a-ident v-ident)))
105
+ (assoc-in [:schema e] v-ident)
106
+ (assoc-in [:ident-ref-map v-ident] e)
107
+ (assoc-in [:ref-ident-map e] v-ident)))
108
+ (if-let [schema-entry (schema e)]
109
+ (if (schema schema-entry)
110
+ (update-in db [:schema schema-entry a-ident] (fn [old]
111
+ (if (ds/entity-spec-attr? a-ident)
112
+ (if old
113
+ (conj old v-ident)
114
+ [v-ident])
115
+ v-ident)))
116
+ (assoc-in db [:schema e a-ident] v-ident))
117
+ (assoc-in db [:schema e] (hash-map a-ident v-ident))))))
118
+
119
+ (defn update-rschema [db]
120
+ (assoc db :rschema (dbu/rschema (:schema db))))
121
+
122
+ (defn remove-schema [db ^Datom datom]
123
+ (let [schema (dbi/-schema db)
124
+ attribute-refs? (:attribute-refs? (dbi/-config db))
125
+ e (.-e datom)
126
+ a (.-a datom)
127
+ v (.-v datom)
128
+ a-ident (if attribute-refs? (dbi/-ident-for db a) a)
129
+ v-ident (if (and attribute-refs? (contains? (dbi/-system-entities db) v))
130
+ (dbi/-ident-for db v)
131
+ v)]
132
+ (when (and attribute-refs? (contains? (dbi/-system-entities db) e))
133
+ (raise "System schema entity cannot be changed"
134
+ {:error :retract/schema :entity-id e}))
135
+ (if (= a-ident :db/ident)
136
+ (if-not (schema v-ident)
137
+ (let [err-msg (str "Schema with attribute " v-ident " does not exist")
138
+ err-map {:error :retract/schema :attribute v-ident}]
139
+ (throw (ex-info err-msg err-map)))
140
+ (-> (assoc-in db [:schema e] (dissoc (schema v-ident) a-ident))
141
+ (update-in [:schema] #(dissoc % v-ident))
142
+ (update-in [:ident-ref-map] #(dissoc % v-ident))
143
+ (update-in [:ref-ident-map] #(dissoc % e))))
144
+ (if-let [schema-entry (schema e)]
145
+ (if (schema schema-entry)
146
+ (update-in db [:schema schema-entry] #(dissoc % a-ident))
147
+ (update-in db [:schema e] #(dissoc % a-ident v-ident)))
148
+ (let [err-msg (str "Schema with entity id " e " does not exist")
149
+ err-map {:error :retract/schema :entity-id e :attribute a :value e}]
150
+ (throw (ex-info err-msg err-map)))))))
151
+
152
+ ;; In context of `with-datom` we can use faster comparators which
153
+ ;; do not check for nil (~10-15% performance gain in `transact`)
154
+
155
+ (defn- with-datom [db ^Datom datom]
156
+ (validate-datom db datom)
157
+ (let [{a-ident :ident} (dbu/attr-info db (.-a datom))
158
+ indexing? (dbu/indexing? db a-ident)
159
+ schema? (or (ds/schema-attr? a-ident) (ds/entity-spec-attr? a-ident))
160
+ keep-history? (and (dbi/-keep-history? db) (not (dbu/no-history? db a-ident)))
161
+ op-count (:op-count db)]
162
+ (if (datom-added datom)
163
+ (cond-> db
164
+ true (update-in [:eavt] #(di/-insert % datom :eavt op-count))
165
+ true (update-in [:aevt] #(di/-insert % datom :aevt op-count))
166
+ indexing? (update-in [:avet] #(di/-insert % datom :avet op-count))
167
+ true (advance-max-eid (.-e datom))
168
+ true (update :hash + (hash datom))
169
+ schema? (-> (update-schema datom)
170
+ update-rschema)
171
+ true (update :op-count inc))
172
+
173
+ (if-some [removing ^Datom (first (dbi/search db [(.-e datom) (.-a datom) (.-v datom)]))]
174
+ (cond-> db
175
+ true (update-in [:eavt] #(di/-remove % removing :eavt op-count))
176
+ true (update-in [:aevt] #(di/-remove % removing :aevt op-count))
177
+ indexing? (update-in [:avet] #(di/-remove % removing :avet op-count))
178
+ true (update :hash - (hash removing))
179
+ schema? (-> (remove-schema datom) update-rschema)
180
+ keep-history? (update-in [:temporal-eavt] #(di/-temporal-insert % removing :eavt op-count))
181
+ keep-history? (update-in [:temporal-eavt] #(di/-temporal-insert % datom :eavt (inc op-count)))
182
+ keep-history? (update-in [:temporal-aevt] #(di/-temporal-insert % removing :aevt op-count))
183
+ keep-history? (update-in [:temporal-aevt] #(di/-temporal-insert % datom :aevt (inc op-count)))
184
+ keep-history? (update :hash + (hash datom))
185
+ (and keep-history? indexing?) (update-in [:temporal-avet] #(di/-temporal-insert % removing :avet op-count))
186
+ (and keep-history? indexing?) (update-in [:temporal-avet] #(di/-temporal-insert % datom :avet (inc op-count)))
187
+ true (update :op-count + (if (or keep-history? indexing?) 2 1)))
188
+ db))))
189
+
190
+ (defn- with-temporal-datom [db ^Datom datom]
191
+ (let [{a-ident :ident} (dbu/attr-info db (.-a datom))
192
+ indexing? (dbu/indexing? db a-ident)
193
+ schema? (ds/schema-attr? a-ident)
194
+ current-datom ^Datom (first (dbi/search db [(.-e datom) (.-a datom) (.-v datom)]))
195
+ history-datom ^Datom (first (dbs/search-temporal-indices db [(.-e datom) (.-a datom) (.-v datom) (.-tx datom)]))
196
+ current? (not (nil? current-datom))
197
+ history? (not (nil? history-datom))
198
+ op-count (:op-count db)]
199
+ (cond-> db
200
+ current? (update-in [:eavt] #(di/-remove % current-datom :eavt op-count))
201
+ current? (update-in [:aevt] #(di/-remove % current-datom :aevt op-count))
202
+ (and current? indexing?) (update-in [:avet] #(di/-remove % current-datom :avet op-count))
203
+ current? (update :hash - (hash current-datom))
204
+ (and current? schema?) (-> (remove-schema datom) update-rschema)
205
+ history? (update-in [:temporal-eavt] #(di/-remove % history-datom :eavt op-count))
206
+ history? (update-in [:temporal-aevt] #(di/-remove % history-datom :aevt op-count))
207
+ (and history? indexing?) (update-in [:temporal-avet] #(di/-remove % history-datom :avet op-count))
208
+ (or current? history?) (update :op-count inc))))
209
+
210
+ (defn- queue-tuple [queue tuple idx db e v]
211
+ (let [tuple-value (or (get queue tuple)
212
+ (:v (first (dbi/datoms db :eavt [e tuple])))
213
+ (vec (repeat (-> db (dbi/-schema) (get tuple) :db/tupleAttrs count) nil)))
214
+ tuple-value' (assoc tuple-value idx v)]
215
+ (assoc queue tuple tuple-value')))
216
+
217
+ (defn- queue-tuples
218
+ "Assuming the attribute we are concerned with is :a and its associated value is 'a',
219
+ returns {:a+b+c [a nil nil], :a+d [a, nil]}"
220
+ [queue tuples db e v]
221
+ (reduce-kv
222
+ (fn [queue tuple idx]
223
+ (queue-tuple queue tuple idx db e v))
224
+ queue
225
+ tuples))
226
+
227
+ (defn validate-datom-upsert [db ^Datom datom]
228
+ (when (dbu/is-attr? db (.-a datom) :db/unique)
229
+ (when-let [old (first (dbi/datoms db :avet [(.-a datom) (.-v datom)]))]
230
+ (when-not (= (.-e datom) (.-e ^Datom old))
231
+ (raise "Cannot add " datom " because of unique constraint: " old
232
+ {:error :transact/unique
233
+ :attribute (.-a datom)
234
+ :datom datom})))))
235
+
236
+ (defn- with-datom-upsert [db ^Datom datom]
237
+ (validate-datom-upsert db datom)
238
+ (let [indexing? (dbu/indexing? db (.-a datom))
239
+ {a-ident :ident} (dbu/attr-info db (.-a datom))
240
+ schema? (ds/schema-attr? a-ident)
241
+ keep-history? (and (dbi/-keep-history? db) (not (dbu/no-history? db a-ident))
242
+ (not= :db/txInstant a-ident))
243
+ op-count (:op-count db)
244
+ old-datom (first (di/-slice (:eavt db)
245
+ (dd/datom (.-e datom) (.-a datom) nil (.-tx datom))
246
+ (dd/datom (.-e datom) (.-a datom) nil (.-tx datom))
247
+ :eavt))]
248
+ (cond-> db
249
+ ;; Optimistic removal of the schema entry (because we don't know whether it is already present or not)
250
+ schema? (try
251
+ (-> db (remove-schema datom) update-rschema)
252
+ (catch ExceptionInfo _e
253
+ db))
254
+
255
+ keep-history? (update-in [:temporal-eavt] #(di/-temporal-upsert % datom :eavt op-count old-datom))
256
+ true (update-in [:eavt] #(di/-upsert % datom :eavt op-count old-datom))
257
+
258
+ keep-history? (update-in [:temporal-aevt] #(di/-temporal-upsert % datom :aevt op-count old-datom))
259
+ true (update-in [:aevt] #(di/-upsert % datom :aevt op-count old-datom))
260
+
261
+ (and keep-history? indexing?) (update-in [:temporal-avet] #(di/-temporal-upsert % datom :avet op-count old-datom))
262
+ indexing? (update-in [:avet] #(di/-upsert % datom :avet op-count old-datom))
263
+
264
+ true (update :op-count inc)
265
+ true (advance-max-eid (.-e datom))
266
+ true (update :hash + (hash datom))
267
+ schema? (-> (update-schema datom) update-rschema))))
268
+
269
+ (defn- transact-report
270
+ ([report datom] (transact-report report datom false))
271
+ ([report datom upsert?]
272
+ (let [db (:db-after report)
273
+ a (:a datom)
274
+ update-fn (if upsert? with-datom-upsert with-datom)
275
+ report' (-> report
276
+ (update-in [:db-after] update-fn datom)
277
+ (update-in [:tx-data] conj datom))]
278
+ (if (dbu/tuple-source? db a)
279
+ (let [e (:e datom)
280
+ v (if (datom-added datom) (:v datom) nil)
281
+ queue (or (-> report' ::queued-tuples (get e)) {})
282
+ tuples (get (dbi/-attrs-by db :db/attrTuples) a)
283
+ queue' (queue-tuples queue tuples db e v)]
284
+ (update report' ::queued-tuples assoc e queue'))
285
+ report'))))
286
+
287
+ (defn- check-upsert-conflict [entity acc]
288
+ (let [[e a v] acc
289
+ _e (:db/id entity)]
290
+ (if (or (nil? _e)
291
+ (tempid? _e)
292
+ (nil? acc)
293
+ (== _e e))
294
+ acc
295
+ (raise "Conflicting upsert: " [a v] " resolves to " e
296
+ ", but entity already has :db/id " _e
297
+ {:error :transact/upsert
298
+ :entity entity
299
+ :assertion acc}))))
300
+
301
+ (defn- upsert-eid [db entity tempids] ;; TODO: adjust to datascript?
302
+ (when-let [unique-idents (not-empty (dbi/-attrs-by db :db.unique/identity))]
303
+ (let [unique-tuple-idents (clojure.set/intersection
304
+ (dbi/-attrs-by db :db.type/tuple)
305
+ unique-idents)
306
+ found-eav
307
+ (reduce-kv
308
+ (fn [acc a-ident v-original] ;; acc = [e a v]
309
+ (if-not (contains? unique-idents a-ident)
310
+ acc
311
+ (let [a (if (:attribute-refs? (dbi/-config db))
312
+ (dbi/-ref-for db a-ident)
313
+ a-ident)
314
+ tempid-val (and (dbu/ref? db a-ident) (tempid? v-original))
315
+ v (if tempid-val
316
+ (tempids v-original)
317
+ v-original)]
318
+ (if-some [e (when v
319
+ (validate-val v [nil nil a v nil] db)
320
+ (:e (first (dbi/datoms db :avet [a v]))))]
321
+ (cond
322
+ (nil? acc) [e a v] ;; first upsert
323
+ (= (get acc 0) e) acc ;; second+ upsert, but does not conflict
324
+ :else
325
+ (let [[_e _a _v] acc]
326
+ (raise "Conflicting upserts: " [_a _v] " resolves to " _e
327
+ ", but " [a v] " resolves to " e
328
+ {:error :transact/upsert
329
+ :entity entity
330
+ :assertion [e a v]
331
+ :conflict [_e _a _v]})))
332
+ acc)))) ;; upsert attr, but resolves to nothing ;; non-upsert attr
333
+ nil
334
+ entity)
335
+
336
+ found-eav-including-composite-tuples
337
+ (reduce
338
+ (fn [acc a-tuple]
339
+ (let [tuple-attrs (get-in (dbi/-schema db) [a-tuple :db/tupleAttrs])
340
+ contains-tuple-attrs? (clojure.set/subset?
341
+ (set tuple-attrs)
342
+ (set (keys entity)))
343
+ tuple-contains-tempids? (and contains-tuple-attrs?
344
+ (some (fn [a] (and (dbu/ref? db a)
345
+ (tempid? (get entity a))))
346
+ tuple-attrs))
347
+ v-tuple (and contains-tuple-attrs?
348
+ (not tuple-contains-tempids?)
349
+ (mapv (fn [a]
350
+ (let [v (get entity a)]
351
+ (validate-val v [nil nil a v nil] db)
352
+ (if (dbu/ref? db a)
353
+ (dbu/entid-strict db v)
354
+ v)))
355
+ tuple-attrs))]
356
+ (if-let [e (and contains-tuple-attrs?
357
+ (not tuple-contains-tempids?)
358
+ (:e (first (dbi/datoms db :avet [a-tuple v-tuple]))))]
359
+ (cond
360
+ (nil? acc) [e a-tuple v-tuple] ;; first upsert
361
+ (= (get acc 0) e) acc ;; second+ upsert, but does not conflict
362
+ :else
363
+ (let [[_e _a _v] acc]
364
+ (raise "Conflicting upserts: " [_a _v] " resolves to " _e
365
+ ", but " [a-tuple v-tuple] " resolves to " e
366
+ {:error :transact/upsert
367
+ :entity entity
368
+ :assertion [e a-tuple v-tuple]
369
+ :conflict [_e _a _v]})))
370
+ acc))) ;; upsert attr, but resolves to nothing ;; non-upsert attr
371
+ found-eav
372
+ unique-tuple-idents)]
373
+ (->> found-eav-including-composite-tuples
374
+ (check-upsert-conflict entity)
375
+ first)))) ;; getting eid from acc
376
+
377
+ ;; multivals/reverse can be specified as coll or as a single value, trying to guess
378
+ (defn- maybe-wrap-multival [db a-ident vs]
379
+ (cond
380
+ ;; not a multival context
381
+ (not (or (dbu/reverse-ref? a-ident)
382
+ (dbu/multival? db a-ident)))
383
+ [vs]
384
+
385
+ ;; not a collection at all, so definitely a single value
386
+ (not (or (arrays/array? vs)
387
+ (and (coll? vs) (not (map? vs)))))
388
+ [vs]
389
+
390
+ ;; probably lookup ref, but not an entity spec
391
+ (and (= (count vs) 2)
392
+ (keyword? (first vs))
393
+ (dbu/is-attr? db (first vs) :db.unique/identity)
394
+ (not (ds/entity-spec-attr? a-ident)))
395
+ [vs]
396
+
397
+ :else vs))
398
+
399
+ (defn- explode [db entity]
400
+ (let [eid (:db/id entity)
401
+ attribute-refs? (:attribute-refs? (dbi/-config db))
402
+ _ (when (and attribute-refs? (contains? (dbi/-system-entities db) eid))
403
+ (raise "Entity with ID " eid " is a system attribute " (dbi/-ident-for db eid) " and cannot be changed"
404
+ {:error :transact/syntax, :eid eid, :attribute (dbi/-ident-for db eid) :context entity}))
405
+ ensure (:db/ensure entity)
406
+ entities (for [[a-ident vs] entity
407
+ :when (not (or (= a-ident :db/id) (= a-ident :db/ensure)))
408
+ :let [_ (dbu/validate-attr-ident a-ident {:db/id eid, a-ident vs} db)
409
+ reverse? (dbu/reverse-ref? a-ident)
410
+ straight-a-ident (if reverse? (dbu/reverse-ref a-ident) a-ident)
411
+ straight-a (if attribute-refs?
412
+ (dbi/-ref-for db straight-a-ident) ;; translation to datom format
413
+ straight-a-ident)
414
+ _ (when (and reverse? (not (dbu/ref? db straight-a-ident)))
415
+ (raise "Bad attribute " a-ident ": reverse attribute name requires {:db/valueType :db.type/ref} in schema"
416
+ {:error :transact/syntax, :attribute a-ident, :context {:db/id eid, a-ident vs}}))]
417
+ v (maybe-wrap-multival db a-ident vs)]
418
+ (if (and (dbu/ref? db straight-a-ident) (map? v)) ;; another entity specified as nested map
419
+ (assoc v (dbu/reverse-ref a-ident) eid)
420
+ (if reverse?
421
+ [:db/add v straight-a eid]
422
+ [:db/add eid straight-a
423
+ (if (and attribute-refs?
424
+ (dbu/is-attr? db straight-a-ident :db/systemAttribRef)
425
+ (ds/is-system-keyword? v)) ;; translation of system enums
426
+ (dbi/-ref-for db v)
427
+ v)])))]
428
+ (if ensure
429
+ (let [{:keys [:db.entity/attrs :db.entity/preds]} (-> db :schema ensure)]
430
+ (if (empty? attrs)
431
+ (if (empty? preds)
432
+ entities
433
+ (concat entities [[:db.ensure/preds eid ensure preds]]))
434
+ (if (empty? preds)
435
+ (concat entities [[:db.ensure/attrs eid ensure attrs]])
436
+ (concat entities [[:db.ensure/attrs eid ensure attrs]
437
+ [:db.ensure/preds eid ensure preds]]))))
438
+ entities)))
439
+
440
+ (defn- transact-add [{:keys [db-after] :as report} [_ e a v tx :as ent]]
441
+ (let [a (dbu/normalize-and-validate-attr a ent db-after)
442
+ _ (validate-val v ent db-after)
443
+ attribute-refs? (:attribute-refs? (dbi/-config db-after))
444
+ tx (or tx (current-tx report))
445
+ db db-after
446
+ e (dbu/entid-strict db e)
447
+ a-ident (if attribute-refs? (dbi/-ident-for db a) a)
448
+ v (if (dbu/ref? db a-ident) (dbu/entid-strict db v) v)
449
+ new-datom (datom e a v tx)
450
+ upsert? (not (dbu/multival? db a))]
451
+ (transact-report report new-datom upsert?)))
452
+
453
+ (defn- transact-retract-datom
454
+ ([report ^Datom d] (transact-retract-datom report d false))
455
+ ([report ^Datom d keep-tx-id]
456
+ (let [txid (or (and keep-tx-id (datom-tx d)) (current-tx report))]
457
+ (transact-report report (datom (.-e d) (.-a d) (.-v d) txid false)))))
458
+
459
+ (defn- transact-purge-datom [report ^Datom d]
460
+ (update-in report [:db-after] with-temporal-datom d))
461
+
462
+ (defn- retract-components [db datoms]
463
+ (into #{} (comp
464
+ (filter (fn [^Datom d] (dbu/component? db (.-a d))))
465
+ (map (fn [^Datom d] [:db.fn/retractEntity (.-v d)]))) datoms))
466
+
467
+ (defn- purge-components [db datoms]
468
+ (let [xf (comp
469
+ (filter (fn [^Datom d] (dbu/component? db (.-a d))))
470
+ (map (fn [^Datom d] [:db.purge/entity (.-v d)])))]
471
+ (into #{} xf datoms)))
472
+
473
+ (declare transact-tx-data)
474
+
475
+ (defn- retry-with-tempid [initial-report report es tempid upserted-eid]
476
+ (if (contains? (:tempids initial-report) tempid)
477
+ (raise "Conflicting upsert: " tempid " resolves"
478
+ " both to " upserted-eid " and " (get-in initial-report [:tempids tempid])
479
+ {:error :transact/upsert})
480
+ ;; try to re-run from the beginning
481
+ ;; but remembering that `tempid` will resolve to `upserted-eid`
482
+ (let [tempids' (-> (:tempids report)
483
+ (assoc tempid upserted-eid))
484
+ report' (assoc initial-report :tempids tempids')]
485
+ (transact-tx-data report' es))))
486
+
487
+ (defn assert-preds [db [_ e _ preds]]
488
+ #?(:cljs (throw (ex-info "tx predicate resolution is not supported in cljs at this time" {:e e :preds preds}))
489
+ :clj
490
+ (reduce
491
+ (fn [coll pred]
492
+ (if ((resolve pred) db e)
493
+ coll
494
+ (conj coll pred)))
495
+ #{} preds)))
496
+
497
+ (def builtin-op?
498
+ #{:db.fn/call
499
+ :db.fn/cas
500
+ :db/cas
501
+ :db/add
502
+ :db/retract
503
+ :db.fn/retractAttribute
504
+ :db.fn/retractEntity
505
+ :db/retractEntity
506
+ :db/purge
507
+ :db.ensure/attrs
508
+ :db.ensure/preds
509
+ :db.purge/entity
510
+ :db.purge/attribute
511
+ :db.history.purge/before})
512
+
513
+ (defn flush-tuples
514
+ "Generates all the add or retract operations needed for updating the states of composite tuples.
515
+ E.g., if '::queued-tuples' contains {100 {:a+b+c [123 nil nil]}}, this function creates this vector [:db/add 100 :a+b+c [123 nil nil]]"
516
+ [report]
517
+ (let [db (:db-after report)]
518
+ (reduce-kv
519
+ (fn [entities eid tuples+values]
520
+ (reduce-kv
521
+ (fn [entities tuple value]
522
+ (let [value (if (every? nil? value) nil value)
523
+ current (:v (first (dbi/datoms db :eavt [eid tuple])))]
524
+ (cond
525
+ (= value current) entities
526
+ ;; adds ::internal to meta-data to mean that these datoms were generated internally.
527
+ (nil? value) (conj entities ^::internal [:db/retract eid tuple current])
528
+ :else (conj entities ^::internal [:db/add eid tuple value]))))
529
+ entities
530
+ tuples+values))
531
+ []
532
+ (::queued-tuples report))))
533
+
534
+ (defn flush-tx-meta
535
+ "Generates add-operations for transaction meta data."
536
+ [{:keys [tx-meta db-before] :as report}]
537
+ (let [;; tx-meta (merge {:db/txInstant (get-date)} tx-meta)
538
+ tid (current-tx report)
539
+ {:keys [attribute-refs?]} (dbi/-config db-before)]
540
+ (reduce-kv
541
+ (fn [entities attribute value]
542
+ (let [straight-a (if attribute-refs? (dbi/-ref-for db-before attribute) attribute)]
543
+ (if (some? straight-a)
544
+ (conj entities
545
+ [:db/add
546
+ tid
547
+ straight-a
548
+ value
549
+ tid])
550
+ (raise "Bad transaction meta attribute " attribute " at " tx-meta ", not defined in system or current schema"
551
+ {:error :transact/schema :attribute attribute :context tx-meta}))))
552
+ []
553
+ tx-meta)))
554
+
555
+ (defn check-schema-update [db entity new-eid]
556
+ (when (ds/schema-entity? entity)
557
+ (when (and (contains? entity :db/ident)
558
+ (ds/is-system-keyword? (:db/ident entity)))
559
+ (raise "Using namespace 'db' for attribute identifiers is not allowed"
560
+ {:error :transact/schema :entity entity}))
561
+ (if-let [attr-name (get-in db [:schema new-eid])]
562
+ (when-let [invalid-updates (ds/find-invalid-schema-updates entity (get-in db [:schema attr-name]))]
563
+ (when-not (empty? invalid-updates)
564
+ (raise "Update not supported for these schema attributes"
565
+ {:error :transact/schema :entity entity :invalid-updates invalid-updates})))
566
+ (when (= :write (get-in db [:config :schema-flexibility]))
567
+ (when (or (:db/cardinality entity) (:db/valueType entity))
568
+ (when-not (ds/schema? entity)
569
+ (raise "Incomplete schema transaction attributes, expected :db/ident, :db/valueType, :db/cardinality"
570
+ {:error :transact/schema :entity entity})))))))
571
+
572
+ (defn entity-map->op-vec [db {:keys [tempids] :as report} entity]
573
+ (let [old-eid (:db/id entity)
574
+ tx? (tx-id? old-eid) ;; :db/current-tx / "datomic.tx"
575
+ resolved-eid (cond tx? (current-tx report)
576
+ (sequential? old-eid) (dbu/entid-strict db old-eid)
577
+ (keyword? old-eid) (dbu/entid-strict db [:db/ident old-eid])
578
+ :else old-eid)
579
+ updated-entity (assoc entity :db/id resolved-eid)
580
+ updated-report (cond-> report
581
+ tx? (allocate-eid old-eid resolved-eid))
582
+ resolved-tempid (tempids resolved-eid)
583
+ upserted-eid (upsert-eid db updated-entity tempids)]
584
+ (if (and (some? upserted-eid)
585
+ resolved-tempid
586
+ (not= upserted-eid resolved-tempid))
587
+ {:retry? true :old-eid resolved-eid :upserted-eid upserted-eid}
588
+ (let [new-eid (cond
589
+ (some? upserted-eid) upserted-eid
590
+ (nil? resolved-eid) (next-eid db)
591
+ (tempid? resolved-eid) (or resolved-tempid (next-eid db))
592
+ (number? resolved-eid) resolved-eid
593
+ :else (raise "Expected number, string, keyword or lookup ref for :db/id, got " old-eid
594
+ {:error :entity-id/syntax, :entity updated-entity}))
595
+ new-entity (assoc updated-entity :db/id new-eid)]
596
+ (check-schema-update db updated-entity new-eid)
597
+ {:new-report (allocate-eid updated-report resolved-eid new-eid)
598
+ :new-entities (explode db new-entity)}))))
599
+
600
+ (defn compare-and-swap [db report op-vec]
601
+ (let [[_ e a ov nv] op-vec
602
+ e (dbu/entid-strict db e)
603
+ _ (dbu/validate-attr a op-vec db)
604
+ nv (if (dbu/ref? db a) (dbu/entid-strict db nv) nv)
605
+ datoms (dbi/search db [e a])]
606
+ (if (nil? ov)
607
+ (if (empty? datoms)
608
+ [(transact-add report [:db/add e a nv]) []]
609
+ (raise ":db.fn/cas failed on datom [" e " " a " " (if (dbu/multival? db a) (map :v datoms) (:v (first datoms))) "], expected nil"
610
+ {:error :transact/cas, :old (if (dbu/multival? db a) datoms (first datoms)), :expected ov, :new nv}))
611
+ (let [ov (if (dbu/ref? db a) (dbu/entid-strict db ov) ov)]
612
+ (validate-val nv op-vec db)
613
+ (if (dbu/multival? db a)
614
+ (if (some (fn [^Datom d] (= (.-v d) ov)) datoms)
615
+ [(transact-add report [:db/add e a nv]) []]
616
+ (raise ":db.fn/cas failed on datom [" e " " a " " (map :v datoms) "], expected " ov
617
+ {:error :transact/cas, :old datoms, :expected ov, :new nv}))
618
+ (let [v (:v (first datoms))]
619
+ (if (= v ov)
620
+ [(transact-add report [:db/add e a nv]) []]
621
+ (raise ":db.fn/cas failed on datom [" e " " a " " v "], expected " ov
622
+ {:error :transact/cas, :old (first datoms), :expected ov, :new nv}))))))))
623
+
624
+ (defn retract-entity [db report op-vec]
625
+ (let [[_ e] op-vec]
626
+ (if-let [e (dbu/entid db e)]
627
+ (let [e-datoms (vec (dbi/search db [e]))
628
+ v-datoms (->> (dbi/-attrs-by db :db.type/ref)
629
+ (map (partial dbi/-ident-for db))
630
+ (mapcat (fn [a] (dbi/search db [nil a e])))
631
+ vec)]
632
+ [(reduce transact-retract-datom report (concat e-datoms v-datoms))
633
+ (retract-components db e-datoms)])
634
+ [report []])))
635
+
636
+ (defn check-tuple [db op-vec]
637
+ (let [[_ _ a v] op-vec
638
+ attr-schema (-> db dbi/-schema (get a))]
639
+ (cond (:db/tupleType attr-schema)
640
+ (cond (> (count v) 8)
641
+ (raise "Cannot store more than 8 values for homogeneous tuple: " op-vec
642
+ {:error :transact/syntax, :tx-data op-vec})
643
+
644
+ (not (apply = (map type v)))
645
+ (raise "Cannot store homogeneous tuple with values of different type: " op-vec
646
+ {:error :transact/syntax, :tx-data op-vec})
647
+
648
+ (not (s/valid? (-> db dbi/-schema a :db/tupleType) (first v)))
649
+ (raise "Cannot store homogeneous tuple. Values are of wrong type: " op-vec
650
+ {:error :transact/syntax, :tx-data op-vec}))
651
+ (:db/tupleTypes attr-schema)
652
+ (cond (not (= (count v) (count (:db/tupleTypes attr-schema))))
653
+ (raise (str "Cannot store heterogeneous tuple: expecting " (count (:db/tupleTypes attr-schema)) " values, got " (count v))
654
+ {:error :transact/syntax, :tx-data op-vec})
655
+
656
+ (not (apply = (map s/valid? (:db/tupleTypes attr-schema) v)))
657
+ (raise (str "Cannot store heterogeneous tuple: there is a mismatch between values " v " and their types " (:db/tupleTypes attr-schema))
658
+ {:error :transact/syntax, :tx-data op-vec}))
659
+ (and (:db/tupleAttrs attr-schema)
660
+ (not (::internal (meta op-vec))))
661
+ (raise "Can’t modify tuple attrs directly: " op-vec
662
+ {:error :transact/syntax, :tx-data op-vec}))))
663
+
664
+ (defn- filter-before [datoms ^Date before-date db]
665
+ (let [before-pred (fn [^Datom d]
666
+ (.before ^Date (.-v d) before-date))
667
+ filtered-tx-ids (dbu/filter-txInstant datoms before-pred db)]
668
+ (filter
669
+ (fn [^Datom d]
670
+ (contains? filtered-tx-ids (datom-tx d)))
671
+ datoms)))
672
+
673
+ (defn apply-db-op [db report op-vec]
674
+ (let [[op e a v] op-vec]
675
+ (case op
676
+
677
+ :db/add [(transact-add report op-vec) []]
678
+
679
+ :db/retract (if-some [e (dbu/entid db e)]
680
+ (let [a (dbu/normalize-and-validate-attr a op-vec db)
681
+ pattern (if (nil? v)
682
+ [e a]
683
+ (let [v (if (dbu/ref? db a) (dbu/entid-strict db v) v)]
684
+ (validate-val v op-vec db)
685
+ [e a v]))
686
+ datoms (vec (dbi/search db pattern))]
687
+ [(reduce transact-retract-datom report datoms) []])
688
+ [report []])
689
+
690
+ :db.fn/retractAttribute (if-let [e (dbu/entid db e)]
691
+ (let [a (dbu/normalize-and-validate-attr a op-vec db)
692
+ datoms (vec (dbi/search db [e a]))]
693
+ [(reduce transact-retract-datom report datoms)
694
+ (retract-components db datoms)])
695
+ [report []])
696
+
697
+ :db.fn/retractEntity (retract-entity db report op-vec)
698
+
699
+ :db/retractEntity (retract-entity db report op-vec)
700
+
701
+ :db/purge (if (dbi/-keep-history? db)
702
+ (let [history (HistoricalDB. db)]
703
+ (if-some [e (dbu/entid history e)]
704
+ (let [v (if (dbu/ref? history a) (dbu/entid-strict history v) v)
705
+ old-datoms (dbi/search history [e a v])]
706
+ [(reduce transact-purge-datom report old-datoms) []])
707
+ (raise "Can't find entity with ID " e " to be purged"
708
+ {:error :transact/purge, :operation op, :tx-data op-vec})))
709
+ (raise "Purge is only available in temporal databases."
710
+ {:error :transact/purge :operation op :tx-data op-vec}))
711
+
712
+ :db.purge/attribute (if (dbi/-keep-history? db)
713
+ (let [history (HistoricalDB. db)]
714
+ (if-let [e (dbu/entid history e)]
715
+ (let [datoms (vec (dbi/search history [e a]))]
716
+ [(reduce transact-purge-datom report datoms)
717
+ (purge-components history datoms)])
718
+ (raise "Can't find entity with ID " e " to be purged"
719
+ {:error :transact/purge, :operation op, :tx-data op-vec})))
720
+ (raise "Purge attribute is only available in temporal databases."
721
+ {:error :transact/purge :operation op :tx-data op-vec}))
722
+
723
+ :db.purge/entity (if (dbi/-keep-history? db)
724
+ (let [history (HistoricalDB. db)]
725
+ (if-let [e (dbu/entid history e)]
726
+ (let [e-datoms (vec (dbi/search history [e]))
727
+ v-datoms (vec (mapcat (fn [a] (dbi/search history [nil a e]))
728
+ (dbi/-attrs-by history :db.type/ref)))]
729
+ [(reduce transact-purge-datom report (concat e-datoms v-datoms))
730
+ (purge-components history e-datoms)])
731
+ (raise "Can't find entity with ID " e " to be purged"
732
+ {:error :transact/purge, :operation op, :tx-data op-vec})))
733
+ (raise "Purge entity is only available in temporal databases."
734
+ {:error :transact/purge :operation op :tx-data op-vec}))
735
+
736
+ :db.history.purge/before (if (dbi/-keep-history? db)
737
+ (let [history (HistoricalDB. db)
738
+ into-sorted-set #(apply sorted-set-by dd/cmp-datoms-eavt-quick %)
739
+ e-datoms (-> (clojure.set/difference
740
+ (into-sorted-set (dbs/search-temporal-indices db nil))
741
+ (into-sorted-set (dbs/search-current-indices db nil)))
742
+ (filter-before e db)
743
+ vec)]
744
+ [(reduce transact-purge-datom report e-datoms)
745
+ (purge-components history e-datoms)])
746
+ (raise "Purge entity is only available in temporal databases."
747
+ {:error :transact/purge :operation op :tx-data op-vec}))
748
+
749
+ :db.ensure/attrs (let [{:keys [tx-data]} report
750
+ asserting-datoms (filter (fn [^Datom d] (= e (.-e d))) tx-data)
751
+ asserting-attributes (map (fn [^Datom d] (.-a d)) asserting-datoms)
752
+ diff (clojure.set/difference (set v) (set asserting-attributes))]
753
+ (if (empty? diff)
754
+ [report []]
755
+ (raise "Entity " e " missing attributes " diff " of spec " a
756
+ {:error :transact/ensure :operation op :tx-data op-vec
757
+ :asserting-datoms asserting-datoms})))
758
+
759
+ :db.ensure/preds (let [{:keys [db-after]} report
760
+ preds (assert-preds db-after op-vec)]
761
+ (if-not (empty? preds)
762
+ (raise "Entity " e " failed predicates " preds " of spec " a
763
+ {:error :transact/ensure :operation op :tx-data op-vec})
764
+ [report []]))
765
+
766
+ :db.fn/cas (compare-and-swap db report op-vec)
767
+
768
+ :db/cas (compare-and-swap db report op-vec)
769
+
770
+ :db.fn/call (let [[_ f & args] op-vec]
771
+ [report (apply f db args)])
772
+
773
+ (if (and (keyword? op)
774
+ (not (builtin-op? op)))
775
+ (if-some [ident (dbu/entid db op)]
776
+ (let [fun (-> (dbi/search db [ident :db/fn]) first :v)
777
+ args (next op-vec)]
778
+ (if (fn? fun)
779
+ [report (apply fun db args)]
780
+ (raise "Entity " op " expected to have :db/fn attribute with fn? value"
781
+ {:error :transact/syntax, :operation :db.fn/call, :tx-data op-vec})))
782
+ (raise "Can’t find entity for transaction fn " op
783
+ {:error :transact/syntax, :operation :db.fn/call, :tx-data op-vec}))
784
+ (raise (str "Unknown operation at " op-vec ", expected " (str/join "," builtin-op?)
785
+ " or an ident corresponding to an installed transaction function"
786
+ " (e.g. {:db/ident <keyword> :db/fn <Ifn>}, usage of :db/ident requires {:db/unique :db.unique/identity} in schema)")
787
+ {:error :transact/syntax, :operation op, :tx-data op-vec})))))
788
+
789
+ (defn transact-tx-data [{:keys [db-before] :as initial-report} initial-es]
790
+ (when-not (or (nil? initial-es)
791
+ (sequential? initial-es))
792
+ (raise "Bad transaction data " initial-es ", expected sequential collection"
793
+ {:error :transact/syntax, :tx-data initial-es}))
794
+ (let [has-tuples? (seq (dbi/-attrs-by (:db-after initial-report) :db.type/tuple))
795
+ initial-es' (if has-tuples?
796
+ (interleave initial-es (repeat ::flush-tuples))
797
+ initial-es)
798
+ initial-report (update initial-report :tx-meta
799
+ #(merge {:db/txInstant (get-date)} %))
800
+ meta-entities (flush-tx-meta initial-report)]
801
+ (loop [report (update initial-report :db-after transient)
802
+ es (if (dbi/-keep-history? db-before)
803
+ (concat meta-entities
804
+ initial-es')
805
+ initial-es')]
806
+ (let [[entity & entities] es
807
+ {:keys [tempids db-after]} report
808
+ db db-after]
809
+ (cond
810
+ (empty? es)
811
+ (-> report
812
+ (assoc-in [:tempids :db/current-tx] (current-tx report))
813
+ (update-in [:db-after :max-tx] inc)
814
+ (update :db-after persistent!))
815
+
816
+ (nil? entity)
817
+ (recur report entities)
818
+
819
+ (= ::flush-tuples entity)
820
+ (if (contains? report ::queued-tuples)
821
+ (recur
822
+ (dissoc report ::queued-tuples)
823
+ (concat (flush-tuples report) entities))
824
+ (recur report entities))
825
+
826
+ (map? entity)
827
+ (let [{:keys [new-report new-entities retry? old-eid upserted-eid]} (entity-map->op-vec db report entity)]
828
+ (if retry?
829
+ (retry-with-tempid initial-report report initial-es old-eid upserted-eid)
830
+ (recur new-report (concat new-entities entities))))
831
+
832
+ (sequential? entity)
833
+ (let [[op e a v] entity]
834
+ (when (dbu/tuple? db a)
835
+ (check-tuple db entity))
836
+ (cond
837
+
838
+ (tx-id? e)
839
+ (recur (allocate-eid report e (current-tx report)) (cons [op (current-tx report) a v] entities))
840
+
841
+ (and (dbu/ref? db a) (tx-id? v))
842
+ (recur (allocate-eid report v (current-tx report)) (cons [op e a (current-tx report)] entities))
843
+
844
+ (tempid? e)
845
+ (if (not= op :db/add)
846
+ (raise "Can't use tempid in '" entity "'. Tempids are allowed in :db/add only"
847
+ {:error :transact/syntax, :op entity})
848
+ (let [upserted-eid (when (dbu/is-attr? db a :db.unique/identity)
849
+ (:e (first (dbi/datoms db :avet [a v]))))
850
+ allocated-eid (get tempids e)]
851
+ (if (and upserted-eid allocated-eid (not= upserted-eid allocated-eid))
852
+ (retry-with-tempid initial-report report initial-es e upserted-eid)
853
+ (let [eid (or upserted-eid allocated-eid (next-eid db))]
854
+ (recur (allocate-eid report e eid) (cons [op eid a v] entities))))))
855
+
856
+ (and (dbu/ref? db a) (tempid? v))
857
+ (if-let [vid (get tempids v)]
858
+ (recur report (cons [op e a vid] entities))
859
+ (recur (allocate-eid report v (next-eid db)) es))
860
+
861
+ :else
862
+ (let [[new-report new-entities] (apply-db-op db report entity)]
863
+ (recur new-report (concat new-entities entities)))))
864
+
865
+ (datom? entity)
866
+ (let [[e a v tx added] entity]
867
+ (if added
868
+ (recur (transact-add report [:db/add e a v tx]) entities)
869
+ (recur (transact-retract-datom report entity true) entities)))
870
+
871
+ :else
872
+ (raise "Bad entity type at " entity ", expected map or vector"
873
+ {:error :transact/syntax, :tx-data entity}))))))
874
+
875
+ (defn transact-entities-directly [initial-report initial-es]
876
+ (loop [report (update initial-report :db-after transient)
877
+ es initial-es
878
+ migration-state (or (get-in initial-report [:db-before :migration]) {})]
879
+ (let [[entity & entities] es
880
+ {:keys [config] :as db} (:db-after report)
881
+ [e a v t op] entity
882
+ a-ident (if (and (number? a) (:attribute-refs? config))
883
+ (dbi/-ident-for db a)
884
+ a)
885
+ a (if (:attribute-refs? config)
886
+ (dbi/-ref-for db a-ident)
887
+ (if (number? a)
888
+ (raise "Configuration mismatch: import data with attribute references can not be imported into a database with no attribute references."
889
+ {:error :import/mismatch :data entity})
890
+ a-ident))
891
+ max-eid (next-eid db)
892
+ max-tid (inc (get-in report [:db-after :max-tx]))]
893
+ (cond
894
+ (empty? es)
895
+ (-> report
896
+ (update-in [:db-after :max-tx] inc)
897
+ (update-in [:db-after :migration] #(if %
898
+ (merge % migration-state)
899
+ migration-state))
900
+ (update :db-after persistent!))
901
+
902
+ (= :db.install/attribute a-ident)
903
+ (recur report entities migration-state)
904
+
905
+ ;; meta entity
906
+ (ds/meta-attr? a-ident)
907
+ (let [new-t (get-in migration-state [:tids t] max-tid)
908
+ new-datom (dd/datom new-t a v new-t op)
909
+ new-e (.-e new-datom)
910
+ upsert? (not (dbu/multival? db a-ident))]
911
+ (recur (-> (transact-report report new-datom upsert?)
912
+ (assoc-in [:db-after :max-tx] max-tid))
913
+ entities
914
+ (-> migration-state
915
+ (assoc-in [:tids e] new-e)
916
+ (assoc-in [:eids e] new-e))))
917
+
918
+ ;; tx not added yet
919
+ (nil? (get-in migration-state [:tids t]))
920
+ (recur (update-in report [:db-after :max-tx] inc) es (assoc-in migration-state [:tids t] max-tid))
921
+
922
+ ;; ref not added yet
923
+ (and (dbu/ref? db a) (nil? (get-in migration-state [:eids v])))
924
+ (recur (allocate-eid report max-eid) es (assoc-in migration-state [:eids v] max-eid))
925
+
926
+ :else
927
+ (let [new-datom ^Datom (dd/datom
928
+ (or (get-in migration-state [:eids e]) max-eid)
929
+ a
930
+ (if (dbu/ref? db a)
931
+ (get-in migration-state [:eids v])
932
+ v)
933
+ (get-in migration-state [:tids t])
934
+ op)
935
+ upsert? (and (not (dbu/multival? db a-ident))
936
+ op)]
937
+ (recur (transact-report report new-datom upsert?) entities (assoc-in migration-state [:eids e] (.-e new-datom))))))))