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,475 @@
1
+ (ns datahike.test.online-gc-test
2
+ "Comprehensive tests for online garbage collection.
3
+
4
+ Tests verify exact freed address counts to detect subtle memory issues.
5
+ Pattern: 3 + 2n addresses for n data transactions
6
+ - Schema tx: 3 (EAVT, AEVT, AVET roots)
7
+ - Data tx: 2 (EAVT, AEVT only - AVET empty for non-indexed attrs)"
8
+ (:require
9
+ #?(:cljs [cljs.test :as t :refer-macros [is deftest testing]]
10
+ :clj [clojure.test :as t :refer [is deftest testing]])
11
+ [datahike.api :as d]
12
+ [datahike.online-gc :as online-gc]
13
+ [konserve.core :as k]
14
+ #?(:clj [clojure.core.async :as async]
15
+ :cljs [cljs.core.async :as async]))
16
+ #?(:clj (:import [java.util Date])))
17
+
18
+ #?(:cljs (def Throwable js/Error))
19
+
20
+ (defn- count-store [db]
21
+ (count (k/keys (:store db) {:sync? true})))
22
+
23
+ (defn- get-freed-addresses [db]
24
+ "Get current freed addresses vector"
25
+ @(-> db :store :storage :freed-addresses))
26
+
27
+ (defn- get-freed-count [db]
28
+ (count (get-freed-addresses db)))
29
+
30
+ (def base-cfg {:store {:backend :file
31
+ :path "/tmp/online-gc-test"
32
+ :id #uuid "a0000000-0000-0000-0000-000000000001"}
33
+ :keep-history? false
34
+ :schema-flexibility :write
35
+ :index :datahike.index/persistent-set})
36
+
37
+ (def schema [{:db/ident :name
38
+ :db/cardinality :db.cardinality/one
39
+ :db/valueType :db.type/string}
40
+ {:db/ident :age
41
+ :db/cardinality :db.cardinality/one
42
+ :db/valueType :db.type/long}])
43
+
44
+ ;;; ============================================================================
45
+ ;;; Exact Freed Count Tests (Most Important)
46
+ ;;; ============================================================================
47
+
48
+ (deftest precise-freed-count-tracking-test
49
+ (testing "Track exact number of freed addresses per transaction"
50
+ (let [cfg (-> base-cfg
51
+ (assoc-in [:store :path] "/tmp/online-gc-precise-freed-test")
52
+ (assoc :online-gc {:enabled? false})) ;; Disabled to accumulate
53
+ conn (do
54
+ (d/delete-database cfg)
55
+ (d/create-database cfg)
56
+ (d/connect cfg))]
57
+
58
+ ;; Initially should be zero
59
+ (is (= 0 (get-freed-count @conn))
60
+ "Initially no freed addresses")
61
+
62
+ ;; Transact schema
63
+ (d/transact conn schema)
64
+ (is (= 3 (get-freed-count @conn))
65
+ "Schema transaction frees 3 addresses (EAVT, AEVT, AVET roots)")
66
+
67
+ ;; First data transaction
68
+ (d/transact conn [{:name "Alice" :age 30}])
69
+ (is (= 5 (get-freed-count @conn))
70
+ "Alice tx frees 2 more addresses (EAVT, AEVT roots). Total: 3+2=5")
71
+
72
+ ;; Second transaction
73
+ (d/transact conn [{:name "Bob" :age 25}])
74
+ (is (= 7 (get-freed-count @conn))
75
+ "Bob tx frees 2 more addresses (EAVT, AEVT roots). Total: 5+2=7")
76
+
77
+ ;; Third transaction
78
+ (d/transact conn [{:name "Charlie" :age 35}])
79
+ (is (= 9 (get-freed-count @conn))
80
+ "Charlie tx frees 2 more addresses (EAVT, AEVT roots). Total: 7+2=9")
81
+
82
+ ;; Note: Only 2 per data tx, not 3, because AVET only stores indexed attributes.
83
+ ;; Since :name and :age are not indexed, AVET stays empty and doesn't change.
84
+
85
+ ;; Verify data is still queryable
86
+ (let [result (d/q '[:find ?name ?age
87
+ :where [?e :name ?name]
88
+ [?e :age ?age]]
89
+ @conn)]
90
+ (is (= 3 (count result))
91
+ "All entities should be queryable despite freed addresses"))
92
+
93
+ (d/release conn))))
94
+
95
+ (deftest precise-gc-deletion-test
96
+ (testing "Verify freed addresses are actually deleted from storage"
97
+ (let [cfg (-> base-cfg
98
+ (assoc-in [:store :path] "/tmp/online-gc-precise-deletion-test")
99
+ (assoc :online-gc {:enabled? true
100
+ :grace-period-ms 0
101
+ :max-batch 1000}))
102
+ conn (do
103
+ (d/delete-database cfg)
104
+ (d/create-database cfg)
105
+ (d/connect cfg))
106
+ initial-store-count (count-store @conn)]
107
+
108
+ ;; Transact schema and data
109
+ (d/transact conn schema)
110
+ (d/transact conn [{:name "Alice" :age 30}])
111
+ (d/transact conn [{:name "Bob" :age 25}])
112
+ (d/transact conn [{:name "Charlie" :age 35}])
113
+
114
+ ;; Freed count should be 0 (all deleted by online GC)
115
+ (is (= 0 (get-freed-count @conn))
116
+ "Online GC should have deleted all freed addresses")
117
+
118
+ ;; Store should not have garbage
119
+ (let [final-store-count (count-store @conn)
120
+ growth (- final-store-count initial-store-count)]
121
+ ;; With online GC, growth should be controlled
122
+ ;; We have 3 entities + schema + branch metadata + indices
123
+ ;; Should be much less than without GC
124
+ (is (< growth 50)
125
+ (str "Store growth should be controlled with online GC. Growth: " growth)))
126
+
127
+ ;; Verify data integrity after releasing and reconnecting
128
+ (d/release conn)
129
+ (let [conn2 (d/connect cfg)
130
+ result (d/q '[:find ?name ?age
131
+ :where [?e :name ?name]
132
+ [?e :age ?age]]
133
+ @conn2)]
134
+ (is (= 3 (count result))
135
+ "All 3 entities should be queryable after GC and reconnect")
136
+ (is (= #{["Alice" 30] ["Bob" 25] ["Charlie" 35]} (set result))
137
+ "All data should be intact")
138
+ (d/release conn2)))))
139
+
140
+ (deftest manual-gc-freed-count-test
141
+ (testing "Manual GC invocation returns exact deletion count"
142
+ (let [cfg (-> base-cfg
143
+ (assoc-in [:store :path] "/tmp/online-gc-manual-count-test")
144
+ (assoc :online-gc {:enabled? false})) ;; Disabled for manual control
145
+ conn (do
146
+ (d/delete-database cfg)
147
+ (d/create-database cfg)
148
+ (d/connect cfg))]
149
+
150
+ (d/transact conn schema)
151
+ (d/transact conn [{:name "Alice" :age 30}])
152
+ (d/transact conn [{:name "Bob" :age 25}])
153
+
154
+ ;; Check how many freed addresses accumulated
155
+ ;; Schema (3) + Alice (2) + Bob (2) = 7
156
+ (let [freed-before-gc (get-freed-count @conn)]
157
+ (is (= 7 freed-before-gc)
158
+ "Should have 7 freed addresses (schema:3 + Alice:2 + Bob:2)")
159
+
160
+ ;; Manually run GC
161
+ (let [deleted-count (online-gc/online-gc! (:store @conn)
162
+ {:enabled? true
163
+ :grace-period-ms 0
164
+ :max-batch 1000
165
+ :sync? true})]
166
+ (is (= 7 deleted-count)
167
+ "GC should delete exactly 7 addresses"))
168
+
169
+ ;; After GC, freed count should be 0
170
+ (is (= 0 (get-freed-count @conn))
171
+ "After manual GC, freed addresses should be cleared"))
172
+
173
+ ;; Data should still be queryable
174
+ (d/release conn)
175
+ (let [conn2 (d/connect cfg)
176
+ result (d/q '[:find (count ?e) .
177
+ :where [?e :name _]]
178
+ @conn2)]
179
+ (is (= 2 result)
180
+ "Data should be intact after manual GC")
181
+ (d/release conn2)))))
182
+
183
+ (deftest recycling-all-at-once-test
184
+ (testing "Address recycling processes all eligible addresses at once"
185
+ (let [cfg (-> base-cfg
186
+ (assoc-in [:store :path] "/tmp/online-gc-recycle-all-test")
187
+ (assoc :online-gc {:enabled? false})) ;; Start disabled
188
+ conn (do
189
+ (d/delete-database cfg)
190
+ (d/create-database cfg)
191
+ (d/connect cfg))]
192
+
193
+ (d/transact conn schema)
194
+ ;; Generate many freed addresses
195
+ (dotimes [i 20]
196
+ (d/transact conn [{:name (str "Person-" i) :age (+ 20 i)}]))
197
+
198
+ ;; Check accumulated freed count
199
+ ;; Schema (3) + 20 data transactions × 2 = 43 total
200
+ (let [total-freed (get-freed-count @conn)]
201
+ (is (= 43 total-freed)
202
+ "Should have 43 freed addresses (schema:3 + 20×2=40)")
203
+
204
+ ;; Run GC - with recycling, all eligible addresses are processed at once
205
+ (let [recycled (online-gc/online-gc! (:store @conn)
206
+ {:enabled? true
207
+ :grace-period-ms 0
208
+ :max-batch 5 ;; This only affects delete mode, not recycling
209
+ :sync? true})]
210
+ (is (= 43 recycled)
211
+ "Should recycle all 43 addresses at once (recycling is not limited by max-batch)"))
212
+
213
+ ;; All addresses should be recycled (moved to freelist)
214
+ (let [remaining (get-freed-count @conn)]
215
+ (is (= 0 remaining)
216
+ "Should have 0 remaining freed addresses (all recycled)")))
217
+
218
+ (d/release conn))))
219
+
220
+ ;;; ============================================================================
221
+ ;;; Integration Tests
222
+ ;;; ============================================================================
223
+
224
+ (deftest online-gc-disabled-test
225
+ (testing "Online GC disabled by default - addresses accumulate"
226
+ (let [cfg (assoc-in base-cfg [:store :path] "/tmp/online-gc-disabled-test")
227
+ conn (do
228
+ (d/delete-database cfg)
229
+ (d/create-database cfg)
230
+ (d/connect cfg))]
231
+ (d/transact conn schema)
232
+ (d/transact conn [{:name "Alice" :age 30}])
233
+ (let [freed-before (get-freed-count @conn)]
234
+ (d/transact conn [{:name "Bob" :age 25}])
235
+ (d/transact conn [{:name "Charlie" :age 35}])
236
+ (let [freed-after (get-freed-count @conn)]
237
+ ;; Freed addresses should increase (not be deleted)
238
+ (is (>= freed-after freed-before))
239
+ (is (pos? freed-after))))
240
+ (d/release conn))))
241
+
242
+ (deftest online-gc-with-reconnect-test
243
+ (testing "Data integrity verified via reconnect after GC"
244
+ (let [cfg (-> base-cfg
245
+ (assoc-in [:store :path] "/tmp/online-gc-reconnect-test")
246
+ (assoc :online-gc {:enabled? true
247
+ :grace-period-ms 0
248
+ :max-batch 1000}))
249
+ conn (do
250
+ (d/delete-database cfg)
251
+ (d/create-database cfg)
252
+ (d/connect cfg))]
253
+
254
+ ;; Insert data
255
+ (d/transact conn schema)
256
+ (d/transact conn [{:name "Alice" :age 30}])
257
+ (d/transact conn [{:name "Bob" :age 25}])
258
+ (d/transact conn [{:name "Charlie" :age 35}])
259
+
260
+ ;; All freed addresses should be cleaned up
261
+ (is (= 0 (get-freed-count @conn))
262
+ "Online GC should have cleaned up all freed addresses")
263
+
264
+ ;; Release and reconnect
265
+ (d/release conn)
266
+ (let [conn2 (d/connect cfg)
267
+ result (d/q '[:find ?name ?age
268
+ :where [?e :name ?name]
269
+ [?e :age ?age]]
270
+ @conn2)]
271
+ (is (= 3 (count result))
272
+ "All 3 entities queryable after reconnect")
273
+ (is (= #{["Alice" 30] ["Bob" 25] ["Charlie" 35]} (set result))
274
+ "All data intact after GC and reconnect")
275
+ (d/release conn2)))))
276
+
277
+ (deftest grace-period-accumulation-test
278
+ (testing "Grace period causes freed addresses to accumulate"
279
+ (let [cfg (-> base-cfg
280
+ (assoc-in [:store :path] "/tmp/online-gc-grace-accumulation-test")
281
+ (assoc :online-gc {:enabled? true
282
+ :grace-period-ms 300000 ;; 5 minutes
283
+ :max-batch 1000}))
284
+ conn (do
285
+ (d/delete-database cfg)
286
+ (d/create-database cfg)
287
+ (d/connect cfg))]
288
+
289
+ (d/transact conn schema)
290
+ (d/transact conn [{:name "Alice" :age 30}])
291
+ (d/transact conn [{:name "Bob" :age 25}])
292
+
293
+ ;; With long grace period, freed addresses should accumulate
294
+ (let [freed-count (get-freed-count @conn)]
295
+ (is (pos? freed-count)
296
+ (str "Freed addresses should accumulate during grace period. Count: " freed-count)))
297
+
298
+ ;; Data should be queryable
299
+ (let [result (d/q '[:find (count ?e) .
300
+ :where [?e :name _]]
301
+ @conn)]
302
+ (is (= 2 result)
303
+ "Data should be queryable with accumulated freed addresses"))
304
+
305
+ (d/release conn))))
306
+
307
+ (deftest online-gc-large-dataset-test
308
+ (testing "Online GC integration with larger dataset"
309
+ (let [cfg (-> base-cfg
310
+ (assoc-in [:store :path] "/tmp/online-gc-integration-test")
311
+ (assoc :online-gc {:enabled? true
312
+ :grace-period-ms 0
313
+ :max-batch 10000}))
314
+ conn (do
315
+ (d/delete-database cfg)
316
+ (d/create-database cfg)
317
+ (d/connect cfg))
318
+ initial-count (count-store @conn)]
319
+ (d/transact conn schema)
320
+
321
+ ;; Bulk insert
322
+ (doseq [batch (partition-all 100 (range 1000))]
323
+ (d/transact conn (mapv (fn [i] {:name (str "Person-" i) :age (+ 20 i)}) batch)))
324
+
325
+ ;; With online GC, growth should be controlled
326
+ (let [final-count (count-store @conn)
327
+ growth-ratio (/ (double final-count) (double initial-count))]
328
+ ;; Growth should be reasonable - not 100x or 1000x
329
+ (is (< growth-ratio 50)
330
+ (str "Store growth should be controlled with online GC. Ratio: " growth-ratio)))
331
+
332
+ ;; Verify data integrity
333
+ (is (= 1000 (d/q '[:find (count ?e) .
334
+ :where [?e :name _]]
335
+ @conn))
336
+ "All 1000 entities should be accessible")
337
+
338
+ (d/release conn))))
339
+
340
+ ;;; ============================================================================
341
+ ;;; Timestamp Filtering Tests
342
+ ;;; ============================================================================
343
+
344
+ (deftest get-and-clear-eligible-freed-test
345
+ (testing "get-and-clear-eligible-freed! correctly filters by timestamp"
346
+ (let [cfg (-> base-cfg
347
+ (assoc-in [:store :path] "/tmp/online-gc-filter-test")
348
+ (assoc :online-gc {:enabled? false}))
349
+ conn (do
350
+ (d/delete-database cfg)
351
+ (d/create-database cfg)
352
+ (d/connect cfg))]
353
+ (d/transact conn schema)
354
+ (d/transact conn [{:name "Alice" :age 30}])
355
+
356
+ ;; Manually check filtering with very long grace period
357
+ (let [[to-delete remaining] (online-gc/get-and-clear-eligible-freed!
358
+ (:store @conn)
359
+ 300000)] ;; 5 minutes
360
+ ;; All should be in remaining (within grace period)
361
+ (is (empty? to-delete) "Nothing should be eligible with long grace period")
362
+ (is (coll? remaining) "Remaining should be a collection"))
363
+
364
+ ;; Check with zero grace period
365
+ (d/transact conn [{:name "Bob" :age 25}]) ;; Generate more freed addresses
366
+ (let [[to-delete _remaining] (online-gc/get-and-clear-eligible-freed!
367
+ (:store @conn)
368
+ 0)] ;; Zero grace period
369
+ ;; Should have eligible addresses
370
+ (is (>= (count to-delete) 0) "Should find eligible addresses with zero grace period"))
371
+
372
+ (d/release conn))))
373
+
374
+ ;;; ============================================================================
375
+ ;;; Background GC Tests
376
+ ;;; ============================================================================
377
+
378
+ (deftest background-gc-test
379
+ (testing "Background GC runs periodically"
380
+ (let [cfg (-> base-cfg
381
+ (assoc-in [:store :path] "/tmp/online-gc-background-test")
382
+ (assoc :online-gc {:enabled? false})) ;; Disable automatic GC in commit
383
+ conn (do
384
+ (d/delete-database cfg)
385
+ (d/create-database cfg)
386
+ (d/connect cfg))]
387
+ (d/transact conn schema)
388
+
389
+ ;; Start background GC with short interval
390
+ (let [stop-ch (online-gc/start-background-gc!
391
+ (:store @conn)
392
+ {:grace-period-ms 0
393
+ :interval-ms 100 ;; Run every 100ms
394
+ :max-batch 1000})]
395
+
396
+ ;; Generate some freed addresses
397
+ (d/transact conn [{:name "Alice" :age 30}])
398
+ (d/transact conn [{:name "Bob" :age 25}])
399
+ (d/transact conn [{:name "Charlie" :age 35}])
400
+
401
+ ;; Wait for background GC to run a few times
402
+ #?(:clj (Thread/sleep 500)
403
+ :cljs (async/<! (async/timeout 500)))
404
+
405
+ ;; Stop background GC
406
+ (async/close! stop-ch)
407
+
408
+ ;; Wait a bit for cleanup
409
+ #?(:clj (Thread/sleep 100)
410
+ :cljs (async/<! (async/timeout 100)))
411
+
412
+ ;; Freed addresses should be cleaned up by background GC
413
+ (let [freed-count (get-freed-count @conn)]
414
+ (is (< freed-count 10)
415
+ (str "Background GC should clean up freed addresses. Remaining: " freed-count))))
416
+
417
+ (d/release conn))))
418
+
419
+ ;;; ============================================================================
420
+ ;;; Safety Tests
421
+ ;;; ============================================================================
422
+
423
+ (deftest multi-branch-safety-test
424
+ (testing "Multi-branch databases skip online GC entirely for safety"
425
+ (let [cfg (-> base-cfg
426
+ (assoc-in [:store :path] "/tmp/online-gc-multi-branch-test")
427
+ (assoc :online-gc {:enabled? false})
428
+ (assoc :crypto-hash? false))
429
+ conn (do
430
+ (d/delete-database cfg)
431
+ (d/create-database cfg)
432
+ (d/connect cfg))]
433
+
434
+ (d/transact conn schema)
435
+ (d/transact conn [{:name "Alice" :age 30}])
436
+
437
+ ;; Check initial branches - should be single branch #{:db}
438
+ (let [branches (k/get (:store @conn) :branches nil {:sync? true})]
439
+ (is (= #{:db} branches)
440
+ "Should start with single branch"))
441
+
442
+ ;; Manually add a second branch to simulate multi-branch scenario
443
+ (k/assoc (:store @conn) :branches #{:db :branch-a} {:sync? true})
444
+
445
+ ;; Verify multi-branch state
446
+ (let [branches (k/get (:store @conn) :branches nil {:sync? true})]
447
+ (is (= #{:db :branch-a} branches)
448
+ "Should have two branches"))
449
+
450
+ ;; Add transaction to generate freed addresses
451
+ (d/transact conn [{:name "Bob" :age 25}])
452
+
453
+ (let [freed-before (get-freed-count @conn)]
454
+ (is (pos? freed-before)
455
+ "Should have freed addresses"))
456
+
457
+ ;; Run online GC - should detect multi-branch and SKIP entirely
458
+ (let [result (online-gc/online-gc! (:store @conn)
459
+ {:enabled? true
460
+ :grace-period-ms 0
461
+ :sync? true})]
462
+ (is (= 0 result)
463
+ "Multi-branch GC should be skipped (return 0)"))
464
+
465
+ ;; Freed addresses should remain (not deleted, left for offline GC)
466
+ (let [freed-after (get-freed-count @conn)]
467
+ (is (pos? freed-after)
468
+ "Multi-branch GC should leave freed addresses for offline GC"))
469
+
470
+ ;; Verify database is still functional
471
+ (let [result (d/q '[:find ?e ?n :where [?e :name ?n]] @conn)]
472
+ (is (= 2 (count result))
473
+ "Both Alice and Bob should still exist"))
474
+
475
+ (d/release conn))))