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,239 @@
|
|
|
1
|
+
(ns ^:no-doc datahike.writer
|
|
2
|
+
(:require [superv.async :refer [S thread-try <?- go-try]]
|
|
3
|
+
[taoensso.timbre :as log]
|
|
4
|
+
[datahike.core]
|
|
5
|
+
[datahike.writing :as w]
|
|
6
|
+
[datahike.gc :as gc]
|
|
7
|
+
[datahike.tools :as dt :refer [throwable-promise get-time-ms]]
|
|
8
|
+
[clojure.core.async :refer [chan close! promise-chan put! go go-loop <! >! poll! buffer timeout]]
|
|
9
|
+
#?(:cljs [cljs.core.async.impl.channels :refer [ManyToManyChannel]]))
|
|
10
|
+
#?(:clj (:import [clojure.core.async.impl.channels ManyToManyChannel])))
|
|
11
|
+
|
|
12
|
+
(defn chan? [x]
|
|
13
|
+
(instance? ManyToManyChannel x))
|
|
14
|
+
|
|
15
|
+
(defprotocol PWriter
|
|
16
|
+
(-dispatch! [_ arg-map] "Returns a channel that resolves when the transaction finalizes.")
|
|
17
|
+
(-shutdown [_] "Returns a channel that resolves when the writer has shut down.")
|
|
18
|
+
(-streaming? [_] "Returns whether the transactor is streaming updates directly into the connection, so it does not need to fetch from store on read."))
|
|
19
|
+
|
|
20
|
+
(defrecord LocalWriter [thread streaming? transaction-queue-size commit-queue-size
|
|
21
|
+
transaction-queue commit-queue]
|
|
22
|
+
PWriter
|
|
23
|
+
(-dispatch! [_ arg-map]
|
|
24
|
+
(let [p (promise-chan)]
|
|
25
|
+
(put! transaction-queue (assoc arg-map :callback p))
|
|
26
|
+
p))
|
|
27
|
+
(-shutdown [_]
|
|
28
|
+
(close! transaction-queue)
|
|
29
|
+
thread)
|
|
30
|
+
(-streaming? [_] streaming?))
|
|
31
|
+
|
|
32
|
+
(def ^:const DEFAULT_QUEUE_SIZE 120000)
|
|
33
|
+
|
|
34
|
+
;; minimum wait time between commits in ms
|
|
35
|
+
;; this reduces write pressure on the storage
|
|
36
|
+
;; at the cost of higher latency
|
|
37
|
+
(def ^:const DEFAULT_COMMIT_WAIT_TIME 0) ;; in ms
|
|
38
|
+
|
|
39
|
+
(defn create-thread
|
|
40
|
+
"Creates new transaction thread"
|
|
41
|
+
[connection write-fn-map transaction-queue-size commit-queue-size commit-wait-time]
|
|
42
|
+
(let [transaction-queue-buffer (buffer transaction-queue-size)
|
|
43
|
+
transaction-queue (chan transaction-queue-buffer)
|
|
44
|
+
commit-queue-buffer (buffer commit-queue-size)
|
|
45
|
+
commit-queue (chan commit-queue-buffer)]
|
|
46
|
+
[transaction-queue commit-queue
|
|
47
|
+
(#?(:clj thread-try :cljs try)
|
|
48
|
+
S
|
|
49
|
+
(do
|
|
50
|
+
;; processing loop
|
|
51
|
+
(go-try S
|
|
52
|
+
;; delay processing until the writer we are part of in connection is set
|
|
53
|
+
(while (not (:writer @(:wrapped-atom connection)))
|
|
54
|
+
(<! (timeout 10)))
|
|
55
|
+
(loop [old @(:wrapped-atom connection)]
|
|
56
|
+
(if-let [{:keys [op args callback] :as invocation} (<?- transaction-queue)]
|
|
57
|
+
(do
|
|
58
|
+
(when (> (count transaction-queue-buffer) (* 0.9 transaction-queue-size))
|
|
59
|
+
(log/warn "Transaction queue buffer more than 90% full, "
|
|
60
|
+
(count transaction-queue-buffer) "of" transaction-queue-size " filled."
|
|
61
|
+
"Reduce transaction frequency."))
|
|
62
|
+
(let [;; TODO remove this after import is ported to writer API
|
|
63
|
+
old (if-not (= (:max-tx old) (:max-tx @(:wrapped-atom connection)))
|
|
64
|
+
(assoc old :max-tx (:max-tx @(:wrapped-atom connection)))
|
|
65
|
+
old)
|
|
66
|
+
|
|
67
|
+
op-fn (write-fn-map op)
|
|
68
|
+
res (try
|
|
69
|
+
(apply op-fn old args)
|
|
70
|
+
;; Catch all Throwables to handle AssertionError and other Errors
|
|
71
|
+
;; These should crash the writer, but we deliver to callback first to prevent hangs
|
|
72
|
+
(catch #?(:clj Throwable :cljs js/Error) e
|
|
73
|
+
(log/error "Error during invocation" invocation e args)
|
|
74
|
+
;; take a guess that a NPE was triggered by an invalid connection
|
|
75
|
+
;; short circuit on errors
|
|
76
|
+
#?(:cljs (put! callback e)
|
|
77
|
+
:clj
|
|
78
|
+
(put! callback
|
|
79
|
+
(if (= (type e) NullPointerException)
|
|
80
|
+
(ex-info "Null pointer encountered in invocation. Connection may have been invalidated, e.g. through db deletion, and needs to be released everywhere."
|
|
81
|
+
{:type :writer-error-during-invocation
|
|
82
|
+
:invocation invocation
|
|
83
|
+
:connection connection
|
|
84
|
+
:error e})
|
|
85
|
+
e)))
|
|
86
|
+
;; Re-throw Errors (AssertionError, OutOfMemoryError, etc.) to crash the writer
|
|
87
|
+
;; Only Exceptions should be handled and allow the writer to continue
|
|
88
|
+
#?(:clj (when (instance? Error e)
|
|
89
|
+
(throw e)))
|
|
90
|
+
:error))]
|
|
91
|
+
(cond (chan? res)
|
|
92
|
+
;; async op, run in parallel in background, no sequential commit handling needed
|
|
93
|
+
(do
|
|
94
|
+
(go (>! callback (<! res)))
|
|
95
|
+
(recur old))
|
|
96
|
+
|
|
97
|
+
(not= res :error)
|
|
98
|
+
(do
|
|
99
|
+
(when (> (count commit-queue-buffer) (/ commit-queue-size 2))
|
|
100
|
+
(log/warn "Commit queue buffer more than 50% full, "
|
|
101
|
+
(count commit-queue-buffer) "of" commit-queue-size " filled."
|
|
102
|
+
"Throttling transaction processing. Reduce transaction frequency and check your storage throughput.")
|
|
103
|
+
(<! (timeout 50)))
|
|
104
|
+
(put! commit-queue [res callback])
|
|
105
|
+
(recur (:db-after res)))
|
|
106
|
+
:else
|
|
107
|
+
(recur old))))
|
|
108
|
+
(do
|
|
109
|
+
(close! commit-queue)
|
|
110
|
+
(log/debug "Writer thread gracefully closed")))))
|
|
111
|
+
;; commit loop
|
|
112
|
+
(go-try S
|
|
113
|
+
(loop [tx (<?- commit-queue)]
|
|
114
|
+
(when tx
|
|
115
|
+
(let [txs (into [tx] (take-while some?) (repeatedly #(poll! commit-queue)))]
|
|
116
|
+
;; empty channel of pending transactions
|
|
117
|
+
(log/trace "Batched transaction count: " (count txs))
|
|
118
|
+
;; commit latest tx to disk
|
|
119
|
+
(let [db (:db-after (first (peek txs)))]
|
|
120
|
+
(try
|
|
121
|
+
(let [start-ts (get-time-ms)
|
|
122
|
+
{{:keys [datahike/commit-id]} :meta
|
|
123
|
+
:as commit-db} (<?- (w/commit! db nil false))
|
|
124
|
+
commit-time (- (get-time-ms) start-ts)]
|
|
125
|
+
(log/trace "Commit time (ms): " commit-time)
|
|
126
|
+
(reset! connection commit-db)
|
|
127
|
+
;; notify all processes that transaction is complete
|
|
128
|
+
(doseq [[tx-report callback] txs]
|
|
129
|
+
(let [tx-report (-> tx-report
|
|
130
|
+
(assoc-in [:tx-meta :db/commitId] commit-id)
|
|
131
|
+
(assoc :db-after commit-db))]
|
|
132
|
+
(>! callback tx-report))))
|
|
133
|
+
(catch #?(:clj Throwable :cljs js/Error) e
|
|
134
|
+
(doseq [[_ callback] txs]
|
|
135
|
+
(put! callback e))
|
|
136
|
+
(log/error "Writer thread shutting down because of commit error." e)
|
|
137
|
+
(close! commit-queue)
|
|
138
|
+
(close! transaction-queue)
|
|
139
|
+
;; Re-throw Errors (AssertionError, OutOfMemoryError, etc.) to crash the writer
|
|
140
|
+
#?(:clj (when (instance? Error e)
|
|
141
|
+
(throw e)))))
|
|
142
|
+
(<! (timeout commit-wait-time))
|
|
143
|
+
(recur (<?- commit-queue)))))))))]))
|
|
144
|
+
|
|
145
|
+
;; public API to internal mapping
|
|
146
|
+
(def default-write-fn-map {'transact! w/transact!
|
|
147
|
+
'load-entities w/load-entities
|
|
148
|
+
;; async operations that run in background
|
|
149
|
+
'gc-storage! gc/gc-storage!})
|
|
150
|
+
|
|
151
|
+
(defmulti create-writer
|
|
152
|
+
(fn [writer-config _]
|
|
153
|
+
(:backend writer-config)))
|
|
154
|
+
|
|
155
|
+
(defmethod create-writer :self
|
|
156
|
+
[{:keys [transaction-queue-size commit-queue-size write-fn-map commit-wait-time]} connection]
|
|
157
|
+
(let [transaction-queue-size (or transaction-queue-size DEFAULT_QUEUE_SIZE)
|
|
158
|
+
commit-queue-size (or commit-queue-size DEFAULT_QUEUE_SIZE)
|
|
159
|
+
commit-wait-time (or commit-wait-time DEFAULT_COMMIT_WAIT_TIME)
|
|
160
|
+
[transaction-queue commit-queue thread]
|
|
161
|
+
(create-thread connection
|
|
162
|
+
(merge default-write-fn-map
|
|
163
|
+
write-fn-map)
|
|
164
|
+
transaction-queue-size
|
|
165
|
+
commit-queue-size
|
|
166
|
+
commit-wait-time)]
|
|
167
|
+
(map->LocalWriter
|
|
168
|
+
{:transaction-queue transaction-queue
|
|
169
|
+
:transaction-queue-size transaction-queue-size
|
|
170
|
+
:commit-queue commit-queue
|
|
171
|
+
:commit-queue-size commit-queue-size
|
|
172
|
+
:thread thread
|
|
173
|
+
:streaming? true})))
|
|
174
|
+
|
|
175
|
+
;; Note: :kabel backend is implemented in datahike.kabel.writer
|
|
176
|
+
;; Require that namespace to register the defmethod
|
|
177
|
+
|
|
178
|
+
(defn dispatch! [writer arg-map]
|
|
179
|
+
(-dispatch! writer arg-map))
|
|
180
|
+
|
|
181
|
+
(defn shutdown [writer]
|
|
182
|
+
(-shutdown writer))
|
|
183
|
+
|
|
184
|
+
(defn streaming? [writer]
|
|
185
|
+
(-streaming? writer))
|
|
186
|
+
|
|
187
|
+
(defn backend-dispatch [& args]
|
|
188
|
+
(get-in (first args) [:writer :backend] :self))
|
|
189
|
+
|
|
190
|
+
(defmulti create-database backend-dispatch)
|
|
191
|
+
|
|
192
|
+
(defmethod create-database :self [& args]
|
|
193
|
+
(let [p (throwable-promise)]
|
|
194
|
+
(go
|
|
195
|
+
(#?(:clj deliver :cljs put!) p (<! (apply w/create-database args))))
|
|
196
|
+
p))
|
|
197
|
+
|
|
198
|
+
(defmulti delete-database backend-dispatch)
|
|
199
|
+
|
|
200
|
+
(defmethod delete-database :self [& args]
|
|
201
|
+
(let [p (throwable-promise)]
|
|
202
|
+
(go
|
|
203
|
+
(let [res (<! (apply w/delete-database args))]
|
|
204
|
+
#?(:clj (deliver p res) :cljs (if (nil? res) (close! p) (put! p res)))))
|
|
205
|
+
p))
|
|
206
|
+
|
|
207
|
+
(defn transact!
|
|
208
|
+
[connection arg-map]
|
|
209
|
+
(let [p (throwable-promise)
|
|
210
|
+
writer (:writer @(:wrapped-atom connection))]
|
|
211
|
+
(go
|
|
212
|
+
(let [tx-report (<! (dispatch! writer
|
|
213
|
+
{:op 'transact!
|
|
214
|
+
:args [arg-map]}))]
|
|
215
|
+
(when (map? tx-report) ;; not error
|
|
216
|
+
(doseq [[_ callback] (some-> (:listeners (meta connection)) (deref))]
|
|
217
|
+
(callback tx-report)))
|
|
218
|
+
(#?(:clj deliver :cljs put!) p tx-report)))
|
|
219
|
+
p))
|
|
220
|
+
|
|
221
|
+
(defn load-entities [connection entities]
|
|
222
|
+
(let [p (throwable-promise)
|
|
223
|
+
writer (:writer @(:wrapped-atom connection))]
|
|
224
|
+
(go
|
|
225
|
+
(let [tx-report (<! (dispatch! writer
|
|
226
|
+
{:op 'load-entities
|
|
227
|
+
:args [entities]}))]
|
|
228
|
+
(#?(:clj deliver :cljs put!) p tx-report)))
|
|
229
|
+
p))
|
|
230
|
+
|
|
231
|
+
(defn gc-storage! [conn & args]
|
|
232
|
+
(let [p (throwable-promise)
|
|
233
|
+
writer (:writer @(:wrapped-atom conn))]
|
|
234
|
+
(go
|
|
235
|
+
(let [result (<! (dispatch! writer
|
|
236
|
+
{:op 'gc-storage!
|
|
237
|
+
:args (vec args)}))]
|
|
238
|
+
(#?(:clj deliver :cljs put!) p result)))
|
|
239
|
+
p))
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
(ns datahike.writing
|
|
2
|
+
"Manage all state changes and access to state of durable store."
|
|
3
|
+
(:require [datahike.connections :refer [delete-connection! *connections*]]
|
|
4
|
+
[datahike.db :as db]
|
|
5
|
+
[datahike.db.utils :as dbu]
|
|
6
|
+
[datahike.index :as di]
|
|
7
|
+
[datahike.store :as ds]
|
|
8
|
+
[datahike.tools :as dt]
|
|
9
|
+
[datahike.core :as core]
|
|
10
|
+
[datahike.config :as dc]
|
|
11
|
+
[datahike.schema-cache :as sc]
|
|
12
|
+
[datahike.online-gc :as online-gc]
|
|
13
|
+
[konserve.core :as k]
|
|
14
|
+
[konserve.store :as ks]
|
|
15
|
+
[taoensso.timbre :as log]
|
|
16
|
+
[hasch.core :refer [uuid squuid]]
|
|
17
|
+
[hasch.platform]
|
|
18
|
+
[clojure.core.async :as async :refer [go put!]]
|
|
19
|
+
[superv.async #?(:clj :refer :cljs :refer-macros) [go-try- <?-]]
|
|
20
|
+
[konserve.utils :refer [#?(:clj async+sync) multi-key-capable? *default-sync-translation*]
|
|
21
|
+
#?@(:cljs [:refer-macros [async+sync]])]))
|
|
22
|
+
|
|
23
|
+
;; mapping to storage
|
|
24
|
+
|
|
25
|
+
(defn stored-db? [obj]
|
|
26
|
+
;; TODO use proper schema to match?
|
|
27
|
+
(let [keys-to-check [:eavt-key :aevt-key :avet-key :config
|
|
28
|
+
:max-tx :max-eid :op-count :hash :meta]]
|
|
29
|
+
(= (count (select-keys obj keys-to-check))
|
|
30
|
+
(count keys-to-check))))
|
|
31
|
+
|
|
32
|
+
(defn get-and-clear-pending-kvs!
|
|
33
|
+
"Retrieves and clears pending key-value pairs from the store's pending-writes atom.
|
|
34
|
+
Assumes :pending-writes in store's storage holds an atom of a collection of [key value] pairs."
|
|
35
|
+
[store]
|
|
36
|
+
(let [pending-writes-atom (-> store :storage :pending-writes) ; Assumes :storage key holds the CachedStorage
|
|
37
|
+
kvs-to-write (atom [])]
|
|
38
|
+
(when pending-writes-atom
|
|
39
|
+
;; Atomically get current KVs and reset the pending-writes atom.
|
|
40
|
+
(swap! pending-writes-atom (fn [old-kvs] (reset! kvs-to-write old-kvs) [])))
|
|
41
|
+
@kvs-to-write))
|
|
42
|
+
|
|
43
|
+
(defn db->stored
|
|
44
|
+
"Maps memory db to storage layout. Index flushes will add [k v] pairs to pending-writes."
|
|
45
|
+
[db flush?]
|
|
46
|
+
(when-not (dbu/db? db)
|
|
47
|
+
(dt/raise "Argument is not a database."
|
|
48
|
+
{:type :argument-is-not-a-db
|
|
49
|
+
:argument db}))
|
|
50
|
+
(let [{:keys [eavt aevt avet temporal-eavt temporal-aevt temporal-avet
|
|
51
|
+
schema rschema system-entities ident-ref-map ref-ident-map config
|
|
52
|
+
max-tx max-eid op-count hash meta store]} db
|
|
53
|
+
schema-meta {:schema schema
|
|
54
|
+
:rschema rschema
|
|
55
|
+
:system-entities system-entities
|
|
56
|
+
:ident-ref-map ident-ref-map
|
|
57
|
+
:ref-ident-map ref-ident-map}
|
|
58
|
+
schema-meta-key (uuid schema-meta)
|
|
59
|
+
backend (di/konserve-backend (:index config) store)
|
|
60
|
+
not-in-memory? (not= :memory (-> config :store :backend))
|
|
61
|
+
flush! (and flush? not-in-memory?)
|
|
62
|
+
;; Prepare schema meta KV pair for writing, but don't write it here.
|
|
63
|
+
schema-meta-kv-to-write (when-not (sc/write-cache-has? (:store config) schema-meta-key)
|
|
64
|
+
(sc/add-to-write-cache (:store config) schema-meta-key)
|
|
65
|
+
[schema-meta-key schema-meta])]
|
|
66
|
+
(when-not (sc/cache-has? schema-meta-key)
|
|
67
|
+
(sc/cache-miss schema-meta-key schema-meta))
|
|
68
|
+
[schema-meta-kv-to-write ;; Return [key value] pair or nil
|
|
69
|
+
(merge
|
|
70
|
+
{:schema-meta-key schema-meta-key
|
|
71
|
+
:config config
|
|
72
|
+
:meta meta
|
|
73
|
+
:hash hash
|
|
74
|
+
:max-tx max-tx
|
|
75
|
+
:max-eid max-eid
|
|
76
|
+
:op-count op-count
|
|
77
|
+
;; di/-flush will now add [k v] to pending-writes via CachedStorage
|
|
78
|
+
:eavt-key (cond-> eavt flush! (di/-flush backend))
|
|
79
|
+
:aevt-key (cond-> aevt flush! (di/-flush backend))
|
|
80
|
+
:avet-key (cond-> avet flush! (di/-flush backend))}
|
|
81
|
+
(when (:keep-history? config)
|
|
82
|
+
{:temporal-eavt-key (cond-> temporal-eavt flush! (di/-flush backend))
|
|
83
|
+
:temporal-aevt-key (cond-> temporal-aevt flush! (di/-flush backend))
|
|
84
|
+
:temporal-avet-key (cond-> temporal-avet flush! (di/-flush backend))}))]))
|
|
85
|
+
|
|
86
|
+
(defn stored->db
|
|
87
|
+
"Constructs in-memory db instance from stored map value."
|
|
88
|
+
[stored-db store]
|
|
89
|
+
(let [{:keys [eavt-key aevt-key avet-key
|
|
90
|
+
temporal-eavt-key temporal-aevt-key temporal-avet-key
|
|
91
|
+
schema rschema system-entities ref-ident-map ident-ref-map
|
|
92
|
+
config max-tx max-eid op-count hash meta schema-meta-key]
|
|
93
|
+
:or {op-count 0}} stored-db
|
|
94
|
+
schema-meta (or (sc/cache-lookup schema-meta-key)
|
|
95
|
+
;; not in store in case we load an old db where the schema meta data was inline
|
|
96
|
+
(when-let [schema-meta (k/get store schema-meta-key nil {:sync? true})]
|
|
97
|
+
(sc/cache-miss schema-meta-key schema-meta)
|
|
98
|
+
schema-meta))
|
|
99
|
+
empty (db/empty-db nil config store)]
|
|
100
|
+
(merge
|
|
101
|
+
(assoc empty
|
|
102
|
+
:max-tx max-tx
|
|
103
|
+
:max-eid max-eid
|
|
104
|
+
:config config
|
|
105
|
+
:meta meta
|
|
106
|
+
:schema schema
|
|
107
|
+
:hash hash
|
|
108
|
+
:op-count op-count
|
|
109
|
+
:eavt eavt-key
|
|
110
|
+
:aevt aevt-key
|
|
111
|
+
:avet avet-key
|
|
112
|
+
:temporal-eavt temporal-eavt-key
|
|
113
|
+
:temporal-aevt temporal-aevt-key
|
|
114
|
+
:temporal-avet temporal-avet-key
|
|
115
|
+
:rschema rschema
|
|
116
|
+
:system-entities system-entities
|
|
117
|
+
:ident-ref-map ident-ref-map
|
|
118
|
+
:ref-ident-map ref-ident-map
|
|
119
|
+
:store store)
|
|
120
|
+
schema-meta)))
|
|
121
|
+
|
|
122
|
+
(defn branch-heads-as-commits [store parents]
|
|
123
|
+
(set (doall (for [p parents]
|
|
124
|
+
(do
|
|
125
|
+
(when (nil? p)
|
|
126
|
+
(dt/raise "Parent cannot be nil." {:type :parent-cannot-be-nil
|
|
127
|
+
:parent p}))
|
|
128
|
+
(if-not (keyword? p) p
|
|
129
|
+
(let [{{:keys [datahike/commit-id]} :meta :as old-db}
|
|
130
|
+
(k/get store p nil {:sync? true})]
|
|
131
|
+
(when-not old-db
|
|
132
|
+
(dt/raise "Parent does not exist in store."
|
|
133
|
+
{:type :parent-does-not-exist-in-store
|
|
134
|
+
:parent p}))
|
|
135
|
+
commit-id)))))))
|
|
136
|
+
|
|
137
|
+
(defn create-commit-id [db]
|
|
138
|
+
(let [{:keys [hash max-tx max-eid meta config]} db
|
|
139
|
+
content-uuid (uuid [hash max-tx max-eid meta])]
|
|
140
|
+
(if (:crypto-hash? config)
|
|
141
|
+
content-uuid
|
|
142
|
+
(squuid content-uuid)))) ;; Sequential UUID for better index locality
|
|
143
|
+
|
|
144
|
+
(defn write-pending-kvs!
|
|
145
|
+
"Writes a collection of key-value pairs to the store.
|
|
146
|
+
Handles synchronous and asynchronous writes.
|
|
147
|
+
Assumes it's called within a go-try- block if sync? is false."
|
|
148
|
+
[store kvs sync?]
|
|
149
|
+
(if sync?
|
|
150
|
+
(doseq [[k v] kvs]
|
|
151
|
+
(k/assoc store k v {:sync? true}))
|
|
152
|
+
(let [pending-ops (mapv (fn [[k v]] (k/assoc store k v {:sync? false})) kvs)]
|
|
153
|
+
(go-try- (doseq [op pending-ops] (<?- op))))))
|
|
154
|
+
|
|
155
|
+
(defn commit!
|
|
156
|
+
([db parents]
|
|
157
|
+
(commit! db parents true))
|
|
158
|
+
([db parents sync?]
|
|
159
|
+
(async+sync sync? *default-sync-translation*
|
|
160
|
+
(go-try-
|
|
161
|
+
(let [{:keys [store config]} db
|
|
162
|
+
parents (or parents #{(get config :branch)})
|
|
163
|
+
parents (branch-heads-as-commits store parents)
|
|
164
|
+
cid (create-commit-id db)
|
|
165
|
+
db (-> db
|
|
166
|
+
(assoc-in [:meta :datahike/commit-id] cid)
|
|
167
|
+
(assoc-in [:meta :datahike/parents] parents))
|
|
168
|
+
;; db->stored now returns [schema-meta-kv-to-write db-to-store]
|
|
169
|
+
;; and index flushes will have populated pending-writes
|
|
170
|
+
[schema-meta-kv-to-write db-to-store] (db->stored db true)
|
|
171
|
+
;; Get all pending [k v] pairs (e.g., from index flushes)
|
|
172
|
+
pending-kvs (get-and-clear-pending-kvs! store)]
|
|
173
|
+
|
|
174
|
+
(if (multi-key-capable? store)
|
|
175
|
+
(let [[meta-key meta-val] schema-meta-kv-to-write
|
|
176
|
+
writes-map (cond-> (into {} pending-kvs) ; Initialize with pending KVs
|
|
177
|
+
schema-meta-kv-to-write (assoc meta-key meta-val)
|
|
178
|
+
true (assoc cid db-to-store)
|
|
179
|
+
true (assoc (:branch config) db-to-store))]
|
|
180
|
+
(<?- (k/multi-assoc store writes-map {:sync? sync?})))
|
|
181
|
+
;; Then write schema-meta, commit-log, branch
|
|
182
|
+
(let [[meta-key meta-val] schema-meta-kv-to-write
|
|
183
|
+
schema-meta-written (when schema-meta-kv-to-write
|
|
184
|
+
(k/assoc store meta-key meta-val {:sync? sync?}))
|
|
185
|
+
|
|
186
|
+
;; Make sure all pointed to values are written before the commit log and branch
|
|
187
|
+
_ (when schema-meta-kv-to-write (<?- schema-meta-written))
|
|
188
|
+
_ (<?- (write-pending-kvs! store pending-kvs sync?))
|
|
189
|
+
|
|
190
|
+
commit-log-written (k/assoc store cid db-to-store {:sync? sync?})
|
|
191
|
+
branch-written (k/assoc store (:branch config) db-to-store {:sync? sync?})]
|
|
192
|
+
(when-not sync?
|
|
193
|
+
(<?- commit-log-written)
|
|
194
|
+
(<?- branch-written))))
|
|
195
|
+
|
|
196
|
+
;; Online GC: delete freed addresses after writes are committed
|
|
197
|
+
(when (get-in config [:online-gc :enabled?])
|
|
198
|
+
(<?- (online-gc/online-gc! store (assoc (:online-gc config) :sync? false))))
|
|
199
|
+
|
|
200
|
+
db)))))
|
|
201
|
+
|
|
202
|
+
(defn complete-db-update [old tx-report]
|
|
203
|
+
(let [{:keys [writer]} old
|
|
204
|
+
{:keys [db-after]
|
|
205
|
+
{:keys [db/txInstant]} :tx-meta} tx-report
|
|
206
|
+
new-meta (assoc (:meta db-after) :datahike/updated-at txInstant)
|
|
207
|
+
db (assoc db-after :meta new-meta :writer writer)
|
|
208
|
+
tx-report (assoc tx-report :db-after db)]
|
|
209
|
+
tx-report))
|
|
210
|
+
|
|
211
|
+
(defprotocol PDatabaseManager
|
|
212
|
+
(-create-database [config opts])
|
|
213
|
+
(-delete-database [config])
|
|
214
|
+
(-database-exists? [config]))
|
|
215
|
+
|
|
216
|
+
(defn -database-exists?* [config]
|
|
217
|
+
(let [p (dt/throwable-promise)]
|
|
218
|
+
(go
|
|
219
|
+
(put! p (try
|
|
220
|
+
(let [config (dc/load-config config)
|
|
221
|
+
store-config (:store config)
|
|
222
|
+
;; First check if store exists (avoids exception when store not in registry)
|
|
223
|
+
store-exists? (<?- (ks/store-exists? store-config {:sync? false}))]
|
|
224
|
+
(if store-exists?
|
|
225
|
+
;; Store exists, now check if it contains a database
|
|
226
|
+
(let [raw-store (<?- (ks/connect-store store-config {:sync? false}))
|
|
227
|
+
store (ds/add-cache-and-handlers raw-store config)
|
|
228
|
+
stored-db (<?- (k/get store :db nil {:sync? false}))]
|
|
229
|
+
;; Release store and await completion
|
|
230
|
+
(<?- (ks/release-store store-config store {:sync? false}))
|
|
231
|
+
(some? stored-db))
|
|
232
|
+
;; Store doesn't exist, so database doesn't exist
|
|
233
|
+
false))
|
|
234
|
+
(catch #?(:clj Exception :cljs js/Error) e
|
|
235
|
+
e))))
|
|
236
|
+
p))
|
|
237
|
+
|
|
238
|
+
(defn -create-database* [config deprecated-config]
|
|
239
|
+
(go-try-
|
|
240
|
+
(let [opts {:sync? false}
|
|
241
|
+
{:keys [keep-history?] :as config} (dc/load-config config deprecated-config)
|
|
242
|
+
store-config (:store config)
|
|
243
|
+
store (ds/add-cache-and-handlers (<?- (ks/create-store store-config opts)) config)
|
|
244
|
+
stored-db (<?- (k/get store :db nil opts))
|
|
245
|
+
_ (when stored-db
|
|
246
|
+
(dt/raise "Database already exists."
|
|
247
|
+
{:type :db-already-exists :config store-config}))
|
|
248
|
+
{:keys [eavt aevt avet temporal-eavt temporal-aevt temporal-avet
|
|
249
|
+
schema rschema system-entities ref-ident-map ident-ref-map
|
|
250
|
+
config max-tx max-eid op-count hash meta] :as db}
|
|
251
|
+
(db/empty-db nil config store)
|
|
252
|
+
backend (di/konserve-backend (:index config) store)
|
|
253
|
+
cid (create-commit-id db)
|
|
254
|
+
meta (assoc meta :datahike/commit-id cid)
|
|
255
|
+
schema-meta {:schema schema
|
|
256
|
+
:rschema rschema
|
|
257
|
+
:system-entities system-entities
|
|
258
|
+
:ident-ref-map ident-ref-map
|
|
259
|
+
:ref-ident-map ref-ident-map}
|
|
260
|
+
schema-meta-key (uuid schema-meta)
|
|
261
|
+
;; di/-flush calls will populate pending-writes via CachedStorage
|
|
262
|
+
db-to-store (merge {:max-tx max-tx
|
|
263
|
+
:max-eid max-eid
|
|
264
|
+
:op-count op-count
|
|
265
|
+
:hash hash
|
|
266
|
+
:schema-meta-key schema-meta-key
|
|
267
|
+
:config (update config :initial-tx (comp not empty?))
|
|
268
|
+
:meta meta
|
|
269
|
+
:eavt-key (di/-flush eavt backend)
|
|
270
|
+
:aevt-key (di/-flush aevt backend)
|
|
271
|
+
:avet-key (di/-flush avet backend)}
|
|
272
|
+
(when keep-history?
|
|
273
|
+
{:temporal-eavt-key (di/-flush temporal-eavt backend)
|
|
274
|
+
:temporal-aevt-key (di/-flush temporal-aevt backend)
|
|
275
|
+
:temporal-avet-key (di/-flush temporal-avet backend)}))]
|
|
276
|
+
;;we just created the first data base in this store, so the write cache is empty
|
|
277
|
+
(<?- (k/assoc store schema-meta-key schema-meta opts))
|
|
278
|
+
(sc/add-to-write-cache (:store config) schema-meta-key)
|
|
279
|
+
(when-not (sc/cache-has? schema-meta-key)
|
|
280
|
+
(sc/cache-miss schema-meta-key schema-meta))
|
|
281
|
+
|
|
282
|
+
;; Process pending KVs from index flushes synchronously
|
|
283
|
+
(let [pending-kvs (get-and-clear-pending-kvs! store)]
|
|
284
|
+
(<?- (write-pending-kvs! store pending-kvs false)))
|
|
285
|
+
|
|
286
|
+
(<?- (k/assoc store :branches #{:db} opts))
|
|
287
|
+
(<?- (k/assoc store cid db-to-store opts))
|
|
288
|
+
(<?- (k/assoc store :db db-to-store opts))
|
|
289
|
+
(ks/release-store store-config store)
|
|
290
|
+
config)))
|
|
291
|
+
|
|
292
|
+
(defn -delete-database* [config]
|
|
293
|
+
(go-try-
|
|
294
|
+
(let [config (dc/load-config config {})
|
|
295
|
+
config-store-id (ds/store-identity (:store config))
|
|
296
|
+
active-conns (filter (fn [[store-id _branch]]
|
|
297
|
+
(= store-id config-store-id))
|
|
298
|
+
(keys @*connections*))]
|
|
299
|
+
(sc/clear-write-cache (:store config))
|
|
300
|
+
(doseq [conn active-conns]
|
|
301
|
+
(log/warn "Deleting database without releasing all connections first: " conn "."
|
|
302
|
+
"All connections will be released now, but this cannot be ensured for remote readers.")
|
|
303
|
+
(delete-connection! conn))
|
|
304
|
+
(ks/delete-store (:store config)))))
|
|
305
|
+
|
|
306
|
+
(extend-protocol PDatabaseManager
|
|
307
|
+
#?(:clj String :cljs string)
|
|
308
|
+
(-create-database #?(:clj [uri & opts] :cljs [uri opts])
|
|
309
|
+
(-create-database (dc/uri->config uri) opts))
|
|
310
|
+
|
|
311
|
+
(-delete-database [uri]
|
|
312
|
+
(-delete-database (dc/uri->config uri)))
|
|
313
|
+
|
|
314
|
+
(-database-exists? [uri]
|
|
315
|
+
(-database-exists? (dc/uri->config uri)))
|
|
316
|
+
|
|
317
|
+
#?(:clj clojure.lang.IPersistentMap :cljs PersistentArrayMap)
|
|
318
|
+
(-database-exists? [config]
|
|
319
|
+
(-database-exists?* config))
|
|
320
|
+
(-create-database [config opts]
|
|
321
|
+
(-create-database* config opts))
|
|
322
|
+
(-delete-database [config]
|
|
323
|
+
(-delete-database* config))
|
|
324
|
+
|
|
325
|
+
#?(:cljs PersistentHashMap)
|
|
326
|
+
#?(:cljs
|
|
327
|
+
(-database-exists? [config]
|
|
328
|
+
(-database-exists?* config)))
|
|
329
|
+
#?(:cljs (-create-database [config opts] (-create-database* config opts)))
|
|
330
|
+
#?(:cljs (-delete-database [config] (-delete-database* config))))
|
|
331
|
+
|
|
332
|
+
;; public API
|
|
333
|
+
|
|
334
|
+
(defn create-database
|
|
335
|
+
([]
|
|
336
|
+
(-create-database {} nil))
|
|
337
|
+
([config & opts]
|
|
338
|
+
(-create-database config opts)))
|
|
339
|
+
|
|
340
|
+
(defn delete-database
|
|
341
|
+
([]
|
|
342
|
+
(-delete-database {}))
|
|
343
|
+
;;deprecated
|
|
344
|
+
([config]
|
|
345
|
+
;; TODO log deprecation notice with #54
|
|
346
|
+
(-delete-database config)))
|
|
347
|
+
|
|
348
|
+
(defn database-exists?
|
|
349
|
+
([]
|
|
350
|
+
(-database-exists? {}))
|
|
351
|
+
([config]
|
|
352
|
+
;; TODO log deprecation notice with #54
|
|
353
|
+
(-database-exists? config)))
|
|
354
|
+
|
|
355
|
+
(defn transact! [old {:keys [tx-data tx-meta]}]
|
|
356
|
+
(log/debug "Transacting" (count tx-data) "objects")
|
|
357
|
+
(log/trace "Transaction data" tx-data "with meta:" tx-meta)
|
|
358
|
+
(complete-db-update old (core/with old tx-data tx-meta)))
|
|
359
|
+
|
|
360
|
+
(defn load-entities [old entities]
|
|
361
|
+
(log/debug "Loading" (count entities) " entities.")
|
|
362
|
+
(complete-db-update old (core/load-entities-with old entities nil)))
|
package/src/deps.cljs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{:externs ["datahike/externs.js"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
(ns ^:no-doc datahike.index.hitchhiker-tree.insert
|
|
2
|
+
(:require [hitchhiker.tree :as tree]
|
|
3
|
+
[hitchhiker.tree.op :as op]))
|
|
4
|
+
|
|
5
|
+
(defn mask [new indices]
|
|
6
|
+
(reduce (fn [mask pos]
|
|
7
|
+
(assoc mask pos (nth new pos)))
|
|
8
|
+
[nil nil nil nil]
|
|
9
|
+
indices))
|
|
10
|
+
|
|
11
|
+
(defn equals-at-indices?
|
|
12
|
+
"Returns true if 'k1' and 'k2' are equals at positions given in 'indices'."
|
|
13
|
+
[indices k1 k2]
|
|
14
|
+
(reduce (fn [_ i]
|
|
15
|
+
(if (= (nth k2 i) (nth k1 i))
|
|
16
|
+
true
|
|
17
|
+
(reduced false)))
|
|
18
|
+
true
|
|
19
|
+
indices))
|
|
20
|
+
|
|
21
|
+
(defn exists-old?
|
|
22
|
+
"Returns true if 'new' already exists in 'old-keys'."
|
|
23
|
+
[old-keys new]
|
|
24
|
+
(when (seq old-keys)
|
|
25
|
+
(let [indices [0 1 2]
|
|
26
|
+
mask (mask new indices)]
|
|
27
|
+
(when-let [candidates (subseq old-keys >= mask)]
|
|
28
|
+
(->> candidates
|
|
29
|
+
ffirst
|
|
30
|
+
(equals-at-indices? indices new))))))
|
|
31
|
+
|
|
32
|
+
(defrecord InsertOp [key op-count]
|
|
33
|
+
op/IOperation
|
|
34
|
+
(-insertion-ts [_] op-count)
|
|
35
|
+
(-affects-key [_] key)
|
|
36
|
+
(-apply-op-to-coll [_ kvs]
|
|
37
|
+
(if (exists-old? kvs key)
|
|
38
|
+
kvs
|
|
39
|
+
(assoc kvs key nil)))
|
|
40
|
+
(-apply-op-to-tree [_ tree]
|
|
41
|
+
(let [children (cond
|
|
42
|
+
(tree/data-node? tree) (:children tree)
|
|
43
|
+
:else (:children (peek (tree/lookup-path tree key))))]
|
|
44
|
+
(if (exists-old? children key)
|
|
45
|
+
tree
|
|
46
|
+
(tree/insert tree key nil)))))
|
|
47
|
+
|
|
48
|
+
(defrecord temporal-InsertOp [key op-count]
|
|
49
|
+
op/IOperation
|
|
50
|
+
(-insertion-ts [_] op-count)
|
|
51
|
+
(-affects-key [_] key)
|
|
52
|
+
(-apply-op-to-coll [_ kvs]
|
|
53
|
+
(assoc kvs key nil))
|
|
54
|
+
(-apply-op-to-tree [_ tree]
|
|
55
|
+
(tree/insert tree key nil)))
|
|
56
|
+
|
|
57
|
+
(defn new-InsertOp [key op-count]
|
|
58
|
+
(InsertOp. key op-count))
|
|
59
|
+
|
|
60
|
+
(defn new-temporal-InsertOp [key op-count]
|
|
61
|
+
(temporal-InsertOp. key op-count))
|
|
62
|
+
|
|
63
|
+
(defn add-insert-handler
|
|
64
|
+
"Tells the store how to deserialize insert related operations"
|
|
65
|
+
[store]
|
|
66
|
+
(swap! (:read-handlers store)
|
|
67
|
+
merge
|
|
68
|
+
{'datahike.index.hitchhiker_tree.insert.InsertOp
|
|
69
|
+
;; TODO Remove ts when Wanderung is available.
|
|
70
|
+
(fn [{:keys [key op-count ts]}]
|
|
71
|
+
(map->InsertOp {:key key :op-count (or op-count ts)}))
|
|
72
|
+
|
|
73
|
+
'datahike.index.hitchhiker_tree.insert.temporal_InsertOp
|
|
74
|
+
(fn [{:keys [key op-count ts]}]
|
|
75
|
+
(map->temporal-InsertOp {:key key :op-count (or op-count ts)}))})
|
|
76
|
+
store)
|