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,325 @@
|
|
|
1
|
+
# Time Variance
|
|
2
|
+
|
|
3
|
+
As a [temporal database](https://en.wikipedia.org/wiki/Temporal_database), Datahike tracks transaction time for every change, enabling auditing, analytics, and time-travel queries. Each transaction records `:db/txInstant`, allowing you to view data at different points in time.
|
|
4
|
+
|
|
5
|
+
## Time Travel Views
|
|
6
|
+
|
|
7
|
+
| View | Function | Returns | Use Case |
|
|
8
|
+
|------|----------|---------|----------|
|
|
9
|
+
| **Current** | `@conn` or `(d/db conn)` | Latest state | Normal queries |
|
|
10
|
+
| **As-of** | `(d/as-of db date)` | State at specific time | "What did the data look like on July 1st?" |
|
|
11
|
+
| **History** | `(d/history db)` | All versions (current + historical) | Audit trails, change analysis |
|
|
12
|
+
| **Since** | `(d/since db date)` | Changes after specific time | "What changed since yesterday?" |
|
|
13
|
+
|
|
14
|
+
**Related:** For git-like branching and merging of database snapshots, see [Versioning](versioning.md). For removing old historical data from storage, see [Garbage Collection](gc.md).
|
|
15
|
+
|
|
16
|
+
## Disabling History
|
|
17
|
+
|
|
18
|
+
If you don't need time-travel queries, disable history tracking to save storage:
|
|
19
|
+
|
|
20
|
+
```clojure
|
|
21
|
+
(require '[datahike.api :as d])
|
|
22
|
+
|
|
23
|
+
(d/create-database {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440000"} :keep-history? false})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Trade-off:** Saves storage and improves write performance, but removes as-of/history/since queries and purging capabilities.
|
|
27
|
+
|
|
28
|
+
## Setup for Examples
|
|
29
|
+
|
|
30
|
+
All examples below use this shared setup:
|
|
31
|
+
|
|
32
|
+
```clojure
|
|
33
|
+
(require '[datahike.api :as d])
|
|
34
|
+
|
|
35
|
+
;; Simple schema for person data
|
|
36
|
+
(def schema [{:db/ident :name
|
|
37
|
+
:db/valueType :db.type/string
|
|
38
|
+
:db/unique :db.unique/identity
|
|
39
|
+
:db/index true
|
|
40
|
+
:db/cardinality :db.cardinality/one}
|
|
41
|
+
{:db/ident :age
|
|
42
|
+
:db/valueType :db.type/long
|
|
43
|
+
:db/cardinality :db.cardinality/one}])
|
|
44
|
+
|
|
45
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440001"} :initial-tx schema})
|
|
46
|
+
|
|
47
|
+
(d/create-database cfg)
|
|
48
|
+
(def conn (d/connect cfg))
|
|
49
|
+
|
|
50
|
+
;; Query to find names and ages
|
|
51
|
+
(def query '[:find ?n ?a :where [?e :name ?n] [?e :age ?a]])
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## DB (Current State)
|
|
55
|
+
|
|
56
|
+
`@conn` or `(d/db conn)` returns the current state - the most common view for queries:
|
|
57
|
+
|
|
58
|
+
```clojure
|
|
59
|
+
|
|
60
|
+
;; add first data
|
|
61
|
+
(d/transact conn {:tx-data [{:name "Alice" :age 25}]})
|
|
62
|
+
|
|
63
|
+
;; define simple query for name and age
|
|
64
|
+
(def query '[:find ?n ?a :where [?e :name ?n] [?e :age ?a]])
|
|
65
|
+
|
|
66
|
+
(d/q query @conn)
|
|
67
|
+
;; => #{["Alice" 25]}
|
|
68
|
+
|
|
69
|
+
;; update the entity
|
|
70
|
+
(d/transact conn {:tx-data [{:db/id [:name "Alice"] :age 30}]})
|
|
71
|
+
|
|
72
|
+
;; `db` reflects the latest state of the database
|
|
73
|
+
(d/q query @conn)
|
|
74
|
+
;; => #{["Alice" 30]}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## As-Of
|
|
78
|
+
|
|
79
|
+
You can query the database at a specific point in time using `as-of`:
|
|
80
|
+
|
|
81
|
+
```clojure
|
|
82
|
+
(require '[datahike.api :as d])
|
|
83
|
+
|
|
84
|
+
;; define simple schema
|
|
85
|
+
(def schema [{:db/ident :name
|
|
86
|
+
:db/valueType :db.type/string
|
|
87
|
+
:db/unique :db.unique/identity
|
|
88
|
+
:db/index true
|
|
89
|
+
:db/cardinality :db.cardinality/one}
|
|
90
|
+
{:db/ident :age
|
|
91
|
+
:db/valueType :db.type/long
|
|
92
|
+
:db/cardinality :db.cardinality/one}])
|
|
93
|
+
|
|
94
|
+
;; create our temporal database
|
|
95
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440003"} :initial-tx schema})
|
|
96
|
+
|
|
97
|
+
(d/create-database cfg)
|
|
98
|
+
|
|
99
|
+
(def conn (d/connect cfg))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
;; add first data
|
|
103
|
+
(d/transact conn {:tx-data [{:name "Alice" :age 25}]})
|
|
104
|
+
|
|
105
|
+
(def first-date (java.util.Date.))
|
|
106
|
+
|
|
107
|
+
;; define simple query for name and age
|
|
108
|
+
(def query '[:find ?n ?a :where [?e :name ?n] [?e :age ?a]])
|
|
109
|
+
|
|
110
|
+
(d/q query @conn)
|
|
111
|
+
;; => #{["Alice" 25]}
|
|
112
|
+
|
|
113
|
+
;; update the entity
|
|
114
|
+
(d/transact conn {:tx-data [{:db/id [:name "Alice"] :age 30}]})
|
|
115
|
+
|
|
116
|
+
;; let's compare the current and the as-of value:
|
|
117
|
+
(d/q query @conn)
|
|
118
|
+
;; => #{["Alice" 30]}
|
|
119
|
+
|
|
120
|
+
(d/q query (d/as-of @conn first-date))
|
|
121
|
+
;; => #{["Alice" 25]}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## History
|
|
125
|
+
|
|
126
|
+
For querying all data over the whole time span you may use `history` which joins
|
|
127
|
+
current and all historical data:
|
|
128
|
+
|
|
129
|
+
```clojure
|
|
130
|
+
(require '[datahike.api :as d])
|
|
131
|
+
|
|
132
|
+
;; define simple schema
|
|
133
|
+
(def schema [{:db/ident :name
|
|
134
|
+
:db/valueType :db.type/string
|
|
135
|
+
:db/unique :db.unique/identity
|
|
136
|
+
:db/index true
|
|
137
|
+
:db/cardinality :db.cardinality/one}
|
|
138
|
+
{:db/ident :age
|
|
139
|
+
:db/valueType :db.type/long
|
|
140
|
+
:db/cardinality :db.cardinality/one}])
|
|
141
|
+
|
|
142
|
+
;; create our temporal database
|
|
143
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440004"} :initial-tx schema})
|
|
144
|
+
|
|
145
|
+
(d/create-database cfg)
|
|
146
|
+
|
|
147
|
+
(def conn (d/connect cfg))
|
|
148
|
+
|
|
149
|
+
;; add first data
|
|
150
|
+
(d/transact conn {:tx-data [{:name "Alice" :age 25}]})
|
|
151
|
+
|
|
152
|
+
;; define simple query for name and age
|
|
153
|
+
(def query '[:find ?n ?a :where [?e :name ?n] [?e :age ?a]])
|
|
154
|
+
|
|
155
|
+
;; history should have only one entry
|
|
156
|
+
(d/q query (d/history @conn))
|
|
157
|
+
;; => #{["Alice" 25]}
|
|
158
|
+
|
|
159
|
+
;; update the entity
|
|
160
|
+
(d/transact conn {:tx-data [{:db/id [:name "Alice"] :age 30}]})
|
|
161
|
+
|
|
162
|
+
;; both entries are present
|
|
163
|
+
(d/q query (d/history @conn))
|
|
164
|
+
;; => #{["Alice" 30] ["Alice" 25]}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Since
|
|
168
|
+
|
|
169
|
+
Changes since a specific point in time can be searched by using the `since`
|
|
170
|
+
database:
|
|
171
|
+
|
|
172
|
+
```clojure
|
|
173
|
+
(require '[datahike.api :as d])
|
|
174
|
+
|
|
175
|
+
;; define simple schema
|
|
176
|
+
(def schema [{:db/ident :name
|
|
177
|
+
:db/valueType :db.type/string
|
|
178
|
+
:db/unique :db.unique/identity
|
|
179
|
+
:db/index true
|
|
180
|
+
:db/cardinality :db.cardinality/one}
|
|
181
|
+
{:db/ident :age
|
|
182
|
+
:db/valueType :db.type/long
|
|
183
|
+
:db/cardinality :db.cardinality/one}])
|
|
184
|
+
|
|
185
|
+
;; create our temporal database
|
|
186
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440005"} :initial-tx schema})
|
|
187
|
+
|
|
188
|
+
(d/create-database cfg)
|
|
189
|
+
|
|
190
|
+
(def conn (d/connect cfg))
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
;; add first data
|
|
194
|
+
(d/transact conn {:tx-data [{:name "Alice" :age 25}]})
|
|
195
|
+
|
|
196
|
+
(def first-date (java.util.Date.))
|
|
197
|
+
|
|
198
|
+
;; define simple query for name and age
|
|
199
|
+
(def query '[:find ?n ?a :where [?e :name ?n] [?e :age ?a]])
|
|
200
|
+
|
|
201
|
+
(d/q query @conn)
|
|
202
|
+
;; => #{["Alice" 25]}
|
|
203
|
+
|
|
204
|
+
;; update the entity
|
|
205
|
+
(d/transact conn {:tx-data [{:db/id [:name "Alice"] :age 30}]})
|
|
206
|
+
|
|
207
|
+
;; let's compare the current and the as-of value:
|
|
208
|
+
(d/q query @conn)
|
|
209
|
+
;; => #{["Alice" 30]}
|
|
210
|
+
|
|
211
|
+
;; now we want to know any additions after a specific time
|
|
212
|
+
(d/q query (d/since @conn first-date))
|
|
213
|
+
;; => {}, because :name was transacted before the first date
|
|
214
|
+
|
|
215
|
+
;; let's build a query where we use the latest db to find the name and the since db to find out who's age changed
|
|
216
|
+
(d/q '[:find ?n ?a
|
|
217
|
+
:in $ $since
|
|
218
|
+
:where
|
|
219
|
+
[$ ?e :name ?n]
|
|
220
|
+
[$since ?e :age ?a]]
|
|
221
|
+
@conn
|
|
222
|
+
(d/since @conn first-date))
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Meta Entity
|
|
226
|
+
|
|
227
|
+
With each transaction a meta entity is added to the index that stores the
|
|
228
|
+
current point in time in the `:db/txInstant` attribute.
|
|
229
|
+
|
|
230
|
+
With this data present in the current index, you can search and analyze them for
|
|
231
|
+
your purposes.
|
|
232
|
+
|
|
233
|
+
```clojure
|
|
234
|
+
(require '[datahike.api :as d])
|
|
235
|
+
|
|
236
|
+
;; define simple schema
|
|
237
|
+
(def schema [{:db/ident :name
|
|
238
|
+
:db/valueType :db.type/string
|
|
239
|
+
:db/unique :db.unique/identity
|
|
240
|
+
:db/index true
|
|
241
|
+
:db/cardinality :db.cardinality/one}
|
|
242
|
+
{:db/ident :age
|
|
243
|
+
:db/valueType :db.type/long
|
|
244
|
+
:db/cardinality :db.cardinality/one}])
|
|
245
|
+
|
|
246
|
+
;; create our temporal database
|
|
247
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440006"} :initial-tx schema})
|
|
248
|
+
|
|
249
|
+
(d/create-database cfg)
|
|
250
|
+
|
|
251
|
+
(def conn (d/connect cfg))
|
|
252
|
+
|
|
253
|
+
;; add first data
|
|
254
|
+
(d/transact conn {:tx-data [{:name "Alice" :age 25}]})
|
|
255
|
+
|
|
256
|
+
;; let's find all transaction dates, should be two: one for the schema and one
|
|
257
|
+
;; for the first data
|
|
258
|
+
(d/q '[:find ?t :where [_ :db/txInstant ?t]] @conn)
|
|
259
|
+
;; => #{[#inst "2019-08-16T11:40:28.794-00:00"] [#inst "2019-08-16T11:40:26.587-00:00"]}
|
|
260
|
+
|
|
261
|
+
;; you might join over the tx id to get the date of any transaction
|
|
262
|
+
(d/q '[:find ?n ?t :where [_ :name ?n ?tx] [?tx :db/txInstant ?t]] @conn)
|
|
263
|
+
;; => #{["Alice" #inst "2019-08-16T11:40:28.794-00:00"]}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Data Purging
|
|
267
|
+
|
|
268
|
+
**Retraction vs Purging:** Normal retractions preserve data in history. Purging permanently deletes data from both current and historical indices.
|
|
269
|
+
|
|
270
|
+
**When to purge:** Privacy regulations (GDPR, HIPAA, CCPA) requiring "right to deletion", sensitive data removal, or retention policy enforcement.
|
|
271
|
+
|
|
272
|
+
**⚠️ Warning:** Purging is permanent and irreversible. Use only when legally required or explicitly needed.
|
|
273
|
+
|
|
274
|
+
### Purge Operations
|
|
275
|
+
|
|
276
|
+
- **`:db/purge`** - Remove specific datom (entity, attribute, value)
|
|
277
|
+
- **`:db.purge/attribute`** - Remove all values for an attribute on an entity
|
|
278
|
+
- **`:db.purge/entity`** - Remove entire entity and all its attributes
|
|
279
|
+
- **`:db.history.purge/before`** - Remove all historical data before a date (retention policy cleanup)
|
|
280
|
+
|
|
281
|
+
```clojure
|
|
282
|
+
(require '[datahike.api :as d])
|
|
283
|
+
|
|
284
|
+
;; define simple schema
|
|
285
|
+
(def schema [{:db/ident :name
|
|
286
|
+
:db/valueType :db.type/string
|
|
287
|
+
:db/unique :db.unique/identity
|
|
288
|
+
:db/index true
|
|
289
|
+
:db/cardinality :db.cardinality/one}
|
|
290
|
+
{:db/ident :age
|
|
291
|
+
:db/valueType :db.type/long
|
|
292
|
+
:db/cardinality :db.cardinality/one}])
|
|
293
|
+
|
|
294
|
+
;; create our temporal database
|
|
295
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440007"} :initial-tx schema})
|
|
296
|
+
|
|
297
|
+
(d/create-database cfg)
|
|
298
|
+
|
|
299
|
+
(def conn (d/connect cfg))
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
;; add data
|
|
303
|
+
(d/transact conn {:tx-data [{:name "Alice" :age 25}]})
|
|
304
|
+
|
|
305
|
+
;; define simple query for name and age
|
|
306
|
+
(def query '[:find ?n ?a :where [?e :name ?n] [?e :age ?a]])
|
|
307
|
+
|
|
308
|
+
(d/q query @conn)
|
|
309
|
+
;; => #{["Alice" 25]}
|
|
310
|
+
|
|
311
|
+
(d/transact conn {:tx-data [[:db.purge/entity [:name "Alice"]]]})
|
|
312
|
+
|
|
313
|
+
;; data was removed from current database view
|
|
314
|
+
(d/q query @conn)
|
|
315
|
+
;; => #{}
|
|
316
|
+
|
|
317
|
+
;; data was also removed from history
|
|
318
|
+
(d/q query (d/history @conn))
|
|
319
|
+
;; => #{}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Requirements for purging:**
|
|
323
|
+
- History must be enabled (`:keep-history? true`)
|
|
324
|
+
- Cannot purge schema attributes
|
|
325
|
+
- Use retractions for normal data lifecycle - reserve purging for compliance requirements
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Unstructured Input Support
|
|
2
|
+
|
|
3
|
+
This experimental feature enables automatic schema inference from unstructured data (like JSON/EDN), allowing you to store richly nested data in Datahike with minimal setup.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The unstructured input feature automatically:
|
|
8
|
+
|
|
9
|
+
- Infers schema definitions from your data structure
|
|
10
|
+
- Handles nested objects, collections, and primitive values
|
|
11
|
+
- Converts complex data structures to Datahike's entity model
|
|
12
|
+
- Works with both schema-on-read and schema-on-write configurations
|
|
13
|
+
|
|
14
|
+
## Basic Usage
|
|
15
|
+
|
|
16
|
+
```clojure
|
|
17
|
+
(require '[datahike.api :as d])
|
|
18
|
+
(require '[datahike.experimental.unstructured :as du])
|
|
19
|
+
|
|
20
|
+
;; Initialize database (schema-on-read is best for unstructured data)
|
|
21
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440000"}
|
|
22
|
+
:schema-flexibility :read})
|
|
23
|
+
(d/create-database cfg)
|
|
24
|
+
(def conn (d/connect cfg))
|
|
25
|
+
|
|
26
|
+
;; Insert complex unstructured data with a single function call
|
|
27
|
+
(du/transact-unstructured conn
|
|
28
|
+
{:name "Alice"
|
|
29
|
+
:age 42
|
|
30
|
+
:addresses [{:city "New York" :primary true}
|
|
31
|
+
{:city "London" :primary false}]
|
|
32
|
+
:skills ["programming" "design"]})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Type and Cardinality Mapping
|
|
36
|
+
|
|
37
|
+
The feature maps runtime values to Datahike types according to this conversion:
|
|
38
|
+
|
|
39
|
+
| Input Type | Datahike Type | Cardinality |
|
|
40
|
+
|------------|---------------|-------------|
|
|
41
|
+
| Integer | `:db.type/long` | `:db.cardinality/one` |
|
|
42
|
+
| Float | `:db.type/float` | `:db.cardinality/one` |
|
|
43
|
+
| Double | `:db.type/double` | `:db.cardinality/one` |
|
|
44
|
+
| String | `:db.type/string` | `:db.cardinality/one` |
|
|
45
|
+
| Boolean | `:db.type/boolean` | `:db.cardinality/one` |
|
|
46
|
+
| Keyword | `:db.type/keyword` | `:db.cardinality/one` |
|
|
47
|
+
| Symbol | `:db.type/symbol` | `:db.cardinality/one` |
|
|
48
|
+
| UUID | `:db.type/uuid` | `:db.cardinality/one` |
|
|
49
|
+
| Date | `:db.type/instant` | `:db.cardinality/one` |
|
|
50
|
+
| Map | `:db.type/ref` | `:db.cardinality/one` |
|
|
51
|
+
| Vector/List (with values) | Based on first value | `:db.cardinality/many` |
|
|
52
|
+
| Empty Vector/List | No schema generated until values exist | - |
|
|
53
|
+
|
|
54
|
+
### Array Handling and Cardinality-Many
|
|
55
|
+
|
|
56
|
+
When you provide an array/vector value, the system automatically:
|
|
57
|
+
|
|
58
|
+
1. Infers the element type from the first item in the collection
|
|
59
|
+
2. Sets the attribute's cardinality to `:db.cardinality/many`
|
|
60
|
+
3. Stores each value in the array as a separate datom
|
|
61
|
+
|
|
62
|
+
This means you can model one-to-many relationships naturally:
|
|
63
|
+
|
|
64
|
+
```clojure
|
|
65
|
+
;; This will create tags with cardinality/many
|
|
66
|
+
(du/transact-unstructured conn
|
|
67
|
+
{:name "Alice"
|
|
68
|
+
:tags ["clojure" "database" "datalog"]})
|
|
69
|
+
|
|
70
|
+
;; Later you can add more tags without losing existing ones
|
|
71
|
+
(d/transact conn [{:db/id [:name "Alice"]
|
|
72
|
+
:tags ["awesome"]}])
|
|
73
|
+
|
|
74
|
+
;; All tags will be preserved
|
|
75
|
+
;; => ["clojure" "database" "datalog" "awesome"]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Advanced Usage
|
|
79
|
+
|
|
80
|
+
### Working with the Schema Directly
|
|
81
|
+
|
|
82
|
+
If you need more control, you can process the data first to examine the schema:
|
|
83
|
+
|
|
84
|
+
```clojure
|
|
85
|
+
;; Process data to get both schema and transaction data
|
|
86
|
+
(def result (du/process-unstructured-data
|
|
87
|
+
{:user {:name "Bob"
|
|
88
|
+
:roles ["admin" "user"]}}))
|
|
89
|
+
|
|
90
|
+
;; Examine the inferred schema
|
|
91
|
+
(println (:schema result))
|
|
92
|
+
;; => [{:db/ident :user, :db/valueType :db.type/ref, :db/cardinality :db.cardinality/one}
|
|
93
|
+
;; {:db/ident :name, :db/valueType :db.type/string, :db/cardinality :db.cardinality/one}
|
|
94
|
+
;; {:db/ident :roles, :db/valueType :db.type/string, :db/cardinality :db.cardinality/many}]
|
|
95
|
+
|
|
96
|
+
;; Manually transact the schema first, if needed
|
|
97
|
+
(d/transact conn (:schema result))
|
|
98
|
+
|
|
99
|
+
;; Then transact the data
|
|
100
|
+
(d/transact conn (:tx-data result))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Schema-on-Write Databases
|
|
104
|
+
|
|
105
|
+
For schema-on-write databases (the default), the feature automatically:
|
|
106
|
+
|
|
107
|
+
1. Infers schema definitions from your data
|
|
108
|
+
2. Adds any missing schema attributes in the same transaction
|
|
109
|
+
3. Validates compatibility with existing schema
|
|
110
|
+
4. Throws helpful errors if there's a conflict
|
|
111
|
+
|
|
112
|
+
This makes the API particularly powerful for schema-on-write - you get the benefits of schema validation without the manual work of defining schemas upfront.
|
|
113
|
+
|
|
114
|
+
```clojure
|
|
115
|
+
;; With a schema-on-write database
|
|
116
|
+
(def cfg {:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440001"}})
|
|
117
|
+
(d/create-database cfg)
|
|
118
|
+
(def conn (d/connect cfg))
|
|
119
|
+
|
|
120
|
+
;; This will add schema and data in a single transaction
|
|
121
|
+
(du/transact-unstructured conn {:name "Charlie" :age 35})
|
|
122
|
+
|
|
123
|
+
;; Later, you can add entities with new attributes
|
|
124
|
+
;; The schema will automatically be extended
|
|
125
|
+
(du/transact-unstructured conn {:name "Diana"
|
|
126
|
+
:age 28
|
|
127
|
+
:email "diana@example.com"}) ;; New attribute!
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The feature supports incremental schema evolution - as your data model grows, the schema grows with it.
|
|
131
|
+
|
|
132
|
+
### Schema Conflicts and Error Handling
|
|
133
|
+
|
|
134
|
+
When using `transact-unstructured` with schema-on-write databases, the feature automatically detects schema conflicts. For example, if you've defined `:score` as a number but try to insert it as a string:
|
|
135
|
+
|
|
136
|
+
```clojure
|
|
137
|
+
;; First, establish schema for score as a number
|
|
138
|
+
(d/transact conn [{:db/ident :score
|
|
139
|
+
:db/valueType :db.type/long
|
|
140
|
+
:db/cardinality :db.cardinality/one}])
|
|
141
|
+
|
|
142
|
+
;; Add a valid score
|
|
143
|
+
(d/transact conn [{:score 100}])
|
|
144
|
+
|
|
145
|
+
;; This will fail with a schema conflict error:
|
|
146
|
+
(du/transact-unstructured conn {:name "Bob", :score "High"})
|
|
147
|
+
;; => ExceptionInfo: Schema conflict detected with existing database schema
|
|
148
|
+
;; {:conflicts [{:attr :score, :conflict :value-type,
|
|
149
|
+
;; :existing :db.type/long, :inferred :db.type/string}]}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The error messages provide details about the specific conflicts, making it easy to diagnose and fix issues.
|
|
153
|
+
|
|
154
|
+
## Future Directions
|
|
155
|
+
|
|
156
|
+
This experimental feature will likely be enhanced with:
|
|
157
|
+
|
|
158
|
+
- Better handling of schema conflicts
|
|
159
|
+
- Schema upgrading for schema-on-write databases
|
|
160
|
+
- Performance optimizations for large datasets
|
|
161
|
+
- Special handling for additional data types
|
|
162
|
+
|
|
163
|
+
## Limitations
|
|
164
|
+
|
|
165
|
+
- Complex querying across nested entities requires knowledge of how the data was transformed
|
|
166
|
+
- For schema-on-write, existing schema attributes must be compatible
|
|
167
|
+
- Very large nested data structures might require chunking (not yet implemented)
|