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.
- package/.circleci/config.yml +405 -0
- package/.circleci/scripts/gen_ci.clj +194 -0
- package/.cirrus.yml +60 -0
- package/.clj-kondo/babashka/sci/config.edn +1 -0
- package/.clj-kondo/babashka/sci/sci/core.clj +9 -0
- package/.clj-kondo/config.edn +95 -0
- package/.dir-locals.el +2 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +68 -0
- package/.github/ISSUE_TEMPLATE/2-feature-request.yml +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +6 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/native-image.yml +84 -0
- package/LICENSE +203 -0
- package/README.md +273 -0
- package/bb/deps.edn +9 -0
- package/bb/resources/github-fingerprints +3 -0
- package/bb/resources/native-image-tests/run-bb-pod-tests.clj +162 -0
- package/bb/resources/native-image-tests/run-libdatahike-tests +12 -0
- package/bb/resources/native-image-tests/run-native-image-tests +74 -0
- package/bb/resources/native-image-tests/run-python-tests +22 -0
- package/bb/resources/native-image-tests/testconfig.attr-refs.edn +6 -0
- package/bb/resources/native-image-tests/testconfig.edn +5 -0
- package/bb/resources/template/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
- package/bb/resources/template/.settings/org.eclipse.jdt.core.prefs +9 -0
- package/bb/resources/template/.settings/org.eclipse.m2e.core.prefs +4 -0
- package/bb/resources/template/pom.xml +22 -0
- package/bb/src/tools/build.clj +132 -0
- package/bb/src/tools/clj_kondo.clj +32 -0
- package/bb/src/tools/deploy.clj +26 -0
- package/bb/src/tools/examples.clj +19 -0
- package/bb/src/tools/npm.clj +100 -0
- package/bb/src/tools/python.clj +14 -0
- package/bb/src/tools/release.clj +94 -0
- package/bb/src/tools/test.clj +148 -0
- package/bb/src/tools/version.clj +47 -0
- package/bb.edn +269 -0
- package/benchmark/src/benchmark/cli.clj +195 -0
- package/benchmark/src/benchmark/compare.clj +157 -0
- package/benchmark/src/benchmark/config.clj +316 -0
- package/benchmark/src/benchmark/measure.clj +187 -0
- package/benchmark/src/benchmark/store.clj +190 -0
- package/benchmark/test/benchmark/measure_test.clj +156 -0
- package/build.clj +30 -0
- package/config.edn +49 -0
- package/deps.edn +138 -0
- package/dev/sandbox.clj +82 -0
- package/dev/sandbox.cljs +127 -0
- package/dev/sandbox_benchmarks.clj +27 -0
- package/dev/sandbox_client.clj +87 -0
- package/dev/sandbox_transact_bench.clj +109 -0
- package/dev/user.clj +79 -0
- package/doc/README.md +96 -0
- package/doc/adl/README.md +6 -0
- package/doc/adl/adr-000-adr.org +28 -0
- package/doc/adl/adr-001-attribute-references.org +15 -0
- package/doc/adl/adr-002-build-tooling.org +54 -0
- package/doc/adl/adr-003-db-meta-data.md +52 -0
- package/doc/adl/adr-004-github-flow.md +40 -0
- package/doc/adl/adr-XYZ-template.md +30 -0
- package/doc/adl/index.org +3 -0
- package/doc/assets/datahike-logo.svg +3 -0
- package/doc/assets/datahiking-invoice.org +85 -0
- package/doc/assets/hhtree2.png +0 -0
- package/doc/assets/network_topology.svg +624 -0
- package/doc/assets/perf.png +0 -0
- package/doc/assets/schema_mindmap.mm +132 -0
- package/doc/assets/schema_mindmap.svg +970 -0
- package/doc/assets/temporal_index.mm +74 -0
- package/doc/backend-development.md +78 -0
- package/doc/bb-pod.md +89 -0
- package/doc/benchmarking.md +360 -0
- package/doc/bindings/edn-conversion.md +383 -0
- package/doc/cli.md +162 -0
- package/doc/cljdoc.edn +27 -0
- package/doc/cljs-support.md +133 -0
- package/doc/config.md +406 -0
- package/doc/contributing.md +114 -0
- package/doc/datalog-vs-sql.md +210 -0
- package/doc/datomic_differences.md +109 -0
- package/doc/development/pull-api-ns.md +186 -0
- package/doc/development/pull-frame-state-diagram.jpg +0 -0
- package/doc/distributed.md +566 -0
- package/doc/entity_spec.md +92 -0
- package/doc/gc.md +273 -0
- package/doc/java-api.md +808 -0
- package/doc/javascript-api.md +421 -0
- package/doc/libdatahike.md +86 -0
- package/doc/logging_and_error_handling.md +43 -0
- package/doc/norms.md +66 -0
- package/doc/schema-migration.md +85 -0
- package/doc/schema.md +287 -0
- package/doc/storage-backends.md +363 -0
- package/doc/store-id-refactoring.md +596 -0
- package/doc/time_variance.md +325 -0
- package/doc/unstructured.md +167 -0
- package/doc/versioning.md +261 -0
- package/examples/basic/README.md +19 -0
- package/examples/basic/deps.edn +6 -0
- package/examples/basic/docker-compose.yml +13 -0
- package/examples/basic/src/examples/core.clj +60 -0
- package/examples/basic/src/examples/schema.clj +155 -0
- package/examples/basic/src/examples/store.clj +60 -0
- package/examples/basic/src/examples/time_travel.clj +185 -0
- package/examples/java/.settings/org.eclipse.core.resources.prefs +3 -0
- package/examples/java/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
- package/examples/java/.settings/org.eclipse.jdt.core.prefs +9 -0
- package/examples/java/.settings/org.eclipse.m2e.core.prefs +4 -0
- package/examples/java/README.md +162 -0
- package/examples/java/pom.xml +62 -0
- package/examples/java/src/main/java/examples/QuickStart.java +115 -0
- package/examples/java/src/main/java/examples/SchemaExample.java +148 -0
- package/examples/java/src/main/java/examples/TimeTravelExample.java +121 -0
- package/flake.lock +27 -0
- package/flake.nix +27 -0
- package/http-server/datahike/http/middleware.clj +75 -0
- package/http-server/datahike/http/server.clj +269 -0
- package/java/src/datahike/java/Database.java +274 -0
- package/java/src/datahike/java/Datahike.java +281 -0
- package/java/src/datahike/java/DatahikeGeneratedTest.java +349 -0
- package/java/src/datahike/java/DatahikeTest.java +370 -0
- package/java/src/datahike/java/EDN.java +170 -0
- package/java/src/datahike/java/IEntity.java +11 -0
- package/java/src/datahike/java/Keywords.java +161 -0
- package/java/src/datahike/java/SchemaFlexibility.java +52 -0
- package/java/src/datahike/java/Util.java +219 -0
- package/karma.conf.js +19 -0
- package/libdatahike/compile-cpp +7 -0
- package/libdatahike/src/datahike/impl/LibDatahikeBase.java +203 -0
- package/libdatahike/src/datahike/impl/libdatahike.clj +59 -0
- package/libdatahike/src/test_cpp.cpp +61 -0
- package/npm-package/PUBLISHING.md +140 -0
- package/npm-package/README.md +226 -0
- package/npm-package/package.template.json +34 -0
- package/npm-package/test-isomorphic.ts +281 -0
- package/npm-package/test.js +557 -0
- package/npm-package/typescript-test.ts +70 -0
- package/package.json +16 -0
- package/pydatahike/README.md +569 -0
- package/pydatahike/pyproject.toml +91 -0
- package/pydatahike/setup.py +42 -0
- package/pydatahike/src/datahike/__init__.py +134 -0
- package/pydatahike/src/datahike/_native.py +250 -0
- package/pydatahike/src/datahike/_version.py +2 -0
- package/pydatahike/src/datahike/database.py +722 -0
- package/pydatahike/src/datahike/edn.py +311 -0
- package/pydatahike/src/datahike/py.typed +0 -0
- package/pydatahike/tests/conftest.py +17 -0
- package/pydatahike/tests/test_basic.py +170 -0
- package/pydatahike/tests/test_database.py +51 -0
- package/pydatahike/tests/test_edn_conversion.py +299 -0
- package/pydatahike/tests/test_query.py +99 -0
- package/pydatahike/tests/test_schema.py +55 -0
- package/resources/clj-kondo.exports/io.replikativ/datahike/config.edn +5 -0
- package/resources/example_server.edn +4 -0
- package/shadow-cljs.edn +56 -0
- package/src/data_readers.clj +7 -0
- package/src/datahike/api/impl.cljc +176 -0
- package/src/datahike/api/specification.cljc +633 -0
- package/src/datahike/api/types.cljc +261 -0
- package/src/datahike/api.cljc +41 -0
- package/src/datahike/array.cljc +99 -0
- package/src/datahike/cli.clj +166 -0
- package/src/datahike/cljs.cljs +6 -0
- package/src/datahike/codegen/cli.clj +406 -0
- package/src/datahike/codegen/clj_kondo.clj +291 -0
- package/src/datahike/codegen/java.clj +403 -0
- package/src/datahike/codegen/naming.cljc +33 -0
- package/src/datahike/codegen/native.clj +559 -0
- package/src/datahike/codegen/pod.clj +488 -0
- package/src/datahike/codegen/python.clj +838 -0
- package/src/datahike/codegen/report.clj +55 -0
- package/src/datahike/codegen/typescript.clj +262 -0
- package/src/datahike/codegen/validation.clj +145 -0
- package/src/datahike/config.cljc +294 -0
- package/src/datahike/connections.cljc +16 -0
- package/src/datahike/connector.cljc +265 -0
- package/src/datahike/constants.cljc +142 -0
- package/src/datahike/core.cljc +297 -0
- package/src/datahike/datom.cljc +459 -0
- package/src/datahike/db/interface.cljc +119 -0
- package/src/datahike/db/search.cljc +305 -0
- package/src/datahike/db/transaction.cljc +937 -0
- package/src/datahike/db/utils.cljc +338 -0
- package/src/datahike/db.cljc +956 -0
- package/src/datahike/experimental/unstructured.cljc +126 -0
- package/src/datahike/experimental/versioning.cljc +172 -0
- package/src/datahike/externs.js +31 -0
- package/src/datahike/gc.cljc +69 -0
- package/src/datahike/http/client.clj +188 -0
- package/src/datahike/http/writer.clj +79 -0
- package/src/datahike/impl/entity.cljc +218 -0
- package/src/datahike/index/interface.cljc +93 -0
- package/src/datahike/index/persistent_set.cljc +469 -0
- package/src/datahike/index/utils.cljc +44 -0
- package/src/datahike/index.cljc +32 -0
- package/src/datahike/js/api.cljs +172 -0
- package/src/datahike/js/api_macros.clj +22 -0
- package/src/datahike/js.cljs +163 -0
- package/src/datahike/json.cljc +209 -0
- package/src/datahike/lru.cljc +146 -0
- package/src/datahike/migrate.clj +39 -0
- package/src/datahike/norm/norm.clj +245 -0
- package/src/datahike/online_gc.cljc +252 -0
- package/src/datahike/pod.clj +155 -0
- package/src/datahike/pull_api.cljc +325 -0
- package/src/datahike/query.cljc +1945 -0
- package/src/datahike/query_stats.cljc +88 -0
- package/src/datahike/readers.cljc +62 -0
- package/src/datahike/remote.cljc +218 -0
- package/src/datahike/schema.cljc +228 -0
- package/src/datahike/schema_cache.cljc +42 -0
- package/src/datahike/spec.cljc +101 -0
- package/src/datahike/store.cljc +80 -0
- package/src/datahike/tools.cljc +308 -0
- package/src/datahike/transit.cljc +80 -0
- package/src/datahike/writer.cljc +239 -0
- package/src/datahike/writing.cljc +362 -0
- package/src/deps.cljs +1 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/insert.cljc +76 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/upsert.cljc +128 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree.cljc +213 -0
- package/test/datahike/backward_compatibility_test/src/backward_test.clj +37 -0
- package/test/datahike/integration_test/config_record_file_test.clj +14 -0
- package/test/datahike/integration_test/config_record_test.clj +14 -0
- package/test/datahike/integration_test/depr_config_uri_test.clj +15 -0
- package/test/datahike/integration_test/return_map_test.clj +62 -0
- package/test/datahike/integration_test.cljc +67 -0
- package/test/datahike/norm/norm_test.clj +124 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/004-tx-data-and-tx-fn-test.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/01-transact-basic-characters.edn +2 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/02 add occupation.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/checksums.edn +12 -0
- package/test/datahike/norm/resources/simple-test/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/simple-test/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/simple-test/checksums.edn +4 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/checksums.edn +6 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/004-tx-data-and-tx-fn-test.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/checksums.edn +2 -0
- package/test/datahike/norm/resources/tx-fn-test/first/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/tx-fn-test/first/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/tx-fn-test/first/checksums.edn +4 -0
- package/test/datahike/norm/resources/tx-fn-test/second/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/tx-fn-test/second/checksums.edn +2 -0
- package/test/datahike/test/api_test.cljc +895 -0
- package/test/datahike/test/array_test.cljc +40 -0
- package/test/datahike/test/attribute_refs/datoms_test.cljc +140 -0
- package/test/datahike/test/attribute_refs/db_test.cljc +42 -0
- package/test/datahike/test/attribute_refs/differences_test.cljc +515 -0
- package/test/datahike/test/attribute_refs/entity_test.cljc +89 -0
- package/test/datahike/test/attribute_refs/pull_api_test.cljc +320 -0
- package/test/datahike/test/attribute_refs/query_find_specs_test.cljc +59 -0
- package/test/datahike/test/attribute_refs/query_fns_test.cljc +130 -0
- package/test/datahike/test/attribute_refs/query_interop_test.cljc +47 -0
- package/test/datahike/test/attribute_refs/query_not_test.cljc +193 -0
- package/test/datahike/test/attribute_refs/query_or_test.cljc +137 -0
- package/test/datahike/test/attribute_refs/query_pull_test.cljc +156 -0
- package/test/datahike/test/attribute_refs/query_rules_test.cljc +176 -0
- package/test/datahike/test/attribute_refs/query_test.cljc +241 -0
- package/test/datahike/test/attribute_refs/temporal_search.cljc +22 -0
- package/test/datahike/test/attribute_refs/transact_test.cljc +220 -0
- package/test/datahike/test/attribute_refs/utils.cljc +128 -0
- package/test/datahike/test/cache_test.cljc +38 -0
- package/test/datahike/test/components_test.cljc +92 -0
- package/test/datahike/test/config_test.cljc +158 -0
- package/test/datahike/test/core_test.cljc +105 -0
- package/test/datahike/test/datom_test.cljc +44 -0
- package/test/datahike/test/db_test.cljc +54 -0
- package/test/datahike/test/entity_spec_test.cljc +159 -0
- package/test/datahike/test/entity_test.cljc +103 -0
- package/test/datahike/test/explode_test.cljc +143 -0
- package/test/datahike/test/filter_test.cljc +75 -0
- package/test/datahike/test/gc_test.cljc +159 -0
- package/test/datahike/test/http/server_test.clj +192 -0
- package/test/datahike/test/http/writer_test.clj +86 -0
- package/test/datahike/test/ident_test.cljc +32 -0
- package/test/datahike/test/index_test.cljc +345 -0
- package/test/datahike/test/insert.cljc +125 -0
- package/test/datahike/test/java_bindings_test.clj +6 -0
- package/test/datahike/test/listen_test.cljc +41 -0
- package/test/datahike/test/lookup_refs_test.cljc +266 -0
- package/test/datahike/test/lru_test.cljc +27 -0
- package/test/datahike/test/migrate_test.clj +297 -0
- package/test/datahike/test/model/core.cljc +376 -0
- package/test/datahike/test/model/invariant.cljc +142 -0
- package/test/datahike/test/model/rng.cljc +82 -0
- package/test/datahike/test/model_test.clj +217 -0
- package/test/datahike/test/nodejs_test.cljs +262 -0
- package/test/datahike/test/online_gc_test.cljc +475 -0
- package/test/datahike/test/pod_test.clj +369 -0
- package/test/datahike/test/pull_api_test.cljc +474 -0
- package/test/datahike/test/purge_test.cljc +144 -0
- package/test/datahike/test/query_aggregates_test.cljc +101 -0
- package/test/datahike/test/query_find_specs_test.cljc +52 -0
- package/test/datahike/test/query_fns_test.cljc +523 -0
- package/test/datahike/test/query_interop_test.cljc +47 -0
- package/test/datahike/test/query_not_test.cljc +189 -0
- package/test/datahike/test/query_or_test.cljc +158 -0
- package/test/datahike/test/query_pull_test.cljc +147 -0
- package/test/datahike/test/query_rules_test.cljc +248 -0
- package/test/datahike/test/query_stats_test.cljc +218 -0
- package/test/datahike/test/query_test.cljc +984 -0
- package/test/datahike/test/schema_test.cljc +424 -0
- package/test/datahike/test/specification_test.cljc +30 -0
- package/test/datahike/test/store_test.cljc +78 -0
- package/test/datahike/test/stress_test.cljc +57 -0
- package/test/datahike/test/time_variance_test.cljc +518 -0
- package/test/datahike/test/tools_test.clj +134 -0
- package/test/datahike/test/transact_test.cljc +518 -0
- package/test/datahike/test/tuples_test.cljc +564 -0
- package/test/datahike/test/unstructured_test.cljc +291 -0
- package/test/datahike/test/upsert_impl_test.cljc +205 -0
- package/test/datahike/test/upsert_test.cljc +363 -0
- package/test/datahike/test/utils.cljc +110 -0
- package/test/datahike/test/validation_test.cljc +48 -0
- package/test/datahike/test/versioning_test.cljc +56 -0
- package/test/datahike/test.cljc +66 -0
- package/tests.edn +24 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
(ns datahike.test.model-test
|
|
2
|
+
"Model-based integration tests for Datahike.
|
|
3
|
+
|
|
4
|
+
This test compares a pure Clojure model of Datahike semantics
|
|
5
|
+
against the actual Datahike implementation. The model tracks
|
|
6
|
+
expected state, and we verify that the real system matches."
|
|
7
|
+
(:require [clojure.test :refer [deftest is testing]]
|
|
8
|
+
[clojure.test.check.clojure-test :refer [defspec]]
|
|
9
|
+
[clojure.test.check.generators :as gen]
|
|
10
|
+
[clojure.test.check.properties :as prop]
|
|
11
|
+
[datahike.api :as d]
|
|
12
|
+
[datahike.test.model.rng :as rng]
|
|
13
|
+
[datahike.test.model.core :as model]
|
|
14
|
+
[datahike.test.model.invariant :as inv])
|
|
15
|
+
(:import [java.util UUID]))
|
|
16
|
+
|
|
17
|
+
;; =============================================================================
|
|
18
|
+
;; Test Schema
|
|
19
|
+
;; =============================================================================
|
|
20
|
+
|
|
21
|
+
(def test-schema
|
|
22
|
+
[{:db/ident :name
|
|
23
|
+
:db/valueType :db.type/string
|
|
24
|
+
:db/cardinality :db.cardinality/one
|
|
25
|
+
:db/index true}
|
|
26
|
+
{:db/ident :label
|
|
27
|
+
:db/valueType :db.type/string
|
|
28
|
+
:db/cardinality :db.cardinality/many
|
|
29
|
+
:db/index true}
|
|
30
|
+
{:db/ident :age
|
|
31
|
+
:db/valueType :db.type/long
|
|
32
|
+
:db/cardinality :db.cardinality/one
|
|
33
|
+
:db/index true}
|
|
34
|
+
{:db/ident :friend
|
|
35
|
+
:db/valueType :db.type/ref
|
|
36
|
+
:db/cardinality :db.cardinality/many}])
|
|
37
|
+
|
|
38
|
+
(def schema-map
|
|
39
|
+
(into {} (map (juxt :db/ident identity)) test-schema))
|
|
40
|
+
|
|
41
|
+
(def user-attrs #{:name :label :age :friend})
|
|
42
|
+
|
|
43
|
+
(def entity-range (vec (range 100 200)))
|
|
44
|
+
|
|
45
|
+
;; =============================================================================
|
|
46
|
+
;; Test Helpers
|
|
47
|
+
;; =============================================================================
|
|
48
|
+
|
|
49
|
+
(defn create-test-db
|
|
50
|
+
"Create a fresh in-memory database with schema.
|
|
51
|
+
Returns map with :conn, :db, :cfg, and :tx-offset.
|
|
52
|
+
|
|
53
|
+
The tx-offset maps model tx-ids to actual tx-ids:
|
|
54
|
+
- Model starts with next-tx = 536870912 (first user tx would be recorded at 536870912)
|
|
55
|
+
- After schema, actual max-tx = 536870913 (schema tx)
|
|
56
|
+
- First user tx will be at 536870914
|
|
57
|
+
- So offset = 536870914 - 536870912 = 2
|
|
58
|
+
|
|
59
|
+
This means: model-tx-id + tx-offset = actual-tx-id
|
|
60
|
+
|
|
61
|
+
opts: optional map to override default config (e.g., {:attribute-refs? false})"
|
|
62
|
+
([]
|
|
63
|
+
(create-test-db {}))
|
|
64
|
+
([opts]
|
|
65
|
+
(let [cfg (merge {:store {:backend :memory :id (UUID/randomUUID)}
|
|
66
|
+
:keep-history? true
|
|
67
|
+
:schema-flexibility :write}
|
|
68
|
+
opts)]
|
|
69
|
+
(d/delete-database cfg)
|
|
70
|
+
(d/create-database cfg)
|
|
71
|
+
(let [conn (d/connect cfg)
|
|
72
|
+
_ (d/transact conn test-schema)
|
|
73
|
+
db-after-schema (d/db conn)
|
|
74
|
+
tx-offset (- (:max-tx db-after-schema) 536870911)]
|
|
75
|
+
{:conn conn :db (d/db conn) :cfg cfg :tx-offset tx-offset}))))
|
|
76
|
+
|
|
77
|
+
(defn apply-tx-to-conn
|
|
78
|
+
"Apply transaction to connection, returning updated db."
|
|
79
|
+
[conn tx-ops]
|
|
80
|
+
(try
|
|
81
|
+
(d/transact conn {:tx-data (mapv vec tx-ops)})
|
|
82
|
+
(d/db conn)
|
|
83
|
+
(catch Exception _
|
|
84
|
+
(d/db conn))))
|
|
85
|
+
|
|
86
|
+
;; =============================================================================
|
|
87
|
+
;; Properties (test.check)
|
|
88
|
+
;; =============================================================================
|
|
89
|
+
|
|
90
|
+
(def seed-gen (gen/choose 0 9223372036854775807))
|
|
91
|
+
|
|
92
|
+
(defn index-integrity-prop
|
|
93
|
+
"Property that checks index integrity with given config options."
|
|
94
|
+
[opts]
|
|
95
|
+
(prop/for-all [seed seed-gen]
|
|
96
|
+
(let [rng (rng/create seed)
|
|
97
|
+
model-state (model/create-model schema-map)
|
|
98
|
+
{:keys [conn]} (create-test-db opts)]
|
|
99
|
+
(loop [i 0
|
|
100
|
+
model-state model-state]
|
|
101
|
+
(if (>= i 50)
|
|
102
|
+
true
|
|
103
|
+
(let [tx-ops (model/generate-transaction rng schema-map entity-range model-state 10)
|
|
104
|
+
model-state' (model/apply-tx model-state {:tx-data tx-ops})
|
|
105
|
+
db (apply-tx-to-conn conn tx-ops)
|
|
106
|
+
result (inv/check-all
|
|
107
|
+
(inv/all-index-invariants user-attrs)
|
|
108
|
+
model-state'
|
|
109
|
+
db)]
|
|
110
|
+
(if (:valid? result)
|
|
111
|
+
(recur (inc i) model-state')
|
|
112
|
+
false)))))))
|
|
113
|
+
|
|
114
|
+
(defn historical-consistency-prop
|
|
115
|
+
"Property that checks historical consistency with given config options."
|
|
116
|
+
[opts]
|
|
117
|
+
(prop/for-all [seed seed-gen]
|
|
118
|
+
(let [rng (rng/create seed)
|
|
119
|
+
model-state (model/create-model schema-map)
|
|
120
|
+
{:keys [conn tx-offset]} (create-test-db opts)]
|
|
121
|
+
(loop [i 0
|
|
122
|
+
model-state model-state]
|
|
123
|
+
(if (>= i 20)
|
|
124
|
+
true
|
|
125
|
+
(let [tx-ops (model/generate-transaction rng schema-map entity-range model-state 3)
|
|
126
|
+
model-state' (model/apply-tx model-state {:tx-data tx-ops})
|
|
127
|
+
db (apply-tx-to-conn conn tx-ops)
|
|
128
|
+
model-tx-ids (model/get-transaction-ids model-state')
|
|
129
|
+
actual-tx-ids (map #(+ % tx-offset) (take 3 model-tx-ids))
|
|
130
|
+
hist-result (inv/check-all
|
|
131
|
+
[(inv/historical-consistency
|
|
132
|
+
actual-tx-ids
|
|
133
|
+
tx-offset
|
|
134
|
+
user-attrs)]
|
|
135
|
+
model-state'
|
|
136
|
+
db)]
|
|
137
|
+
(if (:valid? hist-result)
|
|
138
|
+
(recur (inc i) model-state')
|
|
139
|
+
false)))))))
|
|
140
|
+
|
|
141
|
+
;; With :attribute-refs? true (default)
|
|
142
|
+
(defspec index-integrity-with-attribute-refs 100
|
|
143
|
+
(index-integrity-prop {:attribute-refs? true}))
|
|
144
|
+
|
|
145
|
+
(defspec historical-with-attribute-refs 30
|
|
146
|
+
(historical-consistency-prop {:attribute-refs? true}))
|
|
147
|
+
|
|
148
|
+
;; Without :attribute-refs?
|
|
149
|
+
(defspec index-integrity-without-attribute-refs 100
|
|
150
|
+
(index-integrity-prop {:attribute-refs? false}))
|
|
151
|
+
|
|
152
|
+
(defspec historical-without-attribute-refs 30
|
|
153
|
+
(historical-consistency-prop {:attribute-refs? false}))
|
|
154
|
+
|
|
155
|
+
;; =============================================================================
|
|
156
|
+
;; Unit Tests
|
|
157
|
+
;; =============================================================================
|
|
158
|
+
|
|
159
|
+
(deftest test-model-accumulate-datom
|
|
160
|
+
(testing "cardinality one replaces"
|
|
161
|
+
(let [s1 (model/accumulate-datom schema-map #{} [100 :name "Alice" true])
|
|
162
|
+
s2 (model/accumulate-datom schema-map s1 [100 :name "Bob" true])]
|
|
163
|
+
(is (= #{[100 :name "Bob"]} s2))))
|
|
164
|
+
|
|
165
|
+
(testing "cardinality many accumulates"
|
|
166
|
+
(let [s1 (model/accumulate-datom schema-map #{} [100 :label "a" true])
|
|
167
|
+
s2 (model/accumulate-datom schema-map s1 [100 :label "b" true])]
|
|
168
|
+
(is (= #{[100 :label "a"] [100 :label "b"]} s2))))
|
|
169
|
+
|
|
170
|
+
(testing "retract removes"
|
|
171
|
+
(let [s1 (model/accumulate-datom schema-map #{} [100 :name "Alice" true])
|
|
172
|
+
s2 (model/accumulate-datom schema-map s1 [100 :name "Alice" false])]
|
|
173
|
+
(is (= #{} s2)))))
|
|
174
|
+
|
|
175
|
+
(deftest test-model-index-computation
|
|
176
|
+
(let [model-state (model/apply-txs
|
|
177
|
+
(model/create-model schema-map)
|
|
178
|
+
[{:tx-data [[:db/add 100 :name "Alice"]
|
|
179
|
+
[:db/add 100 :age 25]]}
|
|
180
|
+
{:tx-data [[:db/add 101 :name "Bob"]
|
|
181
|
+
[:db/add 101 :age 30]]}])]
|
|
182
|
+
(testing "EAVT sorted by [e a v]"
|
|
183
|
+
(let [eavt (model/compute-eavt model-state)]
|
|
184
|
+
(is (= [100 :age 25] (first eavt)))
|
|
185
|
+
(is (= [101 :name "Bob"] (last eavt)))))
|
|
186
|
+
|
|
187
|
+
(testing "AVET contains indexed attrs"
|
|
188
|
+
(let [avet (model/compute-avet model-state)]
|
|
189
|
+
(is (some #{[100 :age 25]} avet))
|
|
190
|
+
(is (some #{[100 :name "Alice"]} avet))))
|
|
191
|
+
|
|
192
|
+
(testing "AEVT sorted by [a e v]"
|
|
193
|
+
(let [aevt (model/compute-aevt model-state)]
|
|
194
|
+
(is (= :age (second (first aevt))))))))
|
|
195
|
+
|
|
196
|
+
(deftest test-rng-reproducibility
|
|
197
|
+
(testing "same seed produces same sequence"
|
|
198
|
+
(let [rng1 (rng/create 12345)
|
|
199
|
+
rng2 (rng/create 12345)
|
|
200
|
+
seq1 (repeatedly 10 #(rng/next-int rng1 100))
|
|
201
|
+
seq2 (repeatedly 10 #(rng/next-int rng2 100))]
|
|
202
|
+
(is (= seq1 seq2))))
|
|
203
|
+
|
|
204
|
+
(testing "different seeds produce different sequences"
|
|
205
|
+
(let [rng1 (rng/create 12345)
|
|
206
|
+
rng2 (rng/create 54321)
|
|
207
|
+
seq1 (repeatedly 10 #(rng/next-int rng1 100))
|
|
208
|
+
seq2 (repeatedly 10 #(rng/next-int rng2 100))]
|
|
209
|
+
(is (not= seq1 seq2))))
|
|
210
|
+
|
|
211
|
+
(testing "fork creates independent stream"
|
|
212
|
+
(let [rng (rng/create 12345)
|
|
213
|
+
child (rng/fork rng)
|
|
214
|
+
parent-val (rng/next-int rng 100)
|
|
215
|
+
child-val (rng/next-int child 100)]
|
|
216
|
+
(is (number? parent-val))
|
|
217
|
+
(is (number? child-val)))))
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
(ns datahike.test.nodejs-test
|
|
2
|
+
(:require [cljs.test :refer [deftest is async] :as t]
|
|
3
|
+
[datahike.api :as d]
|
|
4
|
+
[datahike.online-gc :as online-gc]
|
|
5
|
+
[konserve.core :as k]
|
|
6
|
+
[konserve.node-filestore] ;; Register :file backend for Node.js
|
|
7
|
+
[cljs.core.async :refer [go <!] :include-macros true]
|
|
8
|
+
[cljs.nodejs :as nodejs]))
|
|
9
|
+
|
|
10
|
+
(def fs (nodejs/require "fs"))
|
|
11
|
+
(def path (nodejs/require "path"))
|
|
12
|
+
(def os (nodejs/require "os"))
|
|
13
|
+
|
|
14
|
+
(defn tmp-dir []
|
|
15
|
+
(let [dir (path.join (os.tmpdir) (str "datahike-node-test-" (rand-int 100000)))]
|
|
16
|
+
dir))
|
|
17
|
+
|
|
18
|
+
(deftest roundtrip-test
|
|
19
|
+
(let [dir (tmp-dir)
|
|
20
|
+
store-id (random-uuid)
|
|
21
|
+
cfg {:store {:backend :file :path dir :id store-id}
|
|
22
|
+
:keep-history? true
|
|
23
|
+
:schema-flexibility :write}]
|
|
24
|
+
(async done
|
|
25
|
+
(go
|
|
26
|
+
(try
|
|
27
|
+
;; Create database
|
|
28
|
+
(let [created (<! (d/create-database cfg))]
|
|
29
|
+
(is created "Database created"))
|
|
30
|
+
|
|
31
|
+
;; Connect
|
|
32
|
+
(let [conn (d/connect cfg)]
|
|
33
|
+
(is conn "Connection established")
|
|
34
|
+
|
|
35
|
+
;; Add schema
|
|
36
|
+
(let [schema-tx [{:db/ident :name
|
|
37
|
+
:db/valueType :db.type/string
|
|
38
|
+
:db/cardinality :db.cardinality/one}
|
|
39
|
+
{:db/ident :age
|
|
40
|
+
:db/valueType :db.type/long
|
|
41
|
+
:db/cardinality :db.cardinality/one}]]
|
|
42
|
+
(let [report (<! (d/transact! conn schema-tx))]
|
|
43
|
+
(is (:db-after report) "Schema added")))
|
|
44
|
+
|
|
45
|
+
;; Transact data
|
|
46
|
+
(let [tx-report (<! (d/transact! conn [{:name "Alice" :age 30}
|
|
47
|
+
{:name "Bob" :age 25}]))]
|
|
48
|
+
(is (:db-after tx-report) "Data transacted")
|
|
49
|
+
(is (pos? (count (:tx-data tx-report))) "Datoms were added"))
|
|
50
|
+
|
|
51
|
+
;; Verify data via datoms API
|
|
52
|
+
(let [all-datoms (vec (d/datoms @conn :eavt))
|
|
53
|
+
name-datoms (filter #(= :name (:a %)) all-datoms)
|
|
54
|
+
age-datoms (filter #(= :age (:a %)) all-datoms)]
|
|
55
|
+
(is (= 2 (count name-datoms)) "Found 2 name datoms")
|
|
56
|
+
(is (= 2 (count age-datoms)) "Found 2 age datoms"))
|
|
57
|
+
|
|
58
|
+
;; Pull API
|
|
59
|
+
(let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
|
|
60
|
+
e1 (ffirst entities)]
|
|
61
|
+
(when e1
|
|
62
|
+
(let [pulled (d/pull @conn [:name :age] e1)]
|
|
63
|
+
(is (:name pulled) "Pull retrieved name")
|
|
64
|
+
(is (:age pulled) "Pull retrieved age"))))
|
|
65
|
+
|
|
66
|
+
;; Query API
|
|
67
|
+
(let [q1 (d/q '[:find ?e :where [?e :name _]] @conn)]
|
|
68
|
+
(is (= 2 (count q1)) "Single pattern: found 2 entities")
|
|
69
|
+
(is (every? #(number? (first %)) q1) "Single pattern: entity IDs are numbers"))
|
|
70
|
+
|
|
71
|
+
(let [q2 (d/q '[:find ?v :where [_ :name ?v]] @conn)
|
|
72
|
+
names (set (map first q2))]
|
|
73
|
+
(is (= 2 (count q2)) "Value query: found 2 names")
|
|
74
|
+
(is (contains? names "Alice") "Value query: found Alice")
|
|
75
|
+
(is (contains? names "Bob") "Value query: found Bob"))
|
|
76
|
+
|
|
77
|
+
(let [q3 (d/q '[:find ?e ?name ?age
|
|
78
|
+
:where
|
|
79
|
+
[?e :name ?name]
|
|
80
|
+
[?e :age ?age]] @conn)
|
|
81
|
+
results (into {} (map (fn [[e name age]] [name {:e e :age age}]) q3))]
|
|
82
|
+
(is (= 2 (count q3)) "Join query: found 2 entity/name/age tuples")
|
|
83
|
+
(is (number? (get-in results ["Alice" :e])) "Join query: Alice has valid entity ID")
|
|
84
|
+
(is (= 30 (get-in results ["Alice" :age])) "Join query: Alice is 30")
|
|
85
|
+
(is (number? (get-in results ["Bob" :e])) "Join query: Bob has valid entity ID")
|
|
86
|
+
(is (= 25 (get-in results ["Bob" :age])) "Join query: Bob is 25"))
|
|
87
|
+
|
|
88
|
+
;; Entity API
|
|
89
|
+
(let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
|
|
90
|
+
e1 (ffirst entities)]
|
|
91
|
+
(when e1
|
|
92
|
+
(let [entity (d/entity @conn e1)]
|
|
93
|
+
(is (:name entity) "Entity has name")
|
|
94
|
+
(is (:age entity) "Entity has age"))))
|
|
95
|
+
|
|
96
|
+
;; Update for history
|
|
97
|
+
(let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
|
|
98
|
+
e1 (ffirst entities)]
|
|
99
|
+
(when e1
|
|
100
|
+
(<! (d/transact! conn [[:db/add e1 :age 31]]))))
|
|
101
|
+
|
|
102
|
+
;; Test as-of DB
|
|
103
|
+
(let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
|
|
104
|
+
e1 (ffirst entities)
|
|
105
|
+
before-update-tx (:max-tx @conn)]
|
|
106
|
+
(when e1
|
|
107
|
+
(<! (d/transact! conn [[:db/add e1 :age 99]])))
|
|
108
|
+
(let [current-age-after (when e1 (:age (d/entity @conn e1)))]
|
|
109
|
+
(is (= 99 current-age-after) "Current DB shows updated age"))
|
|
110
|
+
(let [as-of-db (d/as-of @conn before-update-tx)
|
|
111
|
+
as-of-age (when e1 (:age (d/entity as-of-db e1)))]
|
|
112
|
+
(is (= 31 as-of-age) "as-of DB shows old age value")))
|
|
113
|
+
|
|
114
|
+
;; History DB
|
|
115
|
+
(let [hist-db (d/history @conn)
|
|
116
|
+
hist-datoms (vec (filter #(= :age (:a %)) (d/datoms hist-db :eavt)))]
|
|
117
|
+
(is (>= (count hist-datoms) 4) "History contains multiple age values"))
|
|
118
|
+
|
|
119
|
+
(d/release conn))
|
|
120
|
+
|
|
121
|
+
;; Reconnect to verify persistence
|
|
122
|
+
(let [conn2 (d/connect cfg)]
|
|
123
|
+
(is conn2 "Reconnected successfully")
|
|
124
|
+
|
|
125
|
+
(let [all-datoms (vec (d/datoms @conn2 :eavt))
|
|
126
|
+
name-datoms (filter #(= :name (:a %)) all-datoms)]
|
|
127
|
+
(is (= 2 (count name-datoms)) "Data persisted after reconnect"))
|
|
128
|
+
|
|
129
|
+
;; Test different index access
|
|
130
|
+
(let [aevt-datoms (take 5 (d/datoms @conn2 :aevt))
|
|
131
|
+
avet-datoms (take 5 (d/datoms @conn2 :avet))]
|
|
132
|
+
(is (seq aevt-datoms) "Got datoms from AEVT index")
|
|
133
|
+
(is (seq avet-datoms) "Got datoms from AVET index"))
|
|
134
|
+
|
|
135
|
+
(d/release conn2))
|
|
136
|
+
|
|
137
|
+
;; Delete database
|
|
138
|
+
(let [deleted (<! (d/delete-database cfg))]
|
|
139
|
+
(is (nil? deleted) "Database deleted"))
|
|
140
|
+
|
|
141
|
+
(is (not (fs.existsSync dir)) "Directory removed")
|
|
142
|
+
|
|
143
|
+
(catch js/Error e
|
|
144
|
+
(is false (str "Error: " (.-message e))))
|
|
145
|
+
(finally
|
|
146
|
+
(done)))))))
|
|
147
|
+
|
|
148
|
+
(deftest online-gc-basic-test
|
|
149
|
+
(async done
|
|
150
|
+
(go
|
|
151
|
+
(try
|
|
152
|
+
(let [dir (tmp-dir)
|
|
153
|
+
cfg-no-gc {:store {:backend :file :path dir :id (random-uuid)}
|
|
154
|
+
:online-gc {:enabled? false}
|
|
155
|
+
:crypto-hash? false
|
|
156
|
+
:keep-history? false
|
|
157
|
+
:schema-flexibility :write}]
|
|
158
|
+
|
|
159
|
+
;; Create database without online GC initially
|
|
160
|
+
(<! (d/create-database cfg-no-gc))
|
|
161
|
+
(let [conn (d/connect cfg-no-gc)]
|
|
162
|
+
|
|
163
|
+
;; Add schema
|
|
164
|
+
(<! (d/transact! conn [{:db/ident :name
|
|
165
|
+
:db/valueType :db.type/string
|
|
166
|
+
:db/cardinality :db.cardinality/one}]))
|
|
167
|
+
|
|
168
|
+
;; Add data to create freed addresses
|
|
169
|
+
(<! (d/transact! conn [{:name "Alice"}]))
|
|
170
|
+
(<! (d/transact! conn [{:name "Bob"}]))
|
|
171
|
+
|
|
172
|
+
;; Get freed count (should have freed addresses from schema + data txs)
|
|
173
|
+
(let [freed-atom (-> @conn :store :storage :freed-addresses)
|
|
174
|
+
initial-freed (count @freed-atom)]
|
|
175
|
+
(is (> initial-freed 0) "Should have freed addresses with GC disabled"))
|
|
176
|
+
|
|
177
|
+
;; Run online GC explicitly
|
|
178
|
+
(let [gc-result (<! (online-gc/online-gc! (:store @conn)
|
|
179
|
+
{:enabled? true
|
|
180
|
+
:grace-period-ms 0
|
|
181
|
+
:sync? false}))]
|
|
182
|
+
(is (number? gc-result) "GC returned a count"))
|
|
183
|
+
|
|
184
|
+
;; Check that freed addresses were cleared
|
|
185
|
+
(let [freed-atom (-> @conn :store :storage :freed-addresses)
|
|
186
|
+
final-freed (count @freed-atom)]
|
|
187
|
+
(is (= 0 final-freed) "Freed addresses should be cleared after GC"))
|
|
188
|
+
|
|
189
|
+
(d/release conn))
|
|
190
|
+
|
|
191
|
+
;; Cleanup
|
|
192
|
+
(<! (d/delete-database cfg-no-gc)))
|
|
193
|
+
|
|
194
|
+
(catch js/Error e
|
|
195
|
+
(is false (str "Error in online-gc-basic-test: " (.-message e))))
|
|
196
|
+
(finally
|
|
197
|
+
(done))))))
|
|
198
|
+
|
|
199
|
+
(deftest online-gc-multi-branch-safety-test
|
|
200
|
+
(async done
|
|
201
|
+
(go
|
|
202
|
+
(try
|
|
203
|
+
(let [dir (tmp-dir)
|
|
204
|
+
cfg-no-gc {:store {:backend :file :path dir :id (random-uuid)}
|
|
205
|
+
:online-gc {:enabled? false}
|
|
206
|
+
:crypto-hash? false
|
|
207
|
+
:keep-history? false
|
|
208
|
+
:schema-flexibility :write}]
|
|
209
|
+
|
|
210
|
+
;; Create database without online GC initially
|
|
211
|
+
(<! (d/create-database cfg-no-gc))
|
|
212
|
+
(let [conn (d/connect cfg-no-gc)]
|
|
213
|
+
|
|
214
|
+
;; Add schema and data
|
|
215
|
+
(<! (d/transact! conn [{:db/ident :name
|
|
216
|
+
:db/valueType :db.type/string
|
|
217
|
+
:db/cardinality :db.cardinality/one}]))
|
|
218
|
+
(<! (d/transact! conn [{:name "Alice"}]))
|
|
219
|
+
|
|
220
|
+
;; Simulate multi-branch scenario by adding a second branch
|
|
221
|
+
(<! (k/assoc (:store @conn) :branches #{:db :branch-a}))
|
|
222
|
+
|
|
223
|
+
;; Verify multi-branch state
|
|
224
|
+
(let [branches (<! (k/get (:store @conn) :branches))]
|
|
225
|
+
(is (= 2 (count branches)) "Should have two branches"))
|
|
226
|
+
|
|
227
|
+
;; Add more data to generate freed addresses
|
|
228
|
+
(<! (d/transact! conn [{:name "Bob"}]))
|
|
229
|
+
|
|
230
|
+
(let [freed-before (count @(-> @conn :store :storage :freed-addresses))]
|
|
231
|
+
(is (> freed-before 0) "Should have freed addresses with GC disabled"))
|
|
232
|
+
|
|
233
|
+
;; Run online GC - should detect multi-branch and SKIP entirely
|
|
234
|
+
(let [gc-result (<! (online-gc/online-gc! (:store @conn)
|
|
235
|
+
{:enabled? true
|
|
236
|
+
:grace-period-ms 0
|
|
237
|
+
:sync? false}))]
|
|
238
|
+
(is (= 0 gc-result) "Multi-branch GC should be skipped (return 0)"))
|
|
239
|
+
|
|
240
|
+
;; Freed addresses should remain (not deleted, re-marked for offline GC)
|
|
241
|
+
(let [freed-after (count @(-> @conn :store :storage :freed-addresses))]
|
|
242
|
+
(is (> freed-after 0) "Multi-branch GC should leave freed addresses for offline GC"))
|
|
243
|
+
|
|
244
|
+
;; Verify database is still functional
|
|
245
|
+
(let [result (d/q '[:find ?e ?n :where [?e :name ?n]] @conn)]
|
|
246
|
+
(is (= 2 (count result)) "Both Alice and Bob should still exist"))
|
|
247
|
+
|
|
248
|
+
(d/release conn))
|
|
249
|
+
|
|
250
|
+
;; Cleanup
|
|
251
|
+
(<! (d/delete-database cfg-no-gc)))
|
|
252
|
+
|
|
253
|
+
(catch js/Error e
|
|
254
|
+
(is false (str "Error in online-gc-multi-branch-safety-test: " (.-message e))))
|
|
255
|
+
(finally
|
|
256
|
+
(done)
|
|
257
|
+
(js/process.nextTick
|
|
258
|
+
(fn []
|
|
259
|
+
(.exit js/process 0))))))))
|
|
260
|
+
|
|
261
|
+
(defn -main []
|
|
262
|
+
(t/run-tests 'datahike.test.nodejs-test))
|