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,376 @@
1
+ (ns datahike.test.model.core
2
+ "Pure model of Datahike semantics for model-based testing.
3
+
4
+ This namespace implements a pure Clojure model of how Datahike
5
+ should behave, without any side effects. It can be used to:
6
+
7
+ 1. Generate valid sequences of transactions
8
+ 2. Track expected state after each transaction
9
+ 3. Compare expected state against actual Datahike state
10
+
11
+ The model handles:
12
+ - Cardinality semantics (one vs many)
13
+ - Type constraints (string, long, ref)
14
+ - Historical state tracking (for as-of queries)
15
+ - Index content tracking (EAVT, AVET, AEVT)"
16
+ (:require [datahike.test.model.rng :as rng]))
17
+
18
+ ;; =============================================================================
19
+ ;; Pattern Matching (for remove-by-pattern)
20
+ ;; =============================================================================
21
+
22
+ (defn- matches
23
+ "Helper that treats nil as wildcard."
24
+ [x0 x]
25
+ (or (nil? x0) (= x0 x)))
26
+
27
+ (defn predicate-from-pattern
28
+ "Returns a predicate function for a pattern.
29
+ Nil in any position matches anything."
30
+ [[e0 a0 v0]]
31
+ (fn [[e a v]]
32
+ (and (matches e0 e)
33
+ (matches a0 a)
34
+ (matches v0 v))))
35
+
36
+ (defn remove-pattern
37
+ "Filter a set of datoms by removing those matching pattern."
38
+ [datom-set pattern]
39
+ (into #{} (remove (predicate-from-pattern pattern)) datom-set))
40
+
41
+ ;; =============================================================================
42
+ ;; Model State
43
+ ;; =============================================================================
44
+
45
+ (defrecord Model
46
+ [;; Schema: {attr-ident {:db/valueType, :db/cardinality, :db/index, ...}}
47
+ schema
48
+ ;; Current datoms: #{[e a v]}
49
+ current-datoms
50
+ ;; Historical states: {tx #{[e a v]}}
51
+ ;; Maps transaction ID to state at that transaction
52
+ history
53
+ ;; Transaction counter
54
+ next-tx])
55
+
56
+ (defn create-model
57
+ "Create initial model state with given schema."
58
+ [schema]
59
+ (->Model schema #{} {} 536870912))
60
+
61
+ ;; =============================================================================
62
+ ;; Core Semantics: Accumulate Datom
63
+ ;; =============================================================================
64
+
65
+ (defn lookup-attribute
66
+ "Look up attribute info from schema."
67
+ [schema a]
68
+ (or (get schema a)
69
+ (throw (ex-info (str "Attribute not found: " a) {:attribute a}))))
70
+
71
+ (defn accumulate-datom
72
+ "Apply a single datom to the model state.
73
+
74
+ This is the core of the model - it implements Datahike's
75
+ cardinality semantics in pure Clojure:
76
+
77
+ - :db.cardinality/one: Replaces existing value for [e a]
78
+ - :db.cardinality/many: Accumulates values, no duplicates
79
+
80
+ Returns updated datom set."
81
+ [schema datom-set [e a v added]]
82
+ (if added
83
+ (let [attr (lookup-attribute schema a)
84
+ cardinality (or (:db/cardinality attr) :db.cardinality/one)]
85
+ (case cardinality
86
+ :db.cardinality/many
87
+ (if (some (predicate-from-pattern [e a v]) datom-set)
88
+ datom-set
89
+ (conj datom-set [e a v]))
90
+ :db.cardinality/one
91
+ (-> datom-set
92
+ (remove-pattern [e a nil])
93
+ (conj [e a v]))))
94
+ (remove-pattern datom-set [e a v])))
95
+
96
+ (defn apply-tx
97
+ "Apply a transaction to the model, returning updated model.
98
+
99
+ Transaction format: {:tx-data [[op e a v] ...]}
100
+ where op is :db/add or :db/retract.
101
+
102
+ Also records historical state for as-of queries."
103
+ [model {:keys [tx-data] :as _tx}]
104
+ (let [tx-id (:next-tx model)
105
+ ;; For accumulate-datom, we need [e a v added] (4 elements)
106
+ datoms-for-accumulate (mapv (fn [[op e a v]]
107
+ [e a v (= op :db/add)])
108
+ tx-data)
109
+ new-current (reduce
110
+ (fn [acc datom]
111
+ (accumulate-datom (:schema model) acc datom))
112
+ (:current-datoms model)
113
+ datoms-for-accumulate)
114
+ new-history (assoc (:history model) tx-id new-current)]
115
+ (assoc model
116
+ :current-datoms new-current
117
+ :history new-history
118
+ :next-tx (inc tx-id))))
119
+
120
+ (defn apply-txs
121
+ "Apply multiple transactions sequentially."
122
+ [model txs]
123
+ (reduce apply-tx model txs))
124
+
125
+ ;; =============================================================================
126
+ ;; Querying the Model
127
+ ;; =============================================================================
128
+
129
+ (defn get-current-datoms
130
+ "Get current datoms as a set of [e a v] tuples."
131
+ [model]
132
+ (:current-datoms model))
133
+
134
+ (defn get-datoms-at-tx
135
+ "Get datoms as of a specific transaction."
136
+ [model tx-id]
137
+ (get (:history model) tx-id))
138
+
139
+ (defn get-transaction-ids
140
+ "Get all transaction IDs in order."
141
+ [model]
142
+ (sort (keys (:history model))))
143
+
144
+ ;; =============================================================================
145
+ ;; Index Computation
146
+ ;; =============================================================================
147
+
148
+ (defn compute-eavt
149
+ "Compute EAVT index from model (sorted by [e a v])."
150
+ [model]
151
+ (sort-by identity (get-current-datoms model)))
152
+
153
+ (defn compute-aevt
154
+ "Compute AEVT index from model (sorted by [a e v])."
155
+ [model]
156
+ (sort-by (fn [[e a v]] [a e v]) (get-current-datoms model)))
157
+
158
+ (defn indexed-attrs
159
+ "Get set of indexed attribute identifiers from schema."
160
+ [schema]
161
+ (set (keep (fn [[k v]] (when (:db/index v) k)) schema)))
162
+
163
+ (defn ref-attrs
164
+ "Get set of ref-type attribute identifiers from schema."
165
+ [schema]
166
+ (set (keep (fn [[k v]] (when (= :db.type/ref (:db/valueType v)) k)) schema)))
167
+
168
+ (defn compute-avet
169
+ "Compute AVET index from model.
170
+
171
+ Only includes datoms where:
172
+ - Attribute has :db/index true, OR
173
+ - Attribute is :db.type/ref"
174
+ [model]
175
+ (let [schema (:schema model)
176
+ indexed (indexed-attrs schema)
177
+ refs (ref-attrs schema)
178
+ indexed-or-ref? (fn [a]
179
+ (or (contains? indexed a)
180
+ (contains? refs a)))]
181
+ (sort-by (fn [[e a v]] [a v e])
182
+ (filter (fn [[_e a _v]] (indexed-or-ref? a))
183
+ (get-current-datoms model)))))
184
+
185
+ ;; =============================================================================
186
+ ;; Transaction Generation - Weighted Action Sampling
187
+ ;; =============================================================================
188
+
189
+ (def action-weights
190
+ "Weights for each action type in transaction generation.
191
+ :add - Add entirely new random datom
192
+ :remove - Retract an existing datom
193
+ :tweak - Take existing datom, generate new value for same entity/attribute
194
+ :combine - Take two existing datoms, mix their entity/value parts
195
+ :transact - End current transaction (emit boundary)"
196
+ {:add 4
197
+ :remove 2
198
+ :tweak 1
199
+ :combine 1
200
+ :transact 1})
201
+
202
+ (defn- generate-value
203
+ "Generate a random value for the given attribute type."
204
+ [rng value-type entity-range]
205
+ (case value-type
206
+ :db.type/string (str "v" (rng/next-int rng 100))
207
+ :db.type/long (long (rng/next-int rng 100))
208
+ :db.type/ref (rng/rand-nth-rng rng entity-range)
209
+ :db.type/boolean (rng/next-boolean rng)
210
+ :db.type/keyword (keyword (str "k" (rng/next-int rng 20)))
211
+ (rng/next-long rng)))
212
+
213
+ (defn- generate-add
214
+ "Generate a random :db/add operation."
215
+ [rng schema entity-range]
216
+ (let [attr-idents (vec (keys schema))
217
+ attr-id (rng/rand-nth-rng rng attr-idents)
218
+ attr (get schema attr-id)
219
+ value-type (:db/valueType attr)
220
+ e (rng/rand-nth-rng rng entity-range)
221
+ v (generate-value rng value-type entity-range)]
222
+ [:db/add e attr-id v]))
223
+
224
+ (defn- generate-remove
225
+ "Generate a :db/retract operation from existing datoms."
226
+ [rng current-datoms]
227
+ (let [[e a v] (rng/rand-nth-rng rng (vec current-datoms))]
228
+ [:db/retract e a v]))
229
+
230
+ (defn- generate-tweak
231
+ "Generate a :db/add that modifies an existing datom with a new value.
232
+ Exercises cardinality-one replacement and upsert semantics."
233
+ [rng schema entity-range current-datoms]
234
+ (let [[e a _old-v] (rng/rand-nth-rng rng (vec current-datoms))
235
+ attr (get schema a)
236
+ value-type (:db/valueType attr)
237
+ new-v (generate-value rng value-type entity-range)]
238
+ [:db/add e a new-v]))
239
+
240
+ (defn- generate-combine
241
+ "Generate a :db/add by mixing entity or value from two datoms with same attribute.
242
+ Creates interesting patterns while preserving type safety.
243
+ Returns nil if no valid combination exists."
244
+ [rng current-datoms]
245
+ (when (seq current-datoms)
246
+ (let [by-attr (group-by second current-datoms)
247
+ valid-groups (filter #(<= 2 (count (second %))) by-attr)]
248
+ (when (seq valid-groups)
249
+ (let [[_ datoms-with-same-attr] (rng/rand-nth-rng rng (vec valid-groups))
250
+ [e1 a _v1] (rng/rand-nth-rng rng (vec datoms-with-same-attr))
251
+ [e2 _a v2] (rng/rand-nth-rng rng (vec datoms-with-same-attr))
252
+ idx (rng/next-int rng 2)
253
+ e (case idx 0 e1 1 e2)
254
+ v (case idx 0 v2 1 v2)]
255
+ [:db/add e a v])))))
256
+
257
+ (defn- sample-action
258
+ "Sample an action type based on weights and current state.
259
+ When no datoms exist, only :add or :transact are valid."
260
+ [rng current-datoms]
261
+ (if (seq current-datoms)
262
+ (rng/weighted-sample-rng rng action-weights)
263
+ (rng/weighted-sample-rng rng {:add 4 :transact 1})))
264
+
265
+ (defn- datom-equivalence
266
+ "Returns the equivalence key for a datom.
267
+ Two datoms are equivalent if they would conflict in the same transaction.
268
+ For cardinality-one: same [e a]
269
+ For cardinality-many: same [e a v]"
270
+ [schema [e a v]]
271
+ (let [attr (get schema a)
272
+ cardinality (or (:db/cardinality attr) :db.cardinality/one)]
273
+ (case cardinality
274
+ :db.cardinality/many [e a v]
275
+ :db.cardinality/one [e a])))
276
+
277
+ (defn- op-equivalence
278
+ "Returns the equivalence key for an operation [op e a v].
279
+ Ignores the op type - add and retract of same datom are equivalent."
280
+ [schema [_op e a v]]
281
+ (datom-equivalence schema [e a v]))
282
+
283
+ (defn generate-transaction
284
+ "Generate a single transaction using weighted action sampling.
285
+
286
+ Returns tx-ops: a vector of [op e a v] operations.
287
+
288
+ The transaction ends when:
289
+ - :transact action is sampled (probabilistic boundary)
290
+ - max-ops limit is reached
291
+
292
+ Uses an internal datom set to track state during generation, so operations
293
+ can reference datoms from previous operations within the same transaction.
294
+ Does NOT update model state - caller should apply the transaction.
295
+
296
+ Avoids generating conflicting operations within the same transaction
297
+ (e.g., add and retract of same datom, or two adds of same cardinality-one
298
+ attribute with same entity but different values)."
299
+ [rng schema entity-range model-state max-ops]
300
+ (let [initial-datoms (get-current-datoms model-state)]
301
+ (letfn [(accumulate [datoms [e a v added?]]
302
+ (if added?
303
+ (let [attr (lookup-attribute schema a)
304
+ cardinality (or (:db/cardinality attr) :db.cardinality/one)]
305
+ (case cardinality
306
+ :db.cardinality/many
307
+ (if (some (predicate-from-pattern [e a v]) datoms)
308
+ datoms
309
+ (conj datoms [e a v]))
310
+ :db.cardinality/one
311
+ (-> datoms
312
+ (remove-pattern [e a nil])
313
+ (conj [e a v]))))
314
+ (remove-pattern datoms [e a v])))
315
+
316
+ (apply-op-to-datoms [datoms [op e a v]]
317
+ (accumulate datoms [e a v (= op :db/add)]))
318
+
319
+ (equiv-key [op]
320
+ (op-equivalence schema op))
321
+
322
+ (step [ops current-datoms current-equiv n]
323
+ (if (>= n max-ops)
324
+ (vec ops)
325
+ (let [action (sample-action rng current-datoms)]
326
+ (case action
327
+ :transact
328
+ (vec ops)
329
+
330
+ :add
331
+ (let [op (generate-add rng schema entity-range)
332
+ equiv (equiv-key op)]
333
+ (if (contains? current-equiv equiv)
334
+ ;; Skip conflicting op, try again with same n
335
+ (recur ops current-datoms current-equiv n)
336
+ ;; Accept op
337
+ (let [current-datoms' (apply-op-to-datoms current-datoms op)]
338
+ (recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
339
+
340
+ :remove
341
+ (let [op (generate-remove rng current-datoms)
342
+ equiv (equiv-key op)]
343
+ (if (contains? current-equiv equiv)
344
+ (recur ops current-datoms current-equiv n)
345
+ (let [current-datoms' (apply-op-to-datoms current-datoms op)]
346
+ (recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
347
+
348
+ :tweak
349
+ (let [op (generate-tweak rng schema entity-range current-datoms)
350
+ equiv (equiv-key op)]
351
+ (if (contains? current-equiv equiv)
352
+ (recur ops current-datoms current-equiv n)
353
+ (let [current-datoms' (apply-op-to-datoms current-datoms op)]
354
+ (recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
355
+
356
+ :combine
357
+ (if-let [op (generate-combine rng current-datoms)]
358
+ (let [equiv (equiv-key op)]
359
+ (if (contains? current-equiv equiv)
360
+ (recur ops current-datoms current-equiv n)
361
+ (let [current-datoms' (apply-op-to-datoms current-datoms op)]
362
+ (recur (conj ops op) current-datoms' (conj current-equiv equiv) (inc n)))))
363
+ ;; combine failed, skip this iteration
364
+ (recur ops current-datoms current-equiv (inc n)))))))]
365
+ (step [] initial-datoms #{} 0))))
366
+
367
+ (defn generate-tx-batch
368
+ "Generate a batch of transaction operations.
369
+
370
+ Deprecated: Use generate-transaction for weighted action sampling.
371
+ Kept for backward compatibility."
372
+ ([rng schema entity-range max-ops]
373
+ (generate-tx-batch rng schema entity-range max-ops nil))
374
+ ([rng schema entity-range max-ops model-state]
375
+ (let [n (inc (rng/next-int rng max-ops))]
376
+ (vec (repeatedly n #(generate-add rng schema entity-range))))))
@@ -0,0 +1,142 @@
1
+ (ns datahike.test.model.invariant
2
+ "Invariant checking framework for datahike model-based testing."
3
+ (:require [clojure.set :as set]
4
+ [datahike.api :as d]
5
+ [datahike.datom :as dd]
6
+ [datahike.db.interface :as dbi]
7
+ [datahike.test.model.core :as model]))
8
+
9
+ (defprotocol PInvariant
10
+ (check [this model-state actual-db])
11
+ (invariant-name [_this]))
12
+
13
+ (defn check-all
14
+ "Check all invariants against model and actual DB."
15
+ [invariants model-state actual-db]
16
+ (let [violations (keep #(check % model-state actual-db) invariants)]
17
+ (if (empty? violations)
18
+ {:valid? true}
19
+ {:valid? false :violations (vec violations)})))
20
+
21
+ ;; =============================================================================
22
+ ;; Helper Functions
23
+ ;; =============================================================================
24
+
25
+ (defn- eav-triplet-set
26
+ "Convert datoms to a set of [e a v] tuples, filtering to user-attrs.
27
+
28
+ Handles both :attribute-refs? true and false:
29
+ - When true, :a is entity ID, uses dbi/-ident-for to convert
30
+ - When false, :a is already a keyword ident"
31
+ [db user-attrs datoms]
32
+ (let [attr-refs? (:attribute-refs? (.-config db))]
33
+ (into #{}
34
+ (comp (filter (if attr-refs?
35
+ (comp user-attrs #(dbi/-ident-for db %) :a)
36
+ (comp user-attrs :a)))
37
+ (map (if attr-refs?
38
+ (juxt :e #(dbi/-ident-for db (:a %)) :v)
39
+ (juxt :e :a :v))))
40
+ datoms)))
41
+
42
+ ;; =============================================================================
43
+ ;; Index Sortedness Invariant
44
+ ;; =============================================================================
45
+
46
+ (defrecord IndexSortedInvariant [index-type]
47
+ PInvariant
48
+ (check [_this _model-state actual-db]
49
+ (let [actual-datoms (into [] (d/datoms actual-db index-type))
50
+ cmp-fn (dd/index-type->cmp-quick index-type true)
51
+ pairs (partition 2 1 actual-datoms)]
52
+ (when-not (every?
53
+ (fn [[a b]] (not (pos? (cmp-fn a b))))
54
+ pairs)
55
+ {:violation :index-unsorted
56
+ :evidence {:index-type index-type
57
+ :sample (take 10 actual-datoms)}})))
58
+ (invariant-name [_this]
59
+ (str (name index-type) "-sorted")))
60
+
61
+ ;; =============================================================================
62
+ ;; Index Content Invariant
63
+ ;; =============================================================================
64
+
65
+ (defrecord IndexContentInvariant [index-type user-attrs]
66
+ PInvariant
67
+ (check [_this model-state actual-db]
68
+ (let [actual-datoms (eav-triplet-set actual-db user-attrs (d/datoms actual-db index-type))
69
+ expected-datoms (case index-type
70
+ :eavt (set (model/compute-eavt model-state))
71
+ :aevt (set (model/compute-aevt model-state))
72
+ :avet (set (model/compute-avet model-state))
73
+ (throw (ex-info "Unknown index" {:index-type index-type})))
74
+ missing (set/difference expected-datoms actual-datoms)
75
+ extra (set/difference actual-datoms expected-datoms)]
76
+ (when (or (seq missing) (seq extra))
77
+ {:violation :index-content-mismatch
78
+ :evidence {:index-type index-type
79
+ :missing (vec (take 10 missing))
80
+ :extra (vec (take 10 extra))}})))
81
+ (invariant-name [_this]
82
+ (str (name index-type) "-content")))
83
+
84
+ ;; =============================================================================
85
+ ;; Historical Consistency Invariant
86
+ ;; =============================================================================
87
+
88
+ (defrecord HistoricalConsistencyInvariant [actual-tx-ids tx-offset user-attrs]
89
+ PInvariant
90
+ (check [_this model-state actual-db]
91
+ (let [violations
92
+ (for [actual-tx actual-tx-ids
93
+ :let [model-tx (- actual-tx tx-offset)
94
+ expected (model/get-datoms-at-tx model-state model-tx)
95
+ actual (try
96
+ (eav-triplet-set (d/as-of actual-db actual-tx) user-attrs (d/datoms (d/as-of actual-db actual-tx) :eavt))
97
+ (catch Exception _ nil))
98
+ missing (when actual (set/difference expected actual))
99
+ extra (when actual (set/difference actual expected))]
100
+ :when (and actual (or (seq missing) (seq extra)))]
101
+ {:tx-id actual-tx
102
+ :model-tx model-tx
103
+ :missing (vec (take 5 missing))
104
+ :extra (vec (take 5 extra))})]
105
+ (when (seq violations)
106
+ {:violation :historical-mismatch
107
+ :evidence {:violations (vec violations)}})))
108
+ (invariant-name [_this]
109
+ "historical-consistency"))
110
+
111
+ ;; =============================================================================
112
+ ;; Constructor Functions
113
+ ;; =============================================================================
114
+
115
+ (defn index-sorted
116
+ "Create invariant that checks an index is sorted."
117
+ [index-type]
118
+ (->IndexSortedInvariant index-type))
119
+
120
+ (defn index-content
121
+ "Create invariant that checks index content matches model."
122
+ [index-type user-attrs]
123
+ (->IndexContentInvariant index-type user-attrs))
124
+
125
+ (defn historical-consistency
126
+ "Create invariant that checks historical states match as-of queries.
127
+
128
+ actual-tx-ids: the actual transaction IDs in Datahike
129
+ tx-offset: the offset to convert actual tx-ids to model tx-ids
130
+ user-attrs: the set of user attributes to check"
131
+ [actual-tx-ids tx-offset user-attrs]
132
+ (->HistoricalConsistencyInvariant actual-tx-ids tx-offset user-attrs))
133
+
134
+ (defn all-index-invariants
135
+ "Create all standard index invariants (sortedness + content for EAVT, AVET, AEVT)."
136
+ [user-attrs]
137
+ [(index-sorted :eavt)
138
+ (index-sorted :avet)
139
+ (index-sorted :aevt)
140
+ (index-content :eavt user-attrs)
141
+ (index-content :avet user-attrs)
142
+ (index-content :aevt user-attrs)])
@@ -0,0 +1,82 @@
1
+ (ns datahike.test.model.rng
2
+ "Fork-safe random number generation using SplittableRandom."
3
+ #?(:clj (:import [java.util SplittableRandom])))
4
+
5
+ (defprotocol PRNG
6
+ (next-double [this])
7
+ (next-long [this])
8
+ (next-int [this bound])
9
+ (next-boolean [this])
10
+ (fork [this]))
11
+
12
+ #?(:clj
13
+ (defn- wrap-splittable-random
14
+ [rng]
15
+ (reify PRNG
16
+ (next-double [_] (.nextDouble rng))
17
+ (next-long [_] (.nextLong rng))
18
+ (next-int [_ bound] (.nextInt rng (int bound)))
19
+ (next-boolean [_] (.nextBoolean rng))
20
+ (fork [_] (wrap-splittable-random (.split rng))))))
21
+
22
+ #?(:clj
23
+ (defn create
24
+ [seed]
25
+ (wrap-splittable-random (SplittableRandom. (long seed)))))
26
+
27
+ #?(:cljs
28
+ (defn- step-state
29
+ "Linear congruential generator step function."
30
+ [s]
31
+ (mod (+ (* s 1103515245) 12345) (js/Math.pow 2 31))))
32
+
33
+ #?(:cljs
34
+ (defn create
35
+ [seed]
36
+ (let [state (atom (long seed))]
37
+ (reify PRNG
38
+ (next-double [_]
39
+ (/ (double (swap! state step-state)) (js/Math.pow 2 31)))
40
+ (next-long [_]
41
+ (swap! state step-state))
42
+ (next-int [_ bound]
43
+ (mod (swap! state step-state) bound))
44
+ (next-boolean [_]
45
+ (even? (swap! state step-state)))
46
+ (fork [_]
47
+ (create (swap! state step-state)))))))
48
+
49
+ (defn should-trigger?
50
+ [rng rate]
51
+ (< (next-double rng) rate))
52
+
53
+ (defn random-in-range
54
+ [rng min-val max-val]
55
+ (+ min-val (next-int rng (inc (- max-val min-val)))))
56
+
57
+ (defn rand-nth-rng
58
+ [rng coll]
59
+ (nth coll (next-int rng (count coll))))
60
+
61
+ (defn weighted-sample-rng
62
+ [rng pairs]
63
+ (let [sum (transduce (map second) + pairs)
64
+ at (* sum (next-double rng))]
65
+ (loop [at at
66
+ [[k w] & rest] pairs]
67
+ (if (<= at w)
68
+ k
69
+ (recur (- at w) rest)))))
70
+
71
+ (defn shuffle-rng
72
+ [rng coll]
73
+ (let [v (vec coll)
74
+ n (count v)]
75
+ (loop [i (dec n)
76
+ v v]
77
+ (if (pos? i)
78
+ (let [j (next-int rng (inc i))
79
+ vi (v i)
80
+ vj (v j)]
81
+ (recur (dec i) (assoc v i vj j vi)))
82
+ v))))