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,1945 @@
|
|
|
1
|
+
(ns ^:no-doc datahike.query
|
|
2
|
+
#?(:cljs (:require-macros [datahike.query :refer [basic-index-selector make-vec-lookup-ref-replacer some-of substitution-expansion]]))
|
|
3
|
+
(:require
|
|
4
|
+
[#?(:cljs cljs.reader :clj clojure.edn) :as edn]
|
|
5
|
+
[clojure.set :as set]
|
|
6
|
+
#?(:clj [clojure.string :as str])
|
|
7
|
+
[clojure.walk :as walk]
|
|
8
|
+
[datahike.datom :as datom]
|
|
9
|
+
[datahike.db.interface :as dbi]
|
|
10
|
+
[datahike.db.utils :as dbu]
|
|
11
|
+
[datahike.array :refer [wrap-comparable]]
|
|
12
|
+
[datahike.impl.entity :as de]
|
|
13
|
+
[datahike.lru]
|
|
14
|
+
[datahike.pull-api :as dpa]
|
|
15
|
+
[datahike.query-stats :as dqs]
|
|
16
|
+
[datahike.tools :as dt]
|
|
17
|
+
[datalog.parser :refer [parse]]
|
|
18
|
+
[datalog.parser.impl :as dpi]
|
|
19
|
+
[datalog.parser.impl.proto :as dpip]
|
|
20
|
+
[datalog.parser.pull :as dpp]
|
|
21
|
+
#?(:cljs [datalog.parser.type :refer [Aggregate BindColl BindIgnore BindScalar BindTuple Constant
|
|
22
|
+
FindColl FindRel FindScalar FindTuple PlainSymbol Pull
|
|
23
|
+
RulesVar SrcVar Variable]])
|
|
24
|
+
[org.replikativ.persistent-sorted-set.arrays :as da]
|
|
25
|
+
[taoensso.timbre :as log])
|
|
26
|
+
(:refer-clojure :exclude [seqable?])
|
|
27
|
+
|
|
28
|
+
#?(:clj (:import [clojure.lang Reflector Seqable]
|
|
29
|
+
[datahike.datom Datom]
|
|
30
|
+
[datalog.parser.type Aggregate BindColl BindIgnore BindScalar BindTuple Constant
|
|
31
|
+
FindColl FindRel FindScalar FindTuple PlainSymbol Pull
|
|
32
|
+
RulesVar SrcVar Variable]
|
|
33
|
+
[java.lang.reflect Method]
|
|
34
|
+
[java.util Date Map HashSet HashSet])))
|
|
35
|
+
|
|
36
|
+
#?(:clj (set! *warn-on-reflection* true))
|
|
37
|
+
|
|
38
|
+
;; ----------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
(def ^:const lru-cache-size 100)
|
|
41
|
+
|
|
42
|
+
(declare -collect -resolve-clause resolve-clause raw-q)
|
|
43
|
+
|
|
44
|
+
;; Records
|
|
45
|
+
|
|
46
|
+
(defrecord Context [rels sources rules consts settings])
|
|
47
|
+
(defrecord StatContext [rels sources rules consts stats settings])
|
|
48
|
+
|
|
49
|
+
;; attrs:
|
|
50
|
+
;; {?e 0, ?v 1} or {?e2 "a", ?age "v"}
|
|
51
|
+
;; tuples:
|
|
52
|
+
;; [ #js [1 "Ivan" 5 14] ... ]
|
|
53
|
+
;; or [ (Datom. 2 "Oleg" 1 55) ... ]
|
|
54
|
+
(defrecord Relation [attrs tuples])
|
|
55
|
+
|
|
56
|
+
;; Main functions
|
|
57
|
+
|
|
58
|
+
(defn normalize-q-input
|
|
59
|
+
"Turns input to q into a map with :query and :args fields.
|
|
60
|
+
Also normalizes the query into a map representation."
|
|
61
|
+
[query-input arg-inputs]
|
|
62
|
+
(let [query (-> query-input
|
|
63
|
+
(#(or (and (map? %) (:query %)) %))
|
|
64
|
+
(#(if (string? %) (edn/read-string %) %))
|
|
65
|
+
(#(if (= 'quote (first %)) (second %) %))
|
|
66
|
+
(#(if (sequential? %) (dpi/query->map %) %)))
|
|
67
|
+
args (if (and (map? query-input) (contains? query-input :args))
|
|
68
|
+
(do (when (seq arg-inputs)
|
|
69
|
+
(log/warn (str "Query-map '" query "' already defines query input."
|
|
70
|
+
" Additional arguments to q will be ignored!")))
|
|
71
|
+
(:args query-input))
|
|
72
|
+
arg-inputs)
|
|
73
|
+
extra-ks [:offset :limit :stats? :settings]]
|
|
74
|
+
(cond-> {:query (apply dissoc query extra-ks)
|
|
75
|
+
:args args}
|
|
76
|
+
(map? query-input)
|
|
77
|
+
(merge (select-keys query-input extra-ks)))))
|
|
78
|
+
|
|
79
|
+
(defn q [query & inputs]
|
|
80
|
+
(raw-q (normalize-q-input query inputs)))
|
|
81
|
+
|
|
82
|
+
(defn query-stats [query & inputs]
|
|
83
|
+
(-> query
|
|
84
|
+
(normalize-q-input inputs)
|
|
85
|
+
(assoc :stats? true)
|
|
86
|
+
q))
|
|
87
|
+
|
|
88
|
+
;; Utilities
|
|
89
|
+
|
|
90
|
+
(defn distinct-tuples
|
|
91
|
+
"Remove duplicates just like `distinct` but with the difference that it only works on values on which `vec` can be applied and two different objects are considered equal if and only if their results after `vec` has been applied are equal. This means that two different Java arrays are considered equal if and only if their elements are equal."
|
|
92
|
+
([tuples]
|
|
93
|
+
(into [] (distinct-tuples) tuples))
|
|
94
|
+
([]
|
|
95
|
+
(let [step ((distinct) (fn [_ _] true))]
|
|
96
|
+
(filter #(step false (vec %))))))
|
|
97
|
+
|
|
98
|
+
(defn seqable?
|
|
99
|
+
#?@(:clj [^Boolean [x]]
|
|
100
|
+
:cljs [^boolean [x]])
|
|
101
|
+
(and (not (string? x))
|
|
102
|
+
#?(:cljs (or (cljs.core/seqable? x)
|
|
103
|
+
(da/array? x))
|
|
104
|
+
:clj (or (seq? x)
|
|
105
|
+
(instance? Seqable x)
|
|
106
|
+
(nil? x)
|
|
107
|
+
(instance? Iterable x)
|
|
108
|
+
(da/array? x)
|
|
109
|
+
(instance? Map x)))))
|
|
110
|
+
|
|
111
|
+
(defn intersect-keys [attrs1 attrs2]
|
|
112
|
+
(set/intersection (set (keys attrs1))
|
|
113
|
+
(set (keys attrs2))))
|
|
114
|
+
|
|
115
|
+
(defn concatv [& xs]
|
|
116
|
+
(into [] cat xs))
|
|
117
|
+
|
|
118
|
+
(defn same-keys? [a b]
|
|
119
|
+
(and (= (count a) (count b))
|
|
120
|
+
(every? #(contains? b %) (keys a))
|
|
121
|
+
(every? #(contains? b %) (keys a))))
|
|
122
|
+
|
|
123
|
+
(defn- looks-like? [pattern form]
|
|
124
|
+
(cond
|
|
125
|
+
(= '_ pattern)
|
|
126
|
+
true
|
|
127
|
+
(= '[*] pattern)
|
|
128
|
+
(sequential? form)
|
|
129
|
+
(symbol? pattern)
|
|
130
|
+
(= form pattern)
|
|
131
|
+
(sequential? pattern)
|
|
132
|
+
(if (= (last pattern) '*)
|
|
133
|
+
(and (sequential? form)
|
|
134
|
+
(every? (fn [[pattern-el form-el]] (looks-like? pattern-el form-el))
|
|
135
|
+
(map vector (butlast pattern) form)))
|
|
136
|
+
(and (sequential? form)
|
|
137
|
+
(= (count form) (count pattern))
|
|
138
|
+
(every? (fn [[pattern-el form-el]] (looks-like? pattern-el form-el))
|
|
139
|
+
(map vector pattern form))))
|
|
140
|
+
:else ;; (predicate? pattern)
|
|
141
|
+
(pattern form)))
|
|
142
|
+
|
|
143
|
+
(defn source? [sym]
|
|
144
|
+
(and (symbol? sym)
|
|
145
|
+
(= \$ (first (name sym)))))
|
|
146
|
+
|
|
147
|
+
(defn free-var? [sym]
|
|
148
|
+
(and (symbol? sym)
|
|
149
|
+
(= \? (first (name sym)))))
|
|
150
|
+
|
|
151
|
+
(defn attr? [form]
|
|
152
|
+
(or (keyword? form) (string? form)))
|
|
153
|
+
|
|
154
|
+
(defn lookup-ref? [form]
|
|
155
|
+
;; Using looks-like? here is quite inefficient.
|
|
156
|
+
(and (vector? form)
|
|
157
|
+
(= 2 (count form))
|
|
158
|
+
(attr? (first form))))
|
|
159
|
+
|
|
160
|
+
(defn entid? [x] ;; See `dbu/entid for all forms that are accepted
|
|
161
|
+
(or (attr? x)
|
|
162
|
+
(lookup-ref? x)
|
|
163
|
+
(dbu/numeric-entid? x)
|
|
164
|
+
(keyword? x)))
|
|
165
|
+
|
|
166
|
+
;; Relation algebra
|
|
167
|
+
(defn join-tuples [t1 #?(:cljs idxs1
|
|
168
|
+
:clj ^{:tag "[[Ljava.lang.Object;"} idxs1)
|
|
169
|
+
t2 #?(:cljs idxs2
|
|
170
|
+
:clj ^{:tag "[[Ljava.lang.Object;"} idxs2)]
|
|
171
|
+
(let [l1 (alength idxs1)
|
|
172
|
+
l2 (alength idxs2)
|
|
173
|
+
res (da/make-array (+ l1 l2))]
|
|
174
|
+
(dotimes [i l1]
|
|
175
|
+
(aset res i (#?(:cljs da/aget :clj get) t1 (aget idxs1 i)))) ;; FIXME aget
|
|
176
|
+
(dotimes [i l2]
|
|
177
|
+
(aset res (+ l1 i) (#?(:cljs da/aget :clj get) t2 (aget idxs2 i)))) ;; FIXME aget
|
|
178
|
+
res))
|
|
179
|
+
|
|
180
|
+
(defn sum-rel [a b]
|
|
181
|
+
(let [{attrs-a :attrs, tuples-a :tuples} a
|
|
182
|
+
{attrs-b :attrs, tuples-b :tuples} b]
|
|
183
|
+
(cond
|
|
184
|
+
(= attrs-a attrs-b)
|
|
185
|
+
(Relation. attrs-a (into (vec tuples-a) tuples-b))
|
|
186
|
+
|
|
187
|
+
(not (same-keys? attrs-a attrs-b))
|
|
188
|
+
(dt/raise "Can't sum relations with different attrs: " attrs-a " and " attrs-b
|
|
189
|
+
{:error :query/where})
|
|
190
|
+
|
|
191
|
+
(every? number? (vals attrs-a)) ;; can’t conj into BTSetIter
|
|
192
|
+
(let [idxb->idxa (vec (for [[sym idx-b] attrs-b]
|
|
193
|
+
[idx-b (attrs-a sym)]))
|
|
194
|
+
tlen (->> (vals attrs-a) (reduce max) (inc))
|
|
195
|
+
tuples' (persistent!
|
|
196
|
+
(reduce
|
|
197
|
+
(fn [acc tuple-b]
|
|
198
|
+
(let [tuple' (da/make-array tlen)]
|
|
199
|
+
(doseq [[idx-b idx-a] idxb->idxa]
|
|
200
|
+
(aset tuple' idx-a (#?(:cljs da/aget :clj get) tuple-b idx-b)))
|
|
201
|
+
(conj! acc tuple')))
|
|
202
|
+
(transient (vec tuples-a))
|
|
203
|
+
tuples-b))]
|
|
204
|
+
(Relation. attrs-a tuples'))
|
|
205
|
+
|
|
206
|
+
:else
|
|
207
|
+
(let [all-attrs (zipmap (keys (merge attrs-a attrs-b)) (range))]
|
|
208
|
+
(-> (Relation. all-attrs [])
|
|
209
|
+
(sum-rel a)
|
|
210
|
+
(sum-rel b))))))
|
|
211
|
+
|
|
212
|
+
(defn simplify-rel [rel]
|
|
213
|
+
(Relation. (:attrs rel) (distinct-tuples (:tuples rel))))
|
|
214
|
+
|
|
215
|
+
(defn prod-rel
|
|
216
|
+
([] (Relation. {} [(da/make-array 0)]))
|
|
217
|
+
([rel1 rel2]
|
|
218
|
+
(let [attrs1 (keys (:attrs rel1))
|
|
219
|
+
attrs2 (keys (:attrs rel2))
|
|
220
|
+
idxs1 (to-array (map (:attrs rel1) attrs1))
|
|
221
|
+
idxs2 (to-array (map (:attrs rel2) attrs2))]
|
|
222
|
+
(Relation.
|
|
223
|
+
(zipmap (concat attrs1 attrs2) (range))
|
|
224
|
+
(persistent!
|
|
225
|
+
(reduce
|
|
226
|
+
(fn [acc t1]
|
|
227
|
+
(reduce (fn [acc t2]
|
|
228
|
+
(conj! acc (join-tuples t1 idxs1 t2 idxs2)))
|
|
229
|
+
acc (:tuples rel2)))
|
|
230
|
+
(transient []) (:tuples rel1)))))))
|
|
231
|
+
|
|
232
|
+
;; built-ins
|
|
233
|
+
|
|
234
|
+
(defn- translate-for [db a]
|
|
235
|
+
(if (and (-> db dbi/-config :attribute-refs?) (keyword? a))
|
|
236
|
+
(dbi/-ref-for db a)
|
|
237
|
+
a))
|
|
238
|
+
|
|
239
|
+
(defn- -differ? [& xs]
|
|
240
|
+
(let [l (count xs)]
|
|
241
|
+
(not= (take (/ l 2) xs) (drop (/ l 2) xs))))
|
|
242
|
+
|
|
243
|
+
(defn- -get-else
|
|
244
|
+
[db e a else-val]
|
|
245
|
+
(when (nil? else-val)
|
|
246
|
+
(dt/raise "get-else: nil default value is not supported" {:error :query/where}))
|
|
247
|
+
(if-some [datom (first (dbi/search db [e (translate-for db a)]))]
|
|
248
|
+
(:v datom)
|
|
249
|
+
else-val))
|
|
250
|
+
|
|
251
|
+
(defn- -get-some
|
|
252
|
+
[db e & as]
|
|
253
|
+
(reduce
|
|
254
|
+
(fn [_ a]
|
|
255
|
+
(when-some [datom (first (dbi/search db [e (translate-for db a)]))]
|
|
256
|
+
(let [a-ident (if (keyword? (:a datom))
|
|
257
|
+
(:a datom)
|
|
258
|
+
(dbi/-ident-for db (:a datom)))]
|
|
259
|
+
(reduced [a-ident (:v datom)]))))
|
|
260
|
+
nil
|
|
261
|
+
as))
|
|
262
|
+
|
|
263
|
+
(defn- -missing?
|
|
264
|
+
[db e a]
|
|
265
|
+
(nil? (get (de/entity db e) a)))
|
|
266
|
+
|
|
267
|
+
(defn- and-fn [& args]
|
|
268
|
+
(reduce (fn [_a b]
|
|
269
|
+
(if b b (reduced b))) true args))
|
|
270
|
+
|
|
271
|
+
(defn- or-fn [& args]
|
|
272
|
+
(reduce (fn [_a b]
|
|
273
|
+
(if b (reduced b) b)) nil args))
|
|
274
|
+
|
|
275
|
+
(defprotocol CollectionOrder
|
|
276
|
+
(-strictly-decreasing? [x more])
|
|
277
|
+
(-decreasing? [x more])
|
|
278
|
+
(-strictly-increasing? [x more])
|
|
279
|
+
(-increasing? [x more]))
|
|
280
|
+
|
|
281
|
+
(extend-protocol CollectionOrder
|
|
282
|
+
|
|
283
|
+
#?(:clj Number :cljs number)
|
|
284
|
+
(-strictly-decreasing? [x more] (apply < x more))
|
|
285
|
+
(-decreasing? [x more] (apply <= x more))
|
|
286
|
+
(-strictly-increasing? [x more] (apply > x more))
|
|
287
|
+
(-increasing? [x more] (apply >= x more))
|
|
288
|
+
|
|
289
|
+
#?(:clj Date :cljs js/Date)
|
|
290
|
+
(-strictly-decreasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.before ^Date d1 ^Date d2)
|
|
291
|
+
res
|
|
292
|
+
(reduced false)))
|
|
293
|
+
true (map vector (cons x more) more))
|
|
294
|
+
:cljs (apply < x more)))
|
|
295
|
+
(-decreasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.after ^Date d1 ^Date d2)
|
|
296
|
+
(reduced false)
|
|
297
|
+
res))
|
|
298
|
+
true (map vector (cons x more) more))
|
|
299
|
+
:cljs (apply <= x more)))
|
|
300
|
+
(-strictly-increasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.after ^Date d1 ^Date d2)
|
|
301
|
+
res
|
|
302
|
+
(reduced false)))
|
|
303
|
+
true (map vector (cons x more) more))
|
|
304
|
+
:cljs (apply > x more)))
|
|
305
|
+
(-increasing? [x more] #?(:clj (reduce (fn [res [d1 d2]] (if (.before ^Date d1 ^Date d2)
|
|
306
|
+
(reduced false)
|
|
307
|
+
res))
|
|
308
|
+
true (map vector (cons x more) more))
|
|
309
|
+
:cljs (apply >= x more)))
|
|
310
|
+
|
|
311
|
+
#?(:clj Object :cljs object) ;; default
|
|
312
|
+
(-strictly-decreasing? [x more] (reduce (fn [res [v1 s2]] (if (neg? (compare v1 s2))
|
|
313
|
+
res
|
|
314
|
+
(reduced false)))
|
|
315
|
+
true (map vector (cons x more) more)))
|
|
316
|
+
|
|
317
|
+
(-decreasing? [x more] (reduce (fn [res [v1 v2]] (if (pos? (compare v1 v2))
|
|
318
|
+
(reduced false)
|
|
319
|
+
res))
|
|
320
|
+
true (map vector (cons x more) more)))
|
|
321
|
+
|
|
322
|
+
(-strictly-increasing? [x more] (reduce (fn [res [v1 v2]] (if (pos? (compare v1 v2))
|
|
323
|
+
res
|
|
324
|
+
(reduced false)))
|
|
325
|
+
true (map vector (cons x more) more)))
|
|
326
|
+
|
|
327
|
+
(-increasing? [x more] (reduce (fn [res [v1 v2]] (if (neg? (compare v1 v2))
|
|
328
|
+
(reduced false)
|
|
329
|
+
res))
|
|
330
|
+
true (map vector (cons x more) more))))
|
|
331
|
+
|
|
332
|
+
(defn- lesser? [& args]
|
|
333
|
+
(-strictly-decreasing? (first args) (rest args)))
|
|
334
|
+
|
|
335
|
+
(defn- lesser-equal? [& args]
|
|
336
|
+
(-decreasing? (first args) (rest args)))
|
|
337
|
+
|
|
338
|
+
(defn- greater? [& args]
|
|
339
|
+
(-strictly-increasing? (first args) (rest args)))
|
|
340
|
+
|
|
341
|
+
(defn- greater-equal? [& args]
|
|
342
|
+
(-increasing? (first args) (rest args)))
|
|
343
|
+
|
|
344
|
+
(defn -min
|
|
345
|
+
([coll] (reduce (fn [acc x]
|
|
346
|
+
(if (neg? (compare x acc))
|
|
347
|
+
x acc))
|
|
348
|
+
(first coll) (next coll)))
|
|
349
|
+
([n coll]
|
|
350
|
+
(vec
|
|
351
|
+
(reduce (fn [acc x]
|
|
352
|
+
(cond
|
|
353
|
+
(< (count acc) n)
|
|
354
|
+
(sort compare (conj acc x))
|
|
355
|
+
(neg? (compare x (last acc)))
|
|
356
|
+
(sort compare (conj (butlast acc) x))
|
|
357
|
+
:else acc))
|
|
358
|
+
[] coll))))
|
|
359
|
+
|
|
360
|
+
(defn -max
|
|
361
|
+
([coll] (reduce (fn [acc x]
|
|
362
|
+
(if (pos? (compare x acc))
|
|
363
|
+
x acc))
|
|
364
|
+
(first coll) (next coll)))
|
|
365
|
+
([n coll]
|
|
366
|
+
(vec
|
|
367
|
+
(reduce (fn [acc x]
|
|
368
|
+
(cond
|
|
369
|
+
(< (count acc) n)
|
|
370
|
+
(sort compare (conj acc x))
|
|
371
|
+
(pos? (compare x (first acc)))
|
|
372
|
+
(sort compare (conj (next acc) x))
|
|
373
|
+
:else acc))
|
|
374
|
+
[] coll))))
|
|
375
|
+
|
|
376
|
+
(def built-ins {'= =, '== ==, 'not= not=, '!= not=, '< lesser?, '> greater?, '<= lesser-equal?, '>= greater-equal?, '+ +, '- -
|
|
377
|
+
'* *, '/ /, 'quot quot, 'rem rem, 'mod mod, 'inc inc, 'dec dec, 'max -max, 'min -min
|
|
378
|
+
'zero? zero?, 'pos? pos?, 'neg? neg?, 'even? even?, 'odd? odd?, 'compare compare
|
|
379
|
+
'rand rand, 'rand-int rand-int
|
|
380
|
+
'true? true?, 'false? false?, 'nil? nil?, 'some? some?, 'not not, 'and and-fn, 'or or-fn
|
|
381
|
+
'complement complement, 'identical? identical?
|
|
382
|
+
'identity identity, 'meta meta, 'name name, 'namespace namespace, 'type type
|
|
383
|
+
'vector vector, 'list list, 'set set, 'hash-map hash-map, 'array-map array-map
|
|
384
|
+
'count count, 'range range, 'not-empty not-empty, 'empty? empty, 'contains? contains?
|
|
385
|
+
'str str, 'pr-str pr-str, 'print-str print-str, 'println-str println-str, 'prn-str prn-str, 'subs subs
|
|
386
|
+
're-find re-find, 're-matches re-matches, 're-seq re-seq
|
|
387
|
+
'-differ? -differ?, 'get-else -get-else, 'get-some -get-some, 'missing? -missing?, 'ground identity, 'before? lesser?, 'after? greater?
|
|
388
|
+
'tuple vector, 'untuple identity
|
|
389
|
+
'q q
|
|
390
|
+
'datahike.query/q q
|
|
391
|
+
#'datahike.query/q q})
|
|
392
|
+
|
|
393
|
+
(def clj-core-built-ins
|
|
394
|
+
#?(:clj
|
|
395
|
+
(dissoc (ns-publics 'clojure.core)
|
|
396
|
+
'eval)
|
|
397
|
+
:cljs {}))
|
|
398
|
+
|
|
399
|
+
(def built-in-aggregates
|
|
400
|
+
(letfn [(sum [coll] (reduce + 0 coll))
|
|
401
|
+
(avg [coll] (/ (sum coll) (count coll)))
|
|
402
|
+
(median
|
|
403
|
+
[coll]
|
|
404
|
+
(let [terms (sort coll)
|
|
405
|
+
size (count coll)
|
|
406
|
+
med (bit-shift-right size 1)]
|
|
407
|
+
(cond-> (nth terms med)
|
|
408
|
+
(even? size)
|
|
409
|
+
(-> (+ (nth terms (dec med)))
|
|
410
|
+
(/ 2)))))
|
|
411
|
+
(variance
|
|
412
|
+
[coll]
|
|
413
|
+
(let [mean (avg coll)
|
|
414
|
+
sum (sum (for [x coll
|
|
415
|
+
:let [delta (- x mean)]]
|
|
416
|
+
(* delta delta)))]
|
|
417
|
+
(/ sum (count coll))))
|
|
418
|
+
(stddev
|
|
419
|
+
[coll]
|
|
420
|
+
(#?(:cljs js/Math.sqrt :clj Math/sqrt) (variance coll)))]
|
|
421
|
+
{'avg avg
|
|
422
|
+
'median median
|
|
423
|
+
'variance variance
|
|
424
|
+
'stddev stddev
|
|
425
|
+
'distinct set
|
|
426
|
+
'min -min
|
|
427
|
+
'max -max
|
|
428
|
+
'sum sum
|
|
429
|
+
'rand (fn
|
|
430
|
+
([coll] (rand-nth coll))
|
|
431
|
+
([n coll] (vec (repeatedly n #(rand-nth coll)))))
|
|
432
|
+
'sample (fn [n coll]
|
|
433
|
+
(vec (take n (shuffle coll))))
|
|
434
|
+
'count count
|
|
435
|
+
'count-distinct (fn [coll] (count (distinct coll)))}))
|
|
436
|
+
|
|
437
|
+
(defn parse-rules [rules]
|
|
438
|
+
(let [rules (if (string? rules) (edn/read-string rules) rules)] ;; for datahike.js interop
|
|
439
|
+
(group-by ffirst rules)))
|
|
440
|
+
|
|
441
|
+
(defn empty-rel [binding]
|
|
442
|
+
(let [vars (->> (dpi/collect-vars-distinct binding)
|
|
443
|
+
(map :symbol))]
|
|
444
|
+
(Relation. (zipmap vars (range)) [])))
|
|
445
|
+
|
|
446
|
+
(defprotocol IBinding
|
|
447
|
+
(in->rel [binding value]))
|
|
448
|
+
|
|
449
|
+
(extend-protocol IBinding
|
|
450
|
+
BindIgnore
|
|
451
|
+
(in->rel [_ _]
|
|
452
|
+
(prod-rel))
|
|
453
|
+
|
|
454
|
+
BindScalar
|
|
455
|
+
(in->rel [binding value]
|
|
456
|
+
(Relation. {(get-in binding [:variable :symbol]) 0} [(into-array [value])]))
|
|
457
|
+
|
|
458
|
+
BindColl
|
|
459
|
+
(in->rel [binding coll]
|
|
460
|
+
(cond
|
|
461
|
+
(not (seqable? coll))
|
|
462
|
+
(dt/raise "Cannot bind value " coll " to collection " (dpi/get-source binding)
|
|
463
|
+
{:error :query/binding, :value coll, :binding (dpi/get-source binding)})
|
|
464
|
+
(empty? coll)
|
|
465
|
+
(empty-rel binding)
|
|
466
|
+
:else
|
|
467
|
+
(->> coll
|
|
468
|
+
(map #(in->rel (:binding binding) %))
|
|
469
|
+
(reduce sum-rel))))
|
|
470
|
+
|
|
471
|
+
BindTuple
|
|
472
|
+
(in->rel [binding coll]
|
|
473
|
+
(cond
|
|
474
|
+
(not (seqable? coll))
|
|
475
|
+
(dt/raise "Cannot bind value " coll " to tuple " (dpi/get-source binding)
|
|
476
|
+
{:error :query/binding, :value coll, :binding (dpi/get-source binding)})
|
|
477
|
+
(< (count coll) (count (:bindings binding)))
|
|
478
|
+
(dt/raise "Not enough elements in a collection " coll " to bind tuple " (dpi/get-source binding)
|
|
479
|
+
{:error :query/binding, :value coll, :binding (dpi/get-source binding)})
|
|
480
|
+
:else
|
|
481
|
+
(reduce prod-rel
|
|
482
|
+
(map #(in->rel %1 %2) (:bindings binding) coll)))))
|
|
483
|
+
|
|
484
|
+
(defn resolve-in [context [binding value]]
|
|
485
|
+
(cond
|
|
486
|
+
(and (instance? BindScalar binding)
|
|
487
|
+
(instance? SrcVar (:variable binding)))
|
|
488
|
+
(update context :sources assoc (get-in binding [:variable :symbol]) value)
|
|
489
|
+
(and (instance? BindScalar binding)
|
|
490
|
+
(instance? RulesVar (:variable binding)))
|
|
491
|
+
(assoc context :rules (parse-rules value))
|
|
492
|
+
(and (instance? BindScalar binding)
|
|
493
|
+
(instance? Variable (:variable binding)))
|
|
494
|
+
(assoc-in context [:consts (get-in binding [:variable :symbol])] value)
|
|
495
|
+
#_(instance? BindColl binding) ;; TODO: later
|
|
496
|
+
:else
|
|
497
|
+
(update context :rels conj (in->rel binding value))))
|
|
498
|
+
|
|
499
|
+
(defn resolve-ins [context bindings values]
|
|
500
|
+
(reduce resolve-in context (zipmap bindings values)))
|
|
501
|
+
|
|
502
|
+
(def ^{:dynamic true
|
|
503
|
+
:doc "List of symbols in current pattern that might potentially be resolved to refs"}
|
|
504
|
+
*lookup-attrs* nil)
|
|
505
|
+
|
|
506
|
+
(def ^{:dynamic true
|
|
507
|
+
:doc "Default pattern source. Lookup refs, patterns, rules will be resolved with it"}
|
|
508
|
+
*implicit-source* nil)
|
|
509
|
+
|
|
510
|
+
(defn getter-fn [attrs attr]
|
|
511
|
+
(let [idx (attrs attr)]
|
|
512
|
+
(if (contains? *lookup-attrs* attr)
|
|
513
|
+
(fn [tuple]
|
|
514
|
+
(let [eid (#?(:cljs da/aget :clj get) tuple idx)]
|
|
515
|
+
(cond
|
|
516
|
+
(number? eid) eid ;; quick path to avoid fn call
|
|
517
|
+
(sequential? eid) (dbu/entid *implicit-source* eid)
|
|
518
|
+
(da/array? eid) (dbu/entid *implicit-source* eid)
|
|
519
|
+
:else eid)))
|
|
520
|
+
(fn [tuple]
|
|
521
|
+
(#?(:cljs da/aget :clj get) tuple idx)))))
|
|
522
|
+
|
|
523
|
+
(defn tuple-key-fn [getters]
|
|
524
|
+
(if (== (count getters) 1)
|
|
525
|
+
(first getters)
|
|
526
|
+
(let [getters (to-array getters)]
|
|
527
|
+
(fn [tuple]
|
|
528
|
+
(list* #?(:cljs (.map getters #(% tuple))
|
|
529
|
+
:clj (to-array (map #(% tuple) getters))))))))
|
|
530
|
+
|
|
531
|
+
(defn hash-attrs [key-fn tuples]
|
|
532
|
+
;; Equivalent to group-by except that it uses a list instead of a vector.
|
|
533
|
+
(loop [tuples tuples
|
|
534
|
+
hash-table (transient {})]
|
|
535
|
+
(if-some [tuple (first tuples)]
|
|
536
|
+
(let [key (key-fn tuple)]
|
|
537
|
+
(recur (next tuples)
|
|
538
|
+
(assoc! hash-table key (conj (get hash-table key '()) tuple))))
|
|
539
|
+
(persistent! hash-table))))
|
|
540
|
+
|
|
541
|
+
(defn hash-join [rel1 rel2]
|
|
542
|
+
(let [tuples1 (:tuples rel1)
|
|
543
|
+
tuples2 (:tuples rel2)
|
|
544
|
+
attrs1 (:attrs rel1)
|
|
545
|
+
attrs2 (:attrs rel2)
|
|
546
|
+
common-attrs (vec (intersect-keys (:attrs rel1) (:attrs rel2)))
|
|
547
|
+
common-gtrs1 (map #(getter-fn attrs1 %) common-attrs)
|
|
548
|
+
common-gtrs2 (map #(getter-fn attrs2 %) common-attrs)
|
|
549
|
+
keep-attrs1 (keys attrs1)
|
|
550
|
+
keep-attrs2 (vec (set/difference (set (keys attrs2)) (set (keys attrs1))))
|
|
551
|
+
keep-idxs1 (to-array (map attrs1 keep-attrs1))
|
|
552
|
+
keep-idxs2 (to-array (map attrs2 keep-attrs2))
|
|
553
|
+
key-fn1 (tuple-key-fn common-gtrs1)
|
|
554
|
+
key-fn2 (tuple-key-fn common-gtrs2)]
|
|
555
|
+
(if (< (count tuples1) (count tuples2))
|
|
556
|
+
(let [hash (hash-attrs key-fn1 tuples1)
|
|
557
|
+
new-tuples (->>
|
|
558
|
+
(reduce (fn [acc tuple2]
|
|
559
|
+
(let [key (key-fn2 tuple2)]
|
|
560
|
+
(if-some [tuples1 (get hash key)]
|
|
561
|
+
(reduce (fn [acc tuple1]
|
|
562
|
+
(conj! acc (join-tuples tuple1 keep-idxs1 tuple2 keep-idxs2)))
|
|
563
|
+
acc tuples1)
|
|
564
|
+
acc)))
|
|
565
|
+
(transient []) tuples2)
|
|
566
|
+
(persistent!))]
|
|
567
|
+
(Relation. (zipmap (concat keep-attrs1 keep-attrs2) (range))
|
|
568
|
+
new-tuples))
|
|
569
|
+
(let [hash (hash-attrs key-fn2 tuples2)
|
|
570
|
+
new-tuples (->>
|
|
571
|
+
(reduce (fn [acc tuple1]
|
|
572
|
+
(let [key (key-fn1 tuple1)]
|
|
573
|
+
(if-some [tuples2 (get hash key)]
|
|
574
|
+
(reduce (fn [acc tuple2]
|
|
575
|
+
(conj! acc (join-tuples tuple1 keep-idxs1 tuple2 keep-idxs2)))
|
|
576
|
+
acc tuples2)
|
|
577
|
+
acc)))
|
|
578
|
+
(transient []) tuples1)
|
|
579
|
+
(persistent!))]
|
|
580
|
+
(Relation. (zipmap (concat keep-attrs1 keep-attrs2) (range))
|
|
581
|
+
new-tuples)))))
|
|
582
|
+
|
|
583
|
+
(defn subtract-rel [a b]
|
|
584
|
+
(let [{attrs-a :attrs, tuples-a :tuples} a
|
|
585
|
+
{attrs-b :attrs, tuples-b :tuples} b
|
|
586
|
+
attrs (intersect-keys attrs-a attrs-b)
|
|
587
|
+
getters-b (map #(getter-fn attrs-b %) attrs)
|
|
588
|
+
key-fn-b (tuple-key-fn getters-b)
|
|
589
|
+
hash (hash-attrs key-fn-b tuples-b)
|
|
590
|
+
getters-a (map #(getter-fn attrs-a %) attrs)
|
|
591
|
+
key-fn-a (tuple-key-fn getters-a)]
|
|
592
|
+
(assoc a
|
|
593
|
+
:tuples (filterv #(nil? (hash (key-fn-a %))) tuples-a))))
|
|
594
|
+
|
|
595
|
+
(defn var-mapping [pattern indices]
|
|
596
|
+
(->> (map vector pattern indices)
|
|
597
|
+
(filter (fn [[s _]] (free-var? s)))
|
|
598
|
+
(into {})))
|
|
599
|
+
|
|
600
|
+
(defn map-consts [context orig-pattern datoms]
|
|
601
|
+
(let [;; Create a map from free var to index
|
|
602
|
+
;; for the positions in the pattern
|
|
603
|
+
attr->idx (var-mapping orig-pattern (range))
|
|
604
|
+
idx->const (reduce-kv (fn [m k v]
|
|
605
|
+
(if-let [c (k (:consts context))]
|
|
606
|
+
(if (= c (get (first datoms) v)) ;; All datoms have the same format and the same value at position v
|
|
607
|
+
m ;; -> avoid unnecessary translations
|
|
608
|
+
(assoc m v c))
|
|
609
|
+
m))
|
|
610
|
+
{}
|
|
611
|
+
attr->idx)]
|
|
612
|
+
(when (seq idx->const)
|
|
613
|
+
(Relation. attr->idx (map #(reduce (fn [datom [k v]]
|
|
614
|
+
(assoc datom k v))
|
|
615
|
+
(vec (seq %))
|
|
616
|
+
idx->const)
|
|
617
|
+
datoms)))))
|
|
618
|
+
|
|
619
|
+
(defn replace-symbols-by-nil [pattern]
|
|
620
|
+
(mapv #(if (symbol? %) nil %) pattern))
|
|
621
|
+
|
|
622
|
+
(defn resolve-pattern-eid [db search-pattern]
|
|
623
|
+
(let [first-p (first search-pattern)]
|
|
624
|
+
(if (and (some? first-p)
|
|
625
|
+
(not (symbol? first-p)))
|
|
626
|
+
(when-let [eid (dbu/entid db first-p)]
|
|
627
|
+
(assoc search-pattern 0 eid))
|
|
628
|
+
search-pattern)))
|
|
629
|
+
|
|
630
|
+
(defn relation-from-datoms-xform []
|
|
631
|
+
(comp (map #?(:cljs (fn [datom]
|
|
632
|
+
(if (and (some? datom)
|
|
633
|
+
(or (instance? datahike.datom/Datom datom)
|
|
634
|
+
(.-e datom)))
|
|
635
|
+
(let [e (.-e ^datahike.datom.Datom datom)
|
|
636
|
+
a (.-a ^datahike.datom.Datom datom)
|
|
637
|
+
a-kw (keyword (.-fqn ^clj a)) ; Extract from Keyword object
|
|
638
|
+
v (.-v ^datahike.datom.Datom datom)
|
|
639
|
+
tx (.-tx ^datahike.datom.Datom datom)]
|
|
640
|
+
#js [e a-kw v tx (pos? tx)]) ; JS array for goog.array/get
|
|
641
|
+
datom))
|
|
642
|
+
:clj (fn [[e a v tx added?]]
|
|
643
|
+
[e a v tx added?])))
|
|
644
|
+
(distinct-tuples)))
|
|
645
|
+
|
|
646
|
+
(defn relation-from-datoms [context orig-pattern datoms]
|
|
647
|
+
(or (map-consts context orig-pattern datoms)
|
|
648
|
+
#?(:cljs (let [converted (mapv (fn [d]
|
|
649
|
+
(if (and (some? d)
|
|
650
|
+
(or (instance? datahike.datom/Datom d)
|
|
651
|
+
(.-e d)))
|
|
652
|
+
(let [a (.-a ^datahike.datom.Datom d)
|
|
653
|
+
a-kw (keyword (.-fqn ^clj a))]
|
|
654
|
+
#js [(.-e ^datahike.datom.Datom d)
|
|
655
|
+
a-kw
|
|
656
|
+
(.-v ^datahike.datom.Datom d)
|
|
657
|
+
(.-tx ^datahike.datom.Datom d)
|
|
658
|
+
(pos? (.-tx ^datahike.datom.Datom d))])
|
|
659
|
+
d))
|
|
660
|
+
datoms)]
|
|
661
|
+
(Relation. (var-mapping orig-pattern (range))
|
|
662
|
+
converted))
|
|
663
|
+
:clj (Relation. (var-mapping orig-pattern (range))
|
|
664
|
+
datoms))))
|
|
665
|
+
|
|
666
|
+
(defn matches-pattern? [pattern tuple]
|
|
667
|
+
(loop [tuple tuple
|
|
668
|
+
pattern pattern]
|
|
669
|
+
(if (and tuple pattern)
|
|
670
|
+
(let [t (first tuple)
|
|
671
|
+
p (first pattern)]
|
|
672
|
+
(if (or (symbol? p) (= t p))
|
|
673
|
+
(recur (next tuple) (next pattern))
|
|
674
|
+
false))
|
|
675
|
+
true)))
|
|
676
|
+
|
|
677
|
+
(defn lookup-pattern-coll [coll pattern orig-pattern]
|
|
678
|
+
(let [attr->idx (var-mapping orig-pattern (range))
|
|
679
|
+
data (filter #(matches-pattern? pattern %) coll)]
|
|
680
|
+
(Relation. attr->idx (mapv to-array data)))) ;; FIXME to-array
|
|
681
|
+
|
|
682
|
+
(defn collapse-rels [rels new-rel]
|
|
683
|
+
(loop [rels rels
|
|
684
|
+
new-rel new-rel
|
|
685
|
+
acc []]
|
|
686
|
+
(if-some [rel (first rels)]
|
|
687
|
+
(if (not-empty (intersect-keys (:attrs new-rel) (:attrs rel)))
|
|
688
|
+
(recur (next rels) (hash-join rel new-rel) acc)
|
|
689
|
+
(recur (next rels) new-rel (conj acc rel)))
|
|
690
|
+
(conj acc new-rel))))
|
|
691
|
+
|
|
692
|
+
(defn- rel-with-attr [context sym]
|
|
693
|
+
(some #(when (contains? (:attrs %) sym) %) (:rels context)))
|
|
694
|
+
|
|
695
|
+
(defn- context-resolve-val [context sym]
|
|
696
|
+
(if-let [replacement (get (:consts context) sym)]
|
|
697
|
+
replacement
|
|
698
|
+
(when-some [rel (rel-with-attr context sym)]
|
|
699
|
+
(when-some [tuple (first (:tuples rel))]
|
|
700
|
+
(#?(:cljs da/aget :clj get) tuple ((:attrs rel) sym))))))
|
|
701
|
+
|
|
702
|
+
(defn- rel-contains-attrs? [rel attrs]
|
|
703
|
+
(some #(contains? (:attrs rel) %) attrs))
|
|
704
|
+
|
|
705
|
+
(defn- rel-prod-by-attrs [context attrs]
|
|
706
|
+
(let [rels (filter #(rel-contains-attrs? % attrs) (:rels context))
|
|
707
|
+
production (reduce prod-rel rels)]
|
|
708
|
+
[(update context :rels #(remove (set rels) %)) production]))
|
|
709
|
+
|
|
710
|
+
(defn -call-fn [context rel f args]
|
|
711
|
+
(let [sources (:sources context)
|
|
712
|
+
attrs (:attrs rel)
|
|
713
|
+
len (count args)
|
|
714
|
+
static-args (da/make-array len)
|
|
715
|
+
tuples-args (da/make-array len)]
|
|
716
|
+
(dotimes [i len]
|
|
717
|
+
(let [arg (nth args i)]
|
|
718
|
+
(if (symbol? arg)
|
|
719
|
+
(if-let [const (get (:consts context) arg)]
|
|
720
|
+
(da/aset static-args i const)
|
|
721
|
+
(if-some [source (get sources arg)]
|
|
722
|
+
(da/aset static-args i source)
|
|
723
|
+
(da/aset tuples-args i (get attrs arg))))
|
|
724
|
+
(da/aset static-args i arg))))
|
|
725
|
+
(fn [tuple]
|
|
726
|
+
;; TODO raise if not all args are bound
|
|
727
|
+
(dotimes [i len]
|
|
728
|
+
(when-some [tuple-idx (aget tuples-args i)]
|
|
729
|
+
(let [v (#?(:cljs da/aget :clj get) tuple tuple-idx)]
|
|
730
|
+
(da/aset static-args i v))))
|
|
731
|
+
(apply f static-args))))
|
|
732
|
+
|
|
733
|
+
(defn- resolve-sym [#?(:clj sym :cljs _)]
|
|
734
|
+
#?(:cljs nil
|
|
735
|
+
:clj (when (namespace sym)
|
|
736
|
+
(when-some [v (resolve sym)] @v))))
|
|
737
|
+
|
|
738
|
+
#?(:clj (def ^:private find-method
|
|
739
|
+
(memoize
|
|
740
|
+
(fn find-method-impl [^Class this-class method-name args-classes]
|
|
741
|
+
(or (->> this-class
|
|
742
|
+
.getMethods
|
|
743
|
+
(some (fn [^Method method]
|
|
744
|
+
(when (and (= method-name (.getName method))
|
|
745
|
+
(= (count args-classes)
|
|
746
|
+
(.getParameterCount method))
|
|
747
|
+
(every? true? (map #(Reflector/paramArgTypeMatch %1 %2)
|
|
748
|
+
(.getParameterTypes method)
|
|
749
|
+
args-classes)))
|
|
750
|
+
method))))
|
|
751
|
+
(throw (ex-info (str (.getName this-class) "."
|
|
752
|
+
method-name "("
|
|
753
|
+
(str/join "," (map #(.getName ^Class %) args-classes))
|
|
754
|
+
") not found")
|
|
755
|
+
{:this-class this-class
|
|
756
|
+
:method-name method-name
|
|
757
|
+
:args-classes args-classes})))))))
|
|
758
|
+
|
|
759
|
+
(defn- resolve-method [#?(:clj method-sym :cljs _)]
|
|
760
|
+
#?(:cljs nil
|
|
761
|
+
:clj (let [method-str (name method-sym)]
|
|
762
|
+
(when (= \. (.charAt method-str 0))
|
|
763
|
+
(let [method-name (subs method-str 1)]
|
|
764
|
+
(fn [this & args]
|
|
765
|
+
(let [^Method method (find-method (class this) method-name (mapv class args))]
|
|
766
|
+
(Reflector/prepRet (.getReturnType method) (.invoke method this (into-array Object args))))))))))
|
|
767
|
+
|
|
768
|
+
(defn filter-by-pred [context clause]
|
|
769
|
+
(let [[[f & args]] clause
|
|
770
|
+
pred (or (get built-ins f)
|
|
771
|
+
(get clj-core-built-ins f)
|
|
772
|
+
(context-resolve-val context f)
|
|
773
|
+
(resolve-sym f)
|
|
774
|
+
(resolve-method f)
|
|
775
|
+
(when (nil? (rel-with-attr context f))
|
|
776
|
+
(dt/raise "Unknown predicate '" f " in " clause
|
|
777
|
+
{:error :query/where, :form clause, :var f})))
|
|
778
|
+
[context production] (rel-prod-by-attrs context (filter symbol? args))
|
|
779
|
+
new-rel (if pred
|
|
780
|
+
(let [tuple-pred (-call-fn context production pred args)]
|
|
781
|
+
(update production :tuples #(filter tuple-pred %)))
|
|
782
|
+
(assoc production :tuples []))]
|
|
783
|
+
(update context :rels conj new-rel)))
|
|
784
|
+
|
|
785
|
+
(defn bind-by-fn [context clause]
|
|
786
|
+
(let [[[f & args] out] clause
|
|
787
|
+
binding (dpi/parse-binding out)
|
|
788
|
+
fun (or (get built-ins f)
|
|
789
|
+
(get clj-core-built-ins f)
|
|
790
|
+
(context-resolve-val context f)
|
|
791
|
+
(resolve-sym f)
|
|
792
|
+
(resolve-method f)
|
|
793
|
+
(when (nil? (rel-with-attr context f))
|
|
794
|
+
(dt/raise "Unknown function '" f " in " clause
|
|
795
|
+
{:error :query/where, :form clause, :var f})))
|
|
796
|
+
attrs (filter symbol? args)
|
|
797
|
+
[context production] (rel-prod-by-attrs context attrs)
|
|
798
|
+
symbols-with-values (into #{}
|
|
799
|
+
(mapcat keys)
|
|
800
|
+
[(:attrs production)
|
|
801
|
+
(:consts context)
|
|
802
|
+
(:sources context)])]
|
|
803
|
+
;; Currently, we can only evaluate this clause if all variables
|
|
804
|
+
;; in the function call are bound. If not, we return nil which
|
|
805
|
+
;; is handled by `datahike.tools/resolve-clauses`.
|
|
806
|
+
(when (every? symbols-with-values attrs)
|
|
807
|
+
(let [new-rel (if fun
|
|
808
|
+
(let [tuple-fn (-call-fn context production fun args)
|
|
809
|
+
rels (for [tuple (:tuples production)
|
|
810
|
+
:let [val (tuple-fn tuple)]
|
|
811
|
+
:when (not (nil? val))]
|
|
812
|
+
(prod-rel (Relation. (:attrs production) [tuple])
|
|
813
|
+
(in->rel binding val)))]
|
|
814
|
+
(if (empty? rels)
|
|
815
|
+
(prod-rel production (empty-rel binding))
|
|
816
|
+
(reduce sum-rel rels)))
|
|
817
|
+
(prod-rel (assoc production :tuples []) (empty-rel binding)))
|
|
818
|
+
idx->const (reduce-kv (fn [m k v]
|
|
819
|
+
(if-let [c (k (:consts context))]
|
|
820
|
+
(assoc m v c) ;; different value at v for each tuple
|
|
821
|
+
m))
|
|
822
|
+
{}
|
|
823
|
+
(:attrs new-rel))]
|
|
824
|
+
(if (empty? (:tuples new-rel))
|
|
825
|
+
(update context :rels collapse-rels new-rel)
|
|
826
|
+
(-> context ;; filter output binding
|
|
827
|
+
(update :rels collapse-rels
|
|
828
|
+
(update new-rel
|
|
829
|
+
:tuples
|
|
830
|
+
#(filter (fn [tuple]
|
|
831
|
+
(every? (fn [[ind c]]
|
|
832
|
+
(= c (get tuple ind)))
|
|
833
|
+
idx->const))
|
|
834
|
+
%)))))))))
|
|
835
|
+
|
|
836
|
+
;;; RULES
|
|
837
|
+
|
|
838
|
+
(defn rule? [context clause]
|
|
839
|
+
(and (sequential? clause)
|
|
840
|
+
(contains? (:rules context)
|
|
841
|
+
(if (source? (first clause))
|
|
842
|
+
(second clause)
|
|
843
|
+
(first clause)))))
|
|
844
|
+
|
|
845
|
+
(def rule-seqid (atom 0))
|
|
846
|
+
|
|
847
|
+
#?(:clj
|
|
848
|
+
(defmacro some-of
|
|
849
|
+
([] nil)
|
|
850
|
+
([x] x)
|
|
851
|
+
([x & more]
|
|
852
|
+
`(let [x# ~x] (if (nil? x#) (some-of ~@more) x#)))))
|
|
853
|
+
|
|
854
|
+
(defn expand-rule [clause context _used-args]
|
|
855
|
+
(let [[rule & call-args] clause
|
|
856
|
+
seqid (swap! rule-seqid inc)
|
|
857
|
+
branches (get (:rules context) rule)
|
|
858
|
+
call-args-new (map #(if (free-var? %) % (symbol (str "?__auto__" %2)))
|
|
859
|
+
call-args
|
|
860
|
+
(range))
|
|
861
|
+
consts (->> (map vector call-args-new call-args)
|
|
862
|
+
(filter (fn [[new old]] (not= new old)))
|
|
863
|
+
(into {}))]
|
|
864
|
+
[(for [branch branches
|
|
865
|
+
:let [[[_ & rule-args] & clauses] branch
|
|
866
|
+
replacements (zipmap rule-args call-args-new)]]
|
|
867
|
+
(walk/postwalk
|
|
868
|
+
#(if (free-var? %)
|
|
869
|
+
(some-of
|
|
870
|
+
(replacements %)
|
|
871
|
+
(symbol (str (name %) "__auto__" seqid)))
|
|
872
|
+
%)
|
|
873
|
+
clauses))
|
|
874
|
+
consts]))
|
|
875
|
+
|
|
876
|
+
(defn remove-pairs [xs ys]
|
|
877
|
+
(let [pairs (->> (map vector xs ys)
|
|
878
|
+
(remove (fn [[x y]] (= x y))))]
|
|
879
|
+
[(map first pairs)
|
|
880
|
+
(map second pairs)]))
|
|
881
|
+
|
|
882
|
+
(defn rule-gen-guards [rule-clause used-args]
|
|
883
|
+
(let [[rule & call-args] rule-clause
|
|
884
|
+
prev-call-args (get used-args rule)]
|
|
885
|
+
(for [prev-args prev-call-args
|
|
886
|
+
:let [[call-args prev-args] (remove-pairs call-args prev-args)]]
|
|
887
|
+
[(concat ['-differ?] call-args prev-args)])))
|
|
888
|
+
|
|
889
|
+
(defn walk-collect [form pred]
|
|
890
|
+
(let [res (atom [])]
|
|
891
|
+
(walk/postwalk #(do (when (pred %) (swap! res conj %)) %) form)
|
|
892
|
+
@res))
|
|
893
|
+
|
|
894
|
+
(defn collect-vars [clause]
|
|
895
|
+
(set (walk-collect clause free-var?)))
|
|
896
|
+
|
|
897
|
+
(defn split-guards [clauses guards]
|
|
898
|
+
(let [bound-vars (collect-vars clauses)
|
|
899
|
+
pred (fn [[[_ & vars]]] (every? bound-vars vars))]
|
|
900
|
+
[(filter pred guards)
|
|
901
|
+
(remove pred guards)]))
|
|
902
|
+
|
|
903
|
+
(defn solve-rule [context clause]
|
|
904
|
+
(let [final-attrs (filter free-var? clause)
|
|
905
|
+
final-attrs-map (zipmap final-attrs (range))
|
|
906
|
+
stats? (:stats context)
|
|
907
|
+
;; clause-cache (atom {}) ;; TODO
|
|
908
|
+
solve (fn [prefix-context clause clauses]
|
|
909
|
+
(if stats?
|
|
910
|
+
(dqs/update-ctx-with-stats prefix-context clause
|
|
911
|
+
(fn [ctx]
|
|
912
|
+
(let [tmp-context (dt/resolve-clauses
|
|
913
|
+
-resolve-clause
|
|
914
|
+
(assoc ctx :stats [])
|
|
915
|
+
clauses)]
|
|
916
|
+
(assoc tmp-context
|
|
917
|
+
:stats (:stats ctx)
|
|
918
|
+
:tmp-stats {:type :solve
|
|
919
|
+
:clauses clauses
|
|
920
|
+
:branches (:stats tmp-context)}))))
|
|
921
|
+
(dt/resolve-clauses -resolve-clause prefix-context clauses)))
|
|
922
|
+
empty-rels? (fn [ctx]
|
|
923
|
+
(some #(empty? (:tuples %)) (:rels ctx)))]
|
|
924
|
+
(loop [stack (list {:prefix-clauses []
|
|
925
|
+
:prefix-context context
|
|
926
|
+
:clauses [clause]
|
|
927
|
+
:used-args {}
|
|
928
|
+
:pending-guards {}
|
|
929
|
+
:clause clause})
|
|
930
|
+
rel (Relation. final-attrs-map [])
|
|
931
|
+
tmp-stats []]
|
|
932
|
+
(if-some [frame (first stack)]
|
|
933
|
+
(let [[clauses [rule-clause & next-clauses]] (split-with #(not (rule? context %)) (:clauses frame))]
|
|
934
|
+
(if (nil? rule-clause)
|
|
935
|
+
|
|
936
|
+
;; no rules -> expand, collect, sum
|
|
937
|
+
(let [prefix-context (solve (:prefix-context frame) (:clause frame) clauses)
|
|
938
|
+
tuples (-collect prefix-context final-attrs)
|
|
939
|
+
new-rel (Relation. final-attrs-map tuples)
|
|
940
|
+
new-stats (conj tmp-stats (last (:stats prefix-context)))]
|
|
941
|
+
(recur (next stack) (sum-rel rel new-rel) new-stats))
|
|
942
|
+
|
|
943
|
+
;; has rule -> add guards -> check if dead -> expand rule -> push to stack, recur
|
|
944
|
+
(let [[rule & call-args] rule-clause
|
|
945
|
+
guards (rule-gen-guards rule-clause (:used-args frame))
|
|
946
|
+
[active-gs pending-gs] (split-guards (concat (:prefix-clauses frame) clauses)
|
|
947
|
+
(concat guards (:pending-guards frame)))]
|
|
948
|
+
(if (some #(= % '[(-differ?)]) active-gs) ;; trivial always false case like [(not= [?a ?b] [?a ?b])]
|
|
949
|
+
|
|
950
|
+
;; this branch has no data, just drop it from stack
|
|
951
|
+
(recur (next stack) rel tmp-stats)
|
|
952
|
+
|
|
953
|
+
(let [prefix-clauses (concat clauses active-gs)
|
|
954
|
+
prefix-context (solve (:prefix-context frame) (:clause frame) prefix-clauses)
|
|
955
|
+
new-stats (conj tmp-stats (last (:stats prefix-context)))]
|
|
956
|
+
(if (empty-rels? prefix-context)
|
|
957
|
+
|
|
958
|
+
;; this branch has no data, just drop it from stack
|
|
959
|
+
(recur (next stack) rel new-stats)
|
|
960
|
+
|
|
961
|
+
;; need to expand rule to branches
|
|
962
|
+
(let [used-args (assoc (:used-args frame) rule
|
|
963
|
+
(conj (get (:used-args frame) rule []) call-args))
|
|
964
|
+
[branches rule-consts] (expand-rule rule-clause context used-args)]
|
|
965
|
+
(recur (concat
|
|
966
|
+
(for [branch branches]
|
|
967
|
+
{:prefix-clauses prefix-clauses
|
|
968
|
+
:prefix-context (update prefix-context :consts merge rule-consts)
|
|
969
|
+
:clauses (concatv branch next-clauses)
|
|
970
|
+
:used-args used-args
|
|
971
|
+
:pending-guards pending-gs
|
|
972
|
+
:clause branch})
|
|
973
|
+
(next stack))
|
|
974
|
+
rel
|
|
975
|
+
new-stats))))))))
|
|
976
|
+
(cond-> (update context :rels collapse-rels rel)
|
|
977
|
+
stats? (assoc :tmp-stats {:type :rule
|
|
978
|
+
:branches tmp-stats}))))))
|
|
979
|
+
|
|
980
|
+
(defn resolve-pattern-lookup-entity-id [source e error-code]
|
|
981
|
+
(cond
|
|
982
|
+
(dbu/numeric-entid? e) e
|
|
983
|
+
(or (lookup-ref? e) (attr? e)) (dbu/entid-strict source e error-code)
|
|
984
|
+
;(entid? e) e
|
|
985
|
+
(keyword? e) e
|
|
986
|
+
(symbol? e) e
|
|
987
|
+
:else (or error-code (dt/raise "Invalid entid" {:error :entity-id/syntax :entity-id e}))))
|
|
988
|
+
|
|
989
|
+
(defn resolve-pattern-lookup-refs
|
|
990
|
+
"Translate pattern entries before using pattern for database search"
|
|
991
|
+
([source pattern] (resolve-pattern-lookup-refs source pattern nil))
|
|
992
|
+
([source pattern error-code]
|
|
993
|
+
(if (dbu/db? source)
|
|
994
|
+
(dt/with-destructured-vector pattern
|
|
995
|
+
e (resolve-pattern-lookup-entity-id source e error-code)
|
|
996
|
+
a (if (and (:attribute-refs? (dbi/-config source)) (keyword? a))
|
|
997
|
+
(dbi/-ref-for source a)
|
|
998
|
+
a)
|
|
999
|
+
v (if (and v (attr? a) (dbu/ref? source a) (or (lookup-ref? v) (attr? v)))
|
|
1000
|
+
(dbu/entid-strict source v error-code)
|
|
1001
|
+
v)
|
|
1002
|
+
tx (if (lookup-ref? tx)
|
|
1003
|
+
(dbu/entid-strict source tx error-code)
|
|
1004
|
+
tx)
|
|
1005
|
+
added added)
|
|
1006
|
+
pattern)))
|
|
1007
|
+
|
|
1008
|
+
(defn good-lookup-refs? [pattern]
|
|
1009
|
+
(if (coll? pattern)
|
|
1010
|
+
(not-any? #(= % ::error) pattern)
|
|
1011
|
+
(not= ::error pattern)))
|
|
1012
|
+
|
|
1013
|
+
(defn resolve-pattern-lookup-refs-or-nil
|
|
1014
|
+
"This function works just like `resolve-pattern-lookup-refs` but if there is an error it returns `nil` instead of throwing an exception. This is used to reject patterns with variables substituted for invalid values.
|
|
1015
|
+
|
|
1016
|
+
For instance, take the query
|
|
1017
|
+
|
|
1018
|
+
(d/q '[:find ?e
|
|
1019
|
+
:in $ [?e ...]
|
|
1020
|
+
:where [?e :friend 3]]
|
|
1021
|
+
db [1 2 3 \"A\"])
|
|
1022
|
+
|
|
1023
|
+
in the test `datahike.test.lookup-refs-test/test-lookup-refs-query`.
|
|
1024
|
+
|
|
1025
|
+
According to this query, the variable `?e` can be either `1`, `2`, `3` or `\"A\"`
|
|
1026
|
+
but \"A\" is not a valid entity.
|
|
1027
|
+
|
|
1028
|
+
The query engine will evaluate the pattern `[?e :friend 3]`. For the strategies
|
|
1029
|
+
`identity` and `select-simple`, no substitution will be performed in this pattern.
|
|
1030
|
+
Instead, they will ask for all tuples from the database and then filter them, so
|
|
1031
|
+
the fact that `?e` can be bound to an impossible entity id `\"A\"` is not a problem.
|
|
1032
|
+
|
|
1033
|
+
But with the strategy `select-all`, the substituted pattern will become
|
|
1034
|
+
|
|
1035
|
+
[\"A\" :friend 3]
|
|
1036
|
+
|
|
1037
|
+
and consequently, the `result` below will take the value `[::error :friend 3]`.
|
|
1038
|
+
The unit test is currently written to simply ignore illegal illegal entity ids
|
|
1039
|
+
such as \"A\" and therefore, we handle that by letting this function return nil
|
|
1040
|
+
in those cases.
|
|
1041
|
+
"
|
|
1042
|
+
[source pattern]
|
|
1043
|
+
(let [result (resolve-pattern-lookup-refs source pattern ::error)]
|
|
1044
|
+
(when (good-lookup-refs? result)
|
|
1045
|
+
result)))
|
|
1046
|
+
|
|
1047
|
+
(defn dynamic-lookup-attrs [source pattern]
|
|
1048
|
+
(let [[e a v tx] pattern]
|
|
1049
|
+
(cond-> #{}
|
|
1050
|
+
(free-var? e) (conj e)
|
|
1051
|
+
(free-var? tx) (conj tx)
|
|
1052
|
+
(and
|
|
1053
|
+
(free-var? v)
|
|
1054
|
+
(not (free-var? a))
|
|
1055
|
+
(dbu/ref? source a)) (conj v))))
|
|
1056
|
+
|
|
1057
|
+
(defn limit-rel [rel vars]
|
|
1058
|
+
(when-some [attrs' (not-empty (select-keys (:attrs rel) vars))]
|
|
1059
|
+
(assoc rel :attrs attrs')))
|
|
1060
|
+
|
|
1061
|
+
(defn limit-context [context vars]
|
|
1062
|
+
(assoc context
|
|
1063
|
+
:rels (->> (:rels context)
|
|
1064
|
+
(keep #(limit-rel % vars)))))
|
|
1065
|
+
|
|
1066
|
+
(defn check-all-bound [context vars form]
|
|
1067
|
+
(let [bound (set (concat (mapcat #(keys (:attrs %)) (:rels context))
|
|
1068
|
+
(keys (:consts context))))]
|
|
1069
|
+
(when-not (set/subset? vars bound)
|
|
1070
|
+
(let [missing (set/difference (set vars) bound)]
|
|
1071
|
+
(dt/raise "Insufficient bindings: " missing " not bound in " form
|
|
1072
|
+
{:error :query/where
|
|
1073
|
+
:form form
|
|
1074
|
+
:vars missing})))))
|
|
1075
|
+
|
|
1076
|
+
(defn check-some-bound [context vars form]
|
|
1077
|
+
(let [bound (set (concat (mapcat #(keys (:attrs %)) (:rels context))
|
|
1078
|
+
(keys (:consts context))))]
|
|
1079
|
+
(when (empty? (set/intersection vars bound))
|
|
1080
|
+
(dt/raise "Insufficient bindings: none of " vars " is bound in " form
|
|
1081
|
+
{:error :query/where
|
|
1082
|
+
:form form}))))
|
|
1083
|
+
|
|
1084
|
+
(defn resolve-context [context clauses]
|
|
1085
|
+
(dt/resolve-clauses resolve-clause context clauses))
|
|
1086
|
+
|
|
1087
|
+
(defn tuple-var-mapper [rel]
|
|
1088
|
+
(let [attrs (:attrs rel)
|
|
1089
|
+
key-fn-pairs (into []
|
|
1090
|
+
(map (juxt identity (partial getter-fn attrs)))
|
|
1091
|
+
(keys attrs))]
|
|
1092
|
+
(fn [tuple]
|
|
1093
|
+
(into {}
|
|
1094
|
+
(map (fn [[k f]] [k (f tuple)]))
|
|
1095
|
+
key-fn-pairs))))
|
|
1096
|
+
|
|
1097
|
+
(def rel-product-unit (Relation. {} [[]]))
|
|
1098
|
+
|
|
1099
|
+
(defn bound-symbol-map
|
|
1100
|
+
"Given a sequential collection of relations, return a map where every key is a symbol of a variable and every value is a map with the keys `:relation-index` and `:tuple-element-index`. The key `:relation-index` is associated with the index of the relation where the variable occurs and the key `:tuple-element-index` is associated with the index of the location in the clause where the symbol occurs."
|
|
1101
|
+
[rels]
|
|
1102
|
+
(into {} (for [[rel-index rel] (map-indexed vector rels)
|
|
1103
|
+
[sym tup-index] (:attrs rel)]
|
|
1104
|
+
[sym {:relation-index rel-index
|
|
1105
|
+
:tuple-element-index tup-index}])))
|
|
1106
|
+
|
|
1107
|
+
(defn normalize-pattern
|
|
1108
|
+
"Takes a pattern and returns a new pattern with exactly five elements, filling in any missing ones with nil."
|
|
1109
|
+
[[e a v tx added?]]
|
|
1110
|
+
[e a v tx added?])
|
|
1111
|
+
|
|
1112
|
+
(defn replace-unbound-symbols-by-nil [bsm pattern]
|
|
1113
|
+
(normalize-pattern
|
|
1114
|
+
(mapv #(when-not (and (symbol? %) (not (contains? bsm %)))
|
|
1115
|
+
%)
|
|
1116
|
+
pattern)))
|
|
1117
|
+
|
|
1118
|
+
(defn search-index-mapping
|
|
1119
|
+
"Returns a sequence of maps with index-information for a subset of e, a, v, tx. The `strategy-vec` argument is a vector of four elements corresponding to e, a, v, tx respectively. Every such element can be either `:substitute`, `:filter` or `nil` depending on how the corresponding element in the pattern should be used. The `clean-pattern` is argument is a vector with the elements corresponding to e, a, v, tx. The argument `selected-strategy-symbol` can be either `:substitute`, `:filter` or `nil` and is used to filter e, a, v, tx based on the value of `:strategy-vec`."
|
|
1120
|
+
[{:keys [strategy-vec clean-pattern bsm]}
|
|
1121
|
+
selected-strategy-symbol]
|
|
1122
|
+
{:pre [(= 4 (count strategy-vec))]}
|
|
1123
|
+
(let [pattern (normalize-pattern clean-pattern)]
|
|
1124
|
+
(for [[pattern-element-index
|
|
1125
|
+
pattern-var
|
|
1126
|
+
strategy-symbol] (map vector (range) pattern strategy-vec)
|
|
1127
|
+
:when (= selected-strategy-symbol strategy-symbol)
|
|
1128
|
+
:let [m (bsm pattern-var)]
|
|
1129
|
+
:when m]
|
|
1130
|
+
(assoc m :pattern-element-index pattern-element-index))))
|
|
1131
|
+
|
|
1132
|
+
(defn substitution-relation-indices
|
|
1133
|
+
"Returns the set of indices of relations that have symbols that are substituted for actual values in the pattern before index lookup."
|
|
1134
|
+
[context]
|
|
1135
|
+
(into #{}
|
|
1136
|
+
(map :relation-index)
|
|
1137
|
+
(search-index-mapping context :substitute)))
|
|
1138
|
+
|
|
1139
|
+
(defn filtering-relation-indices
|
|
1140
|
+
"Returns the set of indices of relations that have symbols that will be used for filtering the datoms returned from the inex lookup."
|
|
1141
|
+
[context subst-inds]
|
|
1142
|
+
(into #{}
|
|
1143
|
+
(comp (map :relation-index)
|
|
1144
|
+
(remove subst-inds))
|
|
1145
|
+
(search-index-mapping context :filter)))
|
|
1146
|
+
|
|
1147
|
+
(defn index-feature-extractor
|
|
1148
|
+
"Given a set of indices referring to elements in a sequential container such as a datom or vector, construct a function that returns a value computed from such a sequential container such that two different values returned from that function are equal if and only if their corresponding values at those indices are equal. Optionally takes a function that can remap the selected elements."
|
|
1149
|
+
([inds include-empty?]
|
|
1150
|
+
(index-feature-extractor inds include-empty? (fn [_ x] x)))
|
|
1151
|
+
([inds include-empty? replacer]
|
|
1152
|
+
(let [first-index (first inds)]
|
|
1153
|
+
(case (count inds)
|
|
1154
|
+
0 (when include-empty?
|
|
1155
|
+
(fn
|
|
1156
|
+
([] [nil])
|
|
1157
|
+
([_] nil)))
|
|
1158
|
+
1 (fn
|
|
1159
|
+
([] [first-index])
|
|
1160
|
+
([x] (wrap-comparable (replacer first-index (nth x first-index)))))
|
|
1161
|
+
(fn
|
|
1162
|
+
([] inds)
|
|
1163
|
+
([x]
|
|
1164
|
+
(mapv #(wrap-comparable (replacer % (nth x %))) inds)))))))
|
|
1165
|
+
|
|
1166
|
+
(defn extend-predicate1 [predicate feature-extractor ref-feature]
|
|
1167
|
+
(if (nil? feature-extractor)
|
|
1168
|
+
predicate
|
|
1169
|
+
(if predicate
|
|
1170
|
+
(fn [datom]
|
|
1171
|
+
(let [feature (feature-extractor datom)]
|
|
1172
|
+
(if (= ref-feature feature)
|
|
1173
|
+
(predicate datom)
|
|
1174
|
+
false)))
|
|
1175
|
+
(fn [datom]
|
|
1176
|
+
(= ref-feature (feature-extractor datom))))))
|
|
1177
|
+
|
|
1178
|
+
(defn predicate-from-set [s]
|
|
1179
|
+
(case (count s)
|
|
1180
|
+
0 (fn [_] false)
|
|
1181
|
+
1 (let [y (first s)]
|
|
1182
|
+
(fn [x] (= x y)))
|
|
1183
|
+
(fn [x] (contains? s x))))
|
|
1184
|
+
|
|
1185
|
+
(defn extend-predicate [predicate feature-extractor features]
|
|
1186
|
+
{:pre [#?(:clj (or (set? features) (instance? HashSet features))
|
|
1187
|
+
:cljs (set? features))]}
|
|
1188
|
+
(let [this-pred (predicate-from-set features)]
|
|
1189
|
+
(if (nil? feature-extractor)
|
|
1190
|
+
predicate
|
|
1191
|
+
(if predicate
|
|
1192
|
+
(fn
|
|
1193
|
+
([] (conj (predicate) [(feature-extractor) features]))
|
|
1194
|
+
([datom]
|
|
1195
|
+
(let [feature (feature-extractor datom)]
|
|
1196
|
+
(if (this-pred feature)
|
|
1197
|
+
(predicate datom)
|
|
1198
|
+
false))))
|
|
1199
|
+
(fn
|
|
1200
|
+
([] [(feature-extractor) features])
|
|
1201
|
+
([datom]
|
|
1202
|
+
(this-pred (feature-extractor datom))))))))
|
|
1203
|
+
|
|
1204
|
+
(defn resolve-pattern-lookup-ref-at-index
|
|
1205
|
+
[source clean-attribute pattern-index pattern-value error-code]
|
|
1206
|
+
(let [a clean-attribute]
|
|
1207
|
+
(case (int pattern-index)
|
|
1208
|
+
0 (resolve-pattern-lookup-entity-id source pattern-value error-code)
|
|
1209
|
+
1 (if (and (:attribute-refs? (dbi/-config source)) (keyword? pattern-value))
|
|
1210
|
+
(dbi/-ref-for source pattern-value)
|
|
1211
|
+
pattern-value)
|
|
1212
|
+
2 (if (and pattern-value
|
|
1213
|
+
(attr? a)
|
|
1214
|
+
(dbu/ref? source a)
|
|
1215
|
+
(or (lookup-ref? pattern-value) (attr? pattern-value)))
|
|
1216
|
+
(dbu/entid-strict source pattern-value error-code)
|
|
1217
|
+
pattern-value)
|
|
1218
|
+
3 (if (lookup-ref? pattern-value)
|
|
1219
|
+
(dbu/entid-strict source pattern-value error-code)
|
|
1220
|
+
pattern-value)
|
|
1221
|
+
4 pattern-value)))
|
|
1222
|
+
|
|
1223
|
+
(defn lookup-ref-replacer
|
|
1224
|
+
([context] (lookup-ref-replacer context ::error))
|
|
1225
|
+
([{:keys [source clean-pattern]} error-value]
|
|
1226
|
+
(let [[_ attribute _ _] clean-pattern]
|
|
1227
|
+
(if source
|
|
1228
|
+
(if (dbu/db? source)
|
|
1229
|
+
(fn [index pattern-value]
|
|
1230
|
+
(resolve-pattern-lookup-ref-at-index source
|
|
1231
|
+
attribute
|
|
1232
|
+
index
|
|
1233
|
+
pattern-value
|
|
1234
|
+
error-value))
|
|
1235
|
+
(fn [_i x] x))
|
|
1236
|
+
(fn [_ x] x)))))
|
|
1237
|
+
|
|
1238
|
+
(defn- generate-substitution-xform-code [pred-expr
|
|
1239
|
+
datom-predicate-symbol
|
|
1240
|
+
filter-feature-symbol
|
|
1241
|
+
pmask
|
|
1242
|
+
substituted-pattern-and-filter-feature-pairs]
|
|
1243
|
+
(let [pattern-symbols (repeatedly 5 gensym)
|
|
1244
|
+
substitution-value-vector (gensym "substitution-value-vector")]
|
|
1245
|
+
`(fn [step#]
|
|
1246
|
+
(fn
|
|
1247
|
+
([] (step#))
|
|
1248
|
+
([dst-one#] (step# dst-one#))
|
|
1249
|
+
|
|
1250
|
+
;; This is a higher-arity step function.
|
|
1251
|
+
([dst# ~@pattern-symbols ~datom-predicate-symbol]
|
|
1252
|
+
|
|
1253
|
+
;; This generates the code that substitutes some of the
|
|
1254
|
+
;; incomping values by values from the relation and calls
|
|
1255
|
+
;; the next step function in the transducer chain.
|
|
1256
|
+
(reduce
|
|
1257
|
+
(fn [dst-inner# [~substitution-value-vector ~filter-feature-symbol]]
|
|
1258
|
+
(step# dst-inner#
|
|
1259
|
+
~@(map (fn [i sym]
|
|
1260
|
+
(if (nil? i)
|
|
1261
|
+
sym
|
|
1262
|
+
`(nth ~substitution-value-vector ~i)))
|
|
1263
|
+
pmask
|
|
1264
|
+
pattern-symbols)
|
|
1265
|
+
~pred-expr))
|
|
1266
|
+
dst#
|
|
1267
|
+
~substituted-pattern-and-filter-feature-pairs))))))
|
|
1268
|
+
|
|
1269
|
+
(defmacro substitution-expansion [substitution-pattern-element-inds
|
|
1270
|
+
filter-feature-extractor
|
|
1271
|
+
substituted-pattern-and-filter-feature-pairs]
|
|
1272
|
+
(let [datom-predicate-symbol (gensym)
|
|
1273
|
+
filter-feature-symbol (gensym)]
|
|
1274
|
+
|
|
1275
|
+
;; This code generates a tree of `if`-forms for all ordered subsets of
|
|
1276
|
+
;; the sequence `(range 5)` that `substitution-pattern-element-inds`.
|
|
1277
|
+
;; can take. At each leaf of the tree, code is generated for that particular
|
|
1278
|
+
;; subset.
|
|
1279
|
+
(dt/range-subset-tree
|
|
1280
|
+
5
|
|
1281
|
+
substitution-pattern-element-inds
|
|
1282
|
+
|
|
1283
|
+
;; This function is called at each leaf of the tree.
|
|
1284
|
+
;; `pmast` is a boolean sequence
|
|
1285
|
+
(fn [_pinds pmask]
|
|
1286
|
+
|
|
1287
|
+
;; `branch-expr` is a function that generates the actual
|
|
1288
|
+
;; code given a predicate expression.
|
|
1289
|
+
(let [branch-expr (fn [pred-expr]
|
|
1290
|
+
|
|
1291
|
+
;; This is the code for the transducer.
|
|
1292
|
+
(generate-substitution-xform-code
|
|
1293
|
+
pred-expr
|
|
1294
|
+
datom-predicate-symbol
|
|
1295
|
+
filter-feature-symbol
|
|
1296
|
+
pmask
|
|
1297
|
+
substituted-pattern-and-filter-feature-pairs))]
|
|
1298
|
+
|
|
1299
|
+
;; Generate different code depending on whether or not there is a
|
|
1300
|
+
;; `filt-extractor`, meaning that the resulting datoms have to be
|
|
1301
|
+
;; filtered.
|
|
1302
|
+
`(if (nil? ~filter-feature-extractor)
|
|
1303
|
+
~(branch-expr datom-predicate-symbol)
|
|
1304
|
+
~(branch-expr `(extend-predicate1 ~datom-predicate-symbol
|
|
1305
|
+
~filter-feature-extractor
|
|
1306
|
+
~filter-feature-symbol))))))))
|
|
1307
|
+
|
|
1308
|
+
#_(instantiate-substitution-xform substitution-pattern-element-inds
|
|
1309
|
+
filter-feature-extractor
|
|
1310
|
+
substituted-pattern-and-filter-feature-pairs)
|
|
1311
|
+
|
|
1312
|
+
(defn instantiate-substitution-xform [substitution-pattern-element-inds
|
|
1313
|
+
filter-feature-extractor
|
|
1314
|
+
substituted-pattern-and-filter-feature-pairs]
|
|
1315
|
+
|
|
1316
|
+
;; Returns a transducer based on the indices in `substitution-pattern-element-inds`
|
|
1317
|
+
(substitution-expansion substitution-pattern-element-inds
|
|
1318
|
+
filter-feature-extractor
|
|
1319
|
+
substituted-pattern-and-filter-feature-pairs))
|
|
1320
|
+
|
|
1321
|
+
;; The performance improvement of using this macro has been measured,
|
|
1322
|
+
;; see comment in single-substition-xform.
|
|
1323
|
+
(defmacro make-vec-lookup-ref-replacer [range-length]
|
|
1324
|
+
(let [inds (gensym)
|
|
1325
|
+
replacer (gensym)
|
|
1326
|
+
tuple (gensym)
|
|
1327
|
+
ex-sym# (if (get-in &env [:ns])
|
|
1328
|
+
'js/Error
|
|
1329
|
+
Exception)]
|
|
1330
|
+
`(fn tree-fn# [~replacer ~inds]
|
|
1331
|
+
~(dt/range-subset-tree
|
|
1332
|
+
range-length inds
|
|
1333
|
+
(fn replacer-fn# [pinds _mask]
|
|
1334
|
+
`(fn [~tuple]
|
|
1335
|
+
(try
|
|
1336
|
+
~(mapv (fn [index i] `(~replacer ~index (nth ~tuple ~i)))
|
|
1337
|
+
pinds
|
|
1338
|
+
(range))
|
|
1339
|
+
(catch ~ex-sym# e# nil))))))))
|
|
1340
|
+
|
|
1341
|
+
(def vec-lookup-ref-replacer (make-vec-lookup-ref-replacer 5))
|
|
1342
|
+
|
|
1343
|
+
;; The performance improvement of using this macro has been measured,
|
|
1344
|
+
;; see comment in single-substition-xform.
|
|
1345
|
+
(defmacro basic-index-selector [max-length]
|
|
1346
|
+
(let [inds (gensym)
|
|
1347
|
+
|
|
1348
|
+
obj (gensym)]
|
|
1349
|
+
`(fn [~inds]
|
|
1350
|
+
(case (count ~inds)
|
|
1351
|
+
~@(mapcat (fn [length]
|
|
1352
|
+
(let [index-symbols (vec (repeatedly length gensym))]
|
|
1353
|
+
[length `(let [~index-symbols ~inds]
|
|
1354
|
+
(fn [~obj]
|
|
1355
|
+
~(mapv
|
|
1356
|
+
(fn [sym] `(nth ~obj ~sym))
|
|
1357
|
+
index-symbols)))]))
|
|
1358
|
+
(range (inc max-length)))))))
|
|
1359
|
+
|
|
1360
|
+
(def make-basic-index-selector (basic-index-selector 5))
|
|
1361
|
+
|
|
1362
|
+
(defn single-substitution-xform
|
|
1363
|
+
"Returns a transducer that substitutes the symbols for a single relation."
|
|
1364
|
+
[search-context
|
|
1365
|
+
relation-index
|
|
1366
|
+
substituted-vars-per-relation
|
|
1367
|
+
filtered-vars-per-relation]
|
|
1368
|
+
(let [;; This function maps the value at a pattern at a certain index to
|
|
1369
|
+
;; a new value where the lookup-ref has been replaced. If there is an
|
|
1370
|
+
;; error, it returns the `::error` value.
|
|
1371
|
+
lrr (lookup-ref-replacer search-context)
|
|
1372
|
+
|
|
1373
|
+
tuples (:tuples (nth (:rels search-context) relation-index))
|
|
1374
|
+
substituted-vars (substituted-vars-per-relation relation-index)
|
|
1375
|
+
filtered-vars (filtered-vars-per-relation relation-index)
|
|
1376
|
+
pattern-substitution-inds (map :tuple-element-index substituted-vars)
|
|
1377
|
+
pattern-filter-inds (map :tuple-element-index filtered-vars)
|
|
1378
|
+
|
|
1379
|
+
;; This function returns a unique feature for the values at
|
|
1380
|
+
;; `pattern-filter-inds` given a pattern.
|
|
1381
|
+
feature-extractor (index-feature-extractor pattern-filter-inds
|
|
1382
|
+
true
|
|
1383
|
+
lrr)
|
|
1384
|
+
|
|
1385
|
+
;; These are the indices of the locations in the pattern that will be substituted
|
|
1386
|
+
;; with values from the tuples in this relation.
|
|
1387
|
+
substitution-pattern-element-inds (map :pattern-element-index substituted-vars)
|
|
1388
|
+
|
|
1389
|
+
;; This function maps the value at a pattern at a certain index to
|
|
1390
|
+
;; a new value where the lookup-ref has been replaced. If there is an error,
|
|
1391
|
+
;; an exception is thrown.
|
|
1392
|
+
lrr-ex (lookup-ref-replacer search-context nil)
|
|
1393
|
+
|
|
1394
|
+
;; This constructs a new pattern given a tuple of values that will be inserted
|
|
1395
|
+
;; at the `substitution-pattern-element-inds`.
|
|
1396
|
+
;;
|
|
1397
|
+
;; Precomputing this function moves some work out of the loop
|
|
1398
|
+
;; and contributes to about 1½ seconds reduction in
|
|
1399
|
+
;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
|
|
1400
|
+
pattern-from-tuple (vec-lookup-ref-replacer lrr-ex substitution-pattern-element-inds)
|
|
1401
|
+
|
|
1402
|
+
;; This is a function that simply picks out a subset of the elements from a sequential
|
|
1403
|
+
;; collection, at the indices `pattern-subsitution-inds`.
|
|
1404
|
+
;;
|
|
1405
|
+
;; Precomputing this function moves some work out of the loop
|
|
1406
|
+
;; and contributes to about 2 seconds reduction in
|
|
1407
|
+
;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
|
|
1408
|
+
select-pattern-substitution-inds (make-basic-index-selector pattern-substitution-inds)
|
|
1409
|
+
|
|
1410
|
+
;; This is a list of pairs such that:
|
|
1411
|
+
;;
|
|
1412
|
+
;; * The first element is a pattern where variables for this relation have been substituted.
|
|
1413
|
+
;; * The second element is a feature used for filtering the datoms after querying the backend
|
|
1414
|
+
;;
|
|
1415
|
+
;; Using a transducer here (with a transient vector under the hood)
|
|
1416
|
+
;; is about ½ second faster than a doseq-loop that accumulates to
|
|
1417
|
+
;; an ArrayList in the benchmark
|
|
1418
|
+
;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
|
|
1419
|
+
;; In other words, there is no use writing imperative code here
|
|
1420
|
+
;; with Java mutable collections.
|
|
1421
|
+
substituted-pattern-and-filter-feature-pairs
|
|
1422
|
+
(into []
|
|
1423
|
+
(keep
|
|
1424
|
+
(fn [tuple]
|
|
1425
|
+
(let [feature (feature-extractor tuple)]
|
|
1426
|
+
(when (good-lookup-refs? feature)
|
|
1427
|
+
(when-let [k (-> tuple
|
|
1428
|
+
select-pattern-substitution-inds
|
|
1429
|
+
pattern-from-tuple)]
|
|
1430
|
+
[k feature])))))
|
|
1431
|
+
tuples)
|
|
1432
|
+
|
|
1433
|
+
filter-feature-extractor (index-feature-extractor
|
|
1434
|
+
(map :pattern-element-index filtered-vars)
|
|
1435
|
+
false
|
|
1436
|
+
lrr)]
|
|
1437
|
+
|
|
1438
|
+
;; This expression will produce an `xform` that performs the substitutions for
|
|
1439
|
+
;; this relation.
|
|
1440
|
+
(instantiate-substitution-xform substitution-pattern-element-inds
|
|
1441
|
+
filter-feature-extractor
|
|
1442
|
+
substituted-pattern-and-filter-feature-pairs)))
|
|
1443
|
+
|
|
1444
|
+
(defn search-context? [x]
|
|
1445
|
+
(assert (map? x))
|
|
1446
|
+
(let [{:keys [bsm clean-pattern rels strategy-vec]} x]
|
|
1447
|
+
(assert bsm)
|
|
1448
|
+
(assert clean-pattern)
|
|
1449
|
+
(assert rels)
|
|
1450
|
+
(assert strategy-vec))
|
|
1451
|
+
true)
|
|
1452
|
+
|
|
1453
|
+
(defn compute-per-rel-map [search-context rel-inds strat-symbol]
|
|
1454
|
+
{:pre [(search-context? search-context)]}
|
|
1455
|
+
(->> strat-symbol
|
|
1456
|
+
(search-index-mapping search-context)
|
|
1457
|
+
(filter (comp rel-inds :relation-index))
|
|
1458
|
+
(group-by :relation-index)))
|
|
1459
|
+
|
|
1460
|
+
(defn clean-pattern-before-substitution [pattern subst-map]
|
|
1461
|
+
(let [subst-pattern-positions (into #{}
|
|
1462
|
+
(comp cat (map :pattern-element-index))
|
|
1463
|
+
(vals subst-map))]
|
|
1464
|
+
(into []
|
|
1465
|
+
(map-indexed (fn [i x]
|
|
1466
|
+
(cond
|
|
1467
|
+
(subst-pattern-positions i) x
|
|
1468
|
+
(symbol? x) nil
|
|
1469
|
+
:else x)))
|
|
1470
|
+
pattern)))
|
|
1471
|
+
|
|
1472
|
+
(defn initialization-and-substitution-xform
|
|
1473
|
+
"Returns a transducer that performs all subsitutions possible given the relations with indices `rel-inds`."
|
|
1474
|
+
[search-context substituted-relation-inds]
|
|
1475
|
+
{:pre [(map? search-context)
|
|
1476
|
+
(set? substituted-relation-inds)]}
|
|
1477
|
+
(let [;; We refer to relations by their index in the vector in the context.
|
|
1478
|
+
substituted-vars-per-relation (compute-per-rel-map search-context
|
|
1479
|
+
substituted-relation-inds
|
|
1480
|
+
:substitute)
|
|
1481
|
+
|
|
1482
|
+
filtered-vars-per-relation (compute-per-rel-map search-context
|
|
1483
|
+
substituted-relation-inds
|
|
1484
|
+
:filter)
|
|
1485
|
+
|
|
1486
|
+
all-substitutions-xform (apply comp
|
|
1487
|
+
(map (fn [relation-index]
|
|
1488
|
+
(single-substitution-xform
|
|
1489
|
+
search-context
|
|
1490
|
+
relation-index
|
|
1491
|
+
substituted-vars-per-relation
|
|
1492
|
+
filtered-vars-per-relation))
|
|
1493
|
+
substituted-relation-inds))
|
|
1494
|
+
init-coll [[;; This is the initial pattern
|
|
1495
|
+
(clean-pattern-before-substitution
|
|
1496
|
+
(:clean-pattern search-context)
|
|
1497
|
+
substituted-vars-per-relation)
|
|
1498
|
+
|
|
1499
|
+
;; This is the initial predicate (nil because there is no predicate)
|
|
1500
|
+
nil]]]
|
|
1501
|
+
[init-coll all-substitutions-xform]))
|
|
1502
|
+
|
|
1503
|
+
(defn datom-filter-predicate [filtered-relation-inds search-context]
|
|
1504
|
+
(let [filtered-vars-per-relation (compute-per-rel-map search-context filtered-relation-inds :filter)
|
|
1505
|
+
rels (:rels search-context)]
|
|
1506
|
+
(reduce (fn [predicate [relation-index filtered-vars]]
|
|
1507
|
+
(let [tuples (:tuples (nth rels relation-index))
|
|
1508
|
+
pos-inds (map :pattern-element-index filtered-vars)
|
|
1509
|
+
tup-inds (map :tuple-element-index filtered-vars)
|
|
1510
|
+
tuple-feature-extractor (index-feature-extractor tup-inds true)
|
|
1511
|
+
features (into #{}
|
|
1512
|
+
(map tuple-feature-extractor)
|
|
1513
|
+
tuples)
|
|
1514
|
+
datom-feature-extractor
|
|
1515
|
+
(index-feature-extractor pos-inds false)]
|
|
1516
|
+
(extend-predicate predicate
|
|
1517
|
+
datom-feature-extractor
|
|
1518
|
+
features)))
|
|
1519
|
+
nil
|
|
1520
|
+
filtered-vars-per-relation)))
|
|
1521
|
+
|
|
1522
|
+
(defn filter-from-predicate [pred]
|
|
1523
|
+
(if pred
|
|
1524
|
+
(filter pred)
|
|
1525
|
+
identity))
|
|
1526
|
+
|
|
1527
|
+
(defn backend-xform [backend-fn]
|
|
1528
|
+
(fn [step]
|
|
1529
|
+
(fn
|
|
1530
|
+
([] (step))
|
|
1531
|
+
([dst] (step dst))
|
|
1532
|
+
([dst e a v tx added? datom-predicate]
|
|
1533
|
+
(let [inner-step (if datom-predicate
|
|
1534
|
+
(fn [dst datom]
|
|
1535
|
+
(if (datom-predicate datom)
|
|
1536
|
+
(step dst datom)
|
|
1537
|
+
dst))
|
|
1538
|
+
step)
|
|
1539
|
+
datoms (try
|
|
1540
|
+
(backend-fn e a v tx added?)
|
|
1541
|
+
(catch #?(:clj Exception :cljs js/Error) e
|
|
1542
|
+
(throw e)))]
|
|
1543
|
+
(reduce inner-step
|
|
1544
|
+
dst
|
|
1545
|
+
datoms))))))
|
|
1546
|
+
|
|
1547
|
+
(defn extend-predicate-for-pattern-constants
|
|
1548
|
+
[predicate {:keys [strategy-vec clean-pattern] :as search-context}]
|
|
1549
|
+
(let [inds (for [[i strategy pattern-value] (mapv vector (range)
|
|
1550
|
+
strategy-vec
|
|
1551
|
+
clean-pattern)
|
|
1552
|
+
:when (= :filter strategy)
|
|
1553
|
+
:when (and (some? pattern-value)
|
|
1554
|
+
(not (symbol? pattern-value)))]
|
|
1555
|
+
i)
|
|
1556
|
+
extractor (index-feature-extractor
|
|
1557
|
+
inds
|
|
1558
|
+
false
|
|
1559
|
+
(lookup-ref-replacer search-context))]
|
|
1560
|
+
(if extractor
|
|
1561
|
+
(extend-predicate predicate extractor #{(extractor clean-pattern)})
|
|
1562
|
+
predicate)))
|
|
1563
|
+
|
|
1564
|
+
(defn unpack6 [step]
|
|
1565
|
+
(fn
|
|
1566
|
+
([] (step))
|
|
1567
|
+
([dst] (step dst))
|
|
1568
|
+
([dst [[e a v tx added?] filt]]
|
|
1569
|
+
(step dst e a v tx added? filt))))
|
|
1570
|
+
|
|
1571
|
+
(defn search-batch-fn
|
|
1572
|
+
"This function constructs a \"strategy function\" that gets called by `dbi/-batch-search.`"
|
|
1573
|
+
[search-context]
|
|
1574
|
+
(fn [strategy-vec backend-fn datom-xform]
|
|
1575
|
+
(let [search-context (merge search-context {:strategy-vec strategy-vec
|
|
1576
|
+
:backend-fn backend-fn})
|
|
1577
|
+
|
|
1578
|
+
;; Relations with indices `substituted-relation-inds` are used for substituting variables
|
|
1579
|
+
;; in the pattern.
|
|
1580
|
+
substituted-relation-inds (substitution-relation-indices search-context)
|
|
1581
|
+
|
|
1582
|
+
;; Relations with indices `filtered-relation-inds` are used for filtering the datoms
|
|
1583
|
+
;; returned by the search backend.
|
|
1584
|
+
filtered-relation-inds (filtering-relation-indices search-context substituted-relation-inds)
|
|
1585
|
+
|
|
1586
|
+
[init-coll substitution-xform] (initialization-and-substitution-xform
|
|
1587
|
+
search-context
|
|
1588
|
+
substituted-relation-inds)
|
|
1589
|
+
|
|
1590
|
+
filter-xform (-> filtered-relation-inds
|
|
1591
|
+
(datom-filter-predicate search-context)
|
|
1592
|
+
(extend-predicate-for-pattern-constants search-context)
|
|
1593
|
+
filter-from-predicate)
|
|
1594
|
+
|
|
1595
|
+
;; This transduction will take the initial pattern,
|
|
1596
|
+
;; perform all variable substitutions for all combinations
|
|
1597
|
+
;; of relations and then look up the datoms in the index.
|
|
1598
|
+
;; Finally, the datoms will be filtered for the variables
|
|
1599
|
+
;; that were not substituted.
|
|
1600
|
+
result (into []
|
|
1601
|
+
|
|
1602
|
+
;; From the output of `unpack6`
|
|
1603
|
+
;; to the input of `backend-xform`
|
|
1604
|
+
;; the transducers are higher-arity. That is,
|
|
1605
|
+
;; instead of calling `(step acc [[e a v tx added?] pred])`,
|
|
1606
|
+
;; they call `(step acc e a v tx added? pred)`. This avoids
|
|
1607
|
+
;; the allocation of short-lived vectors and speeds up the
|
|
1608
|
+
;; process by about 0.4 seconds in
|
|
1609
|
+
;; https://gitlab.com/arbetsformedlingen/taxonomy-dev/backend/experimental/datahike-benchmark/
|
|
1610
|
+
|
|
1611
|
+
(comp
|
|
1612
|
+
|
|
1613
|
+
;; Unpack the pattern as arguments to the next step function.
|
|
1614
|
+
unpack6
|
|
1615
|
+
|
|
1616
|
+
;; Substitute variables with values
|
|
1617
|
+
;; from tuples in the relations and accumulate the filter predicate.
|
|
1618
|
+
substitution-xform
|
|
1619
|
+
|
|
1620
|
+
;; Perform the lookup in the search backend.
|
|
1621
|
+
(backend-xform backend-fn)
|
|
1622
|
+
|
|
1623
|
+
;; Filter the datoms returned from the search backend.
|
|
1624
|
+
filter-xform
|
|
1625
|
+
|
|
1626
|
+
;; Apply the provided datom-xform on the returned datoms
|
|
1627
|
+
datom-xform)
|
|
1628
|
+
init-coll)]
|
|
1629
|
+
result)))
|
|
1630
|
+
|
|
1631
|
+
(defn lookup-batch-search [source context orig-pattern pattern1]
|
|
1632
|
+
(let [new-rel (if (dbu/db? source)
|
|
1633
|
+
(let [rels (vec (:rels context))
|
|
1634
|
+
bsm (bound-symbol-map rels)
|
|
1635
|
+
clean-pattern (->> pattern1
|
|
1636
|
+
(replace-unbound-symbols-by-nil bsm)
|
|
1637
|
+
(resolve-pattern-eid source))
|
|
1638
|
+
search-context {:source source
|
|
1639
|
+
:bsm bsm
|
|
1640
|
+
:clean-pattern clean-pattern
|
|
1641
|
+
:rels rels}
|
|
1642
|
+
|
|
1643
|
+
datoms (if clean-pattern
|
|
1644
|
+
|
|
1645
|
+
;; Make the call to the search backend
|
|
1646
|
+
(dbi/batch-search
|
|
1647
|
+
source clean-pattern
|
|
1648
|
+
(search-batch-fn search-context)
|
|
1649
|
+
(relation-from-datoms-xform))
|
|
1650
|
+
|
|
1651
|
+
[])
|
|
1652
|
+
|
|
1653
|
+
new-rel (relation-from-datoms
|
|
1654
|
+
context orig-pattern datoms)]
|
|
1655
|
+
new-rel)
|
|
1656
|
+
(lookup-pattern-coll source pattern1 orig-pattern))]
|
|
1657
|
+
|
|
1658
|
+
;; This binding is needed for `collapse-rels` to work, and more specifically,
|
|
1659
|
+
;; `hash-join` to work, that in turn depends on `getter-fn`.
|
|
1660
|
+
(binding [*lookup-attrs* (if (satisfies? dbi/IDB source)
|
|
1661
|
+
(dynamic-lookup-attrs source pattern1)
|
|
1662
|
+
*lookup-attrs*)]
|
|
1663
|
+
(cond-> (update context :rels collapse-rels new-rel)
|
|
1664
|
+
(:stats context) (assoc :tmp-stats {:type :lookup})))))
|
|
1665
|
+
|
|
1666
|
+
(defn -resolve-clause*
|
|
1667
|
+
([context clause]
|
|
1668
|
+
(-resolve-clause* context clause clause))
|
|
1669
|
+
([context clause orig-clause]
|
|
1670
|
+
(condp looks-like? clause
|
|
1671
|
+
[[symbol? '*]] ;; predicate [(pred ?a ?b ?c)]
|
|
1672
|
+
(do (check-all-bound context (identity (filter free-var? (first clause))) orig-clause)
|
|
1673
|
+
(filter-by-pred context clause))
|
|
1674
|
+
|
|
1675
|
+
[[symbol? '*] '_] ;; function [(fn ?a ?b) ?res]
|
|
1676
|
+
(bind-by-fn context clause)
|
|
1677
|
+
|
|
1678
|
+
[source? '*] ;; source + anything
|
|
1679
|
+
(let [[source-sym & rest] clause]
|
|
1680
|
+
(binding [*implicit-source* (get (:sources context) source-sym)]
|
|
1681
|
+
(-resolve-clause context rest clause)))
|
|
1682
|
+
|
|
1683
|
+
'[or *] ;; (or ...)
|
|
1684
|
+
(let [[_ & branches] clause
|
|
1685
|
+
context' (assoc context :stats [])
|
|
1686
|
+
contexts (mapv #(resolve-clause context' %) branches)
|
|
1687
|
+
sum-rel (->> contexts
|
|
1688
|
+
(map #(reduce hash-join (:rels %)))
|
|
1689
|
+
(reduce sum-rel))]
|
|
1690
|
+
(cond-> (assoc context :rels [sum-rel])
|
|
1691
|
+
(:stats context) (assoc :tmp-stats {:type :or
|
|
1692
|
+
:branches (mapv :stats contexts)})))
|
|
1693
|
+
|
|
1694
|
+
'[or-join [[*] *] *] ;; (or-join [[req-vars] vars] ...)
|
|
1695
|
+
(let [[_ [req-vars & vars] & branches] clause]
|
|
1696
|
+
(check-all-bound context req-vars orig-clause)
|
|
1697
|
+
(recur context (list* 'or-join (concat req-vars vars) branches) clause))
|
|
1698
|
+
|
|
1699
|
+
'[or-join [*] *] ;; (or-join [vars] ...)
|
|
1700
|
+
;; TODO required vars
|
|
1701
|
+
(let [[_ vars & branches] clause
|
|
1702
|
+
vars (set vars)
|
|
1703
|
+
join-context (-> context
|
|
1704
|
+
(assoc :stats [])
|
|
1705
|
+
(limit-context vars))
|
|
1706
|
+
contexts (map #(-> join-context
|
|
1707
|
+
(resolve-clause %)
|
|
1708
|
+
(limit-context vars))
|
|
1709
|
+
branches)
|
|
1710
|
+
sum-rel (->> contexts
|
|
1711
|
+
(map #(reduce hash-join (:rels %)))
|
|
1712
|
+
(reduce sum-rel))]
|
|
1713
|
+
(cond-> (update context :rels collapse-rels sum-rel)
|
|
1714
|
+
(:stats context) (assoc :tmp-stats {:type :or-join
|
|
1715
|
+
:branches (mapv #(-> % :stats first) contexts)})))
|
|
1716
|
+
|
|
1717
|
+
'[and *] ;; (and ...)
|
|
1718
|
+
(let [[_ & clauses] clause]
|
|
1719
|
+
(if (:stats context)
|
|
1720
|
+
(let [and-context (-> context
|
|
1721
|
+
(assoc :stats [])
|
|
1722
|
+
(resolve-context clauses))]
|
|
1723
|
+
(assoc and-context
|
|
1724
|
+
:tmp-stats {:type :and
|
|
1725
|
+
:branches (:stats and-context)}
|
|
1726
|
+
:stats (:stats context)))
|
|
1727
|
+
(resolve-context context clauses)))
|
|
1728
|
+
|
|
1729
|
+
'[not *] ;; (not ...)
|
|
1730
|
+
(let [[_ & clauses] clause
|
|
1731
|
+
negation-vars (collect-vars clauses)
|
|
1732
|
+
_ (check-some-bound context negation-vars orig-clause)
|
|
1733
|
+
join-rel (reduce hash-join (:rels context))
|
|
1734
|
+
negation-context (-> context
|
|
1735
|
+
(assoc :rels [join-rel])
|
|
1736
|
+
(assoc :stats [])
|
|
1737
|
+
(resolve-context clauses))
|
|
1738
|
+
negation-join-rel (reduce hash-join (:rels negation-context))
|
|
1739
|
+
negation (subtract-rel join-rel negation-join-rel)]
|
|
1740
|
+
(cond-> (assoc context :rels [negation])
|
|
1741
|
+
(:stats context) (assoc :tmp-stats {:type :not
|
|
1742
|
+
:branches (:stats negation-context)})))
|
|
1743
|
+
|
|
1744
|
+
'[not-join [*] *] ;; (not-join [vars] ...)
|
|
1745
|
+
(let [[_ vars & clauses] clause
|
|
1746
|
+
_ (check-all-bound context vars orig-clause)
|
|
1747
|
+
join-rel (reduce hash-join (:rels context))
|
|
1748
|
+
negation-context (-> context
|
|
1749
|
+
(assoc :rels [join-rel])
|
|
1750
|
+
(assoc :stats [])
|
|
1751
|
+
(limit-context vars)
|
|
1752
|
+
(resolve-context clauses)
|
|
1753
|
+
(limit-context vars))
|
|
1754
|
+
negation-join-rel (reduce hash-join (:rels negation-context))
|
|
1755
|
+
negation (subtract-rel join-rel negation-join-rel)]
|
|
1756
|
+
(cond-> (assoc context :rels [negation])
|
|
1757
|
+
(:stats context) (assoc :tmp-stats {:type :not
|
|
1758
|
+
:branches (:stats negation-context)})))
|
|
1759
|
+
|
|
1760
|
+
'[*] ;; pattern
|
|
1761
|
+
(let [source *implicit-source*
|
|
1762
|
+
pattern0 (replace (:consts context) clause)
|
|
1763
|
+
pattern1 (resolve-pattern-lookup-refs source pattern0)]
|
|
1764
|
+
(lookup-batch-search source context clause pattern1)))))
|
|
1765
|
+
|
|
1766
|
+
(defn -resolve-clause
|
|
1767
|
+
([context clause]
|
|
1768
|
+
(-resolve-clause context clause clause))
|
|
1769
|
+
([context clause orig-clause]
|
|
1770
|
+
(dqs/update-ctx-with-stats context orig-clause
|
|
1771
|
+
(fn [context]
|
|
1772
|
+
(-resolve-clause* context clause orig-clause)))))
|
|
1773
|
+
|
|
1774
|
+
(defn resolve-clause [context clause]
|
|
1775
|
+
(if (rule? context clause)
|
|
1776
|
+
(if (source? (first clause))
|
|
1777
|
+
(binding [*implicit-source* (get (:sources context) (first clause))]
|
|
1778
|
+
(resolve-clause context (next clause)))
|
|
1779
|
+
(dqs/update-ctx-with-stats context clause
|
|
1780
|
+
(fn [context] (solve-rule context clause))))
|
|
1781
|
+
(-resolve-clause context clause)))
|
|
1782
|
+
|
|
1783
|
+
(defn -q [context clauses]
|
|
1784
|
+
(binding [*implicit-source* (get (:sources context) '$)]
|
|
1785
|
+
(dt/resolve-clauses resolve-clause context clauses)))
|
|
1786
|
+
|
|
1787
|
+
(defn -collect
|
|
1788
|
+
([context symbols]
|
|
1789
|
+
(let [rels (:rels context)
|
|
1790
|
+
start-array (to-array (map #(get (:consts context) %) symbols))]
|
|
1791
|
+
(-collect [start-array] rels symbols)))
|
|
1792
|
+
([acc rels symbols]
|
|
1793
|
+
(if-some [rel (first rels)]
|
|
1794
|
+
(let [keep-attrs (select-keys (:attrs rel) symbols)]
|
|
1795
|
+
(if (empty? keep-attrs)
|
|
1796
|
+
(recur acc (next rels) symbols)
|
|
1797
|
+
(let [copy-map (to-array (map #(get keep-attrs %) symbols))
|
|
1798
|
+
len (count symbols)]
|
|
1799
|
+
(recur (for [#?(:cljs t1
|
|
1800
|
+
:clj ^{:tag "[[Ljava.lang.Object;"} t1) acc
|
|
1801
|
+
t2 (:tuples rel)]
|
|
1802
|
+
(let [res (aclone t1)]
|
|
1803
|
+
(dotimes [i len]
|
|
1804
|
+
(when-some [idx (aget copy-map i)]
|
|
1805
|
+
(aset res i (get t2 idx))
|
|
1806
|
+
;; TODO figure out why this array lookup does not work anymore (returns nil)
|
|
1807
|
+
#_(aset res i (#?(:cljs da/aget :clj get) t2 idx))))
|
|
1808
|
+
res))
|
|
1809
|
+
(next rels)
|
|
1810
|
+
symbols))))
|
|
1811
|
+
acc)))
|
|
1812
|
+
|
|
1813
|
+
(defprotocol IContextResolve
|
|
1814
|
+
(-context-resolve [var context]))
|
|
1815
|
+
|
|
1816
|
+
(extend-protocol IContextResolve
|
|
1817
|
+
Variable
|
|
1818
|
+
(-context-resolve [var context]
|
|
1819
|
+
(context-resolve-val context (.-symbol var)))
|
|
1820
|
+
SrcVar
|
|
1821
|
+
(-context-resolve [var context]
|
|
1822
|
+
(get-in context [:sources (.-symbol var)]))
|
|
1823
|
+
PlainSymbol
|
|
1824
|
+
(-context-resolve [var _]
|
|
1825
|
+
(or (get built-in-aggregates (.-symbol var))
|
|
1826
|
+
(resolve-sym (.-symbol var))))
|
|
1827
|
+
Constant
|
|
1828
|
+
(-context-resolve [var _]
|
|
1829
|
+
(.-value var)))
|
|
1830
|
+
|
|
1831
|
+
(defn -aggregate [find-elements context tuples]
|
|
1832
|
+
(mapv (fn [element fixed-value i]
|
|
1833
|
+
(if (instance? Aggregate element)
|
|
1834
|
+
(let [f (-context-resolve (:fn element) context)
|
|
1835
|
+
args (map #(-context-resolve % context) (butlast (:args element)))
|
|
1836
|
+
vals (map #(nth % i) tuples)]
|
|
1837
|
+
(apply f (concat args [vals])))
|
|
1838
|
+
fixed-value))
|
|
1839
|
+
find-elements
|
|
1840
|
+
(first tuples)
|
|
1841
|
+
(range)))
|
|
1842
|
+
|
|
1843
|
+
(defn- idxs-of [pred coll]
|
|
1844
|
+
(->> (map #(when (pred %1) %2) coll (range))
|
|
1845
|
+
(remove nil?)))
|
|
1846
|
+
|
|
1847
|
+
(defn aggregate [find-elements context resultset]
|
|
1848
|
+
(let [group-idxs (idxs-of (complement #(instance? Aggregate %)) find-elements)
|
|
1849
|
+
group-fn (fn [tuple]
|
|
1850
|
+
(map #(nth tuple %) group-idxs))
|
|
1851
|
+
grouped (group-by group-fn resultset)]
|
|
1852
|
+
(for [[_ tuples] grouped]
|
|
1853
|
+
(-aggregate find-elements context tuples))))
|
|
1854
|
+
|
|
1855
|
+
(defprotocol IPostProcess
|
|
1856
|
+
(-post-process [find tuples]))
|
|
1857
|
+
|
|
1858
|
+
(extend-protocol IPostProcess
|
|
1859
|
+
FindRel
|
|
1860
|
+
(-post-process [_ tuples]
|
|
1861
|
+
(if (seq? tuples) (vec tuples) tuples))
|
|
1862
|
+
FindColl
|
|
1863
|
+
(-post-process [_ tuples]
|
|
1864
|
+
(into [] (map first) tuples))
|
|
1865
|
+
FindScalar
|
|
1866
|
+
(-post-process [_ tuples]
|
|
1867
|
+
(ffirst tuples))
|
|
1868
|
+
FindTuple
|
|
1869
|
+
(-post-process [_ tuples]
|
|
1870
|
+
(first tuples)))
|
|
1871
|
+
|
|
1872
|
+
(defn- pull [find-elements context resultset]
|
|
1873
|
+
(let [resolved (for [find find-elements]
|
|
1874
|
+
(when (instance? Pull find)
|
|
1875
|
+
[(-context-resolve (:source find) context)
|
|
1876
|
+
(dpp/parse-pull
|
|
1877
|
+
(-context-resolve (:pattern find) context))]))]
|
|
1878
|
+
(for [tuple resultset]
|
|
1879
|
+
(mapv (fn [env el]
|
|
1880
|
+
(if env
|
|
1881
|
+
(let [[src spec] env]
|
|
1882
|
+
(dpa/pull-spec src spec [el] false))
|
|
1883
|
+
el))
|
|
1884
|
+
resolved
|
|
1885
|
+
tuple))))
|
|
1886
|
+
|
|
1887
|
+
(def ^:private query-cache (volatile! (datahike.lru/lru lru-cache-size)))
|
|
1888
|
+
|
|
1889
|
+
(defn memoized-parse-query [q]
|
|
1890
|
+
(if-some [cached (get @query-cache q nil)]
|
|
1891
|
+
cached
|
|
1892
|
+
(let [qp (parse q)]
|
|
1893
|
+
(vswap! query-cache assoc q qp)
|
|
1894
|
+
qp)))
|
|
1895
|
+
|
|
1896
|
+
(defn convert-to-return-maps [{:keys [mapping-type mapping-keys]} resultset]
|
|
1897
|
+
(let [mapping-keys (map #(get % :mapping-key) mapping-keys)
|
|
1898
|
+
convert-fn (fn [mkeys]
|
|
1899
|
+
(mapv #(zipmap mkeys %) resultset))]
|
|
1900
|
+
(condp = mapping-type
|
|
1901
|
+
:keys (convert-fn (map keyword mapping-keys))
|
|
1902
|
+
:strs (convert-fn (map str mapping-keys))
|
|
1903
|
+
:syms (convert-fn (map symbol mapping-keys)))))
|
|
1904
|
+
|
|
1905
|
+
(defn collect [context symbols]
|
|
1906
|
+
(->> (-collect context symbols)
|
|
1907
|
+
(map vec)))
|
|
1908
|
+
|
|
1909
|
+
(def default-settings {})
|
|
1910
|
+
|
|
1911
|
+
(defn raw-q [{:keys [query args offset limit stats? settings] :as _query-map}]
|
|
1912
|
+
(let [settings (merge default-settings settings)
|
|
1913
|
+
{:keys [qfind
|
|
1914
|
+
qwith
|
|
1915
|
+
qreturnmaps
|
|
1916
|
+
qin]} (memoized-parse-query query)
|
|
1917
|
+
context-in (-> (if stats?
|
|
1918
|
+
(StatContext. [] {} {} {} [] settings)
|
|
1919
|
+
(Context. [] {} {} {} settings))
|
|
1920
|
+
(resolve-ins qin args))
|
|
1921
|
+
;; TODO utilize parser
|
|
1922
|
+
|
|
1923
|
+
all-vars (concat (dpi/find-vars qfind) (map :symbol qwith))
|
|
1924
|
+
context-out (-q context-in (:where query))
|
|
1925
|
+
resultset (collect context-out all-vars)
|
|
1926
|
+
find-elements (dpip/find-elements qfind)
|
|
1927
|
+
result-arity (count find-elements)]
|
|
1928
|
+
(cond->> (into #{}
|
|
1929
|
+
(comp (distinct)
|
|
1930
|
+
(if offset
|
|
1931
|
+
(drop offset)
|
|
1932
|
+
identity)
|
|
1933
|
+
(if (or (nil? limit) (neg? limit))
|
|
1934
|
+
identity
|
|
1935
|
+
(take limit)))
|
|
1936
|
+
resultset)
|
|
1937
|
+
(:with query) (mapv #(subvec % 0 result-arity))
|
|
1938
|
+
(some #(instance? Aggregate %) find-elements) (aggregate find-elements context-in)
|
|
1939
|
+
(some #(instance? Pull %) find-elements) (pull find-elements context-in)
|
|
1940
|
+
true (-post-process qfind)
|
|
1941
|
+
qreturnmaps (convert-to-return-maps qreturnmaps)
|
|
1942
|
+
stats? (#(-> context-out
|
|
1943
|
+
(dissoc :rels :sources :settings)
|
|
1944
|
+
(assoc :ret %
|
|
1945
|
+
:query query))))))
|