lora-python 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {lora_python-0.2.0 → lora_python-0.3.0}/Cargo.lock +36 -13
- {lora_python-0.2.0 → lora_python-0.3.0}/Cargo.toml +10 -8
- {lora_python-0.2.0 → lora_python-0.3.0}/PKG-INFO +1 -1
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/src/analyzer.rs +3 -3
- lora_python-0.3.0/crates/lora-database/src/database.rs +301 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/src/lib.rs +5 -1
- lora_python-0.3.0/crates/lora-database/tests/backend_stub.rs +388 -0
- lora_python-0.3.0/crates/lora-database/tests/snapshot.rs +195 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/vectors.rs +1 -1
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-executor/src/eval.rs +85 -84
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-executor/src/executor.rs +139 -102
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/src/lib.rs +49 -17
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-store/Cargo.toml +4 -0
- lora_python-0.3.0/crates/lora-store/src/graph.rs +837 -0
- lora_python-0.3.0/crates/lora-store/src/lib.rs +18 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-store/src/memory.rs +588 -161
- lora_python-0.3.0/crates/lora-store/src/mutation.rs +115 -0
- lora_python-0.3.0/crates/lora-store/src/snapshot.rs +441 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-store/src/spatial.rs +1 -1
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-store/src/temporal.rs +6 -6
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-store/src/vector.rs +5 -3
- {lora_python-0.2.0 → lora_python-0.3.0}/pyproject.toml +1 -1
- {lora_python-0.2.0 → lora_python-0.3.0}/python/lora_python/__init__.py +2 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/python/lora_python/_async.py +20 -1
- {lora_python-0.2.0 → lora_python-0.3.0}/python/lora_python/types.py +18 -0
- lora_python-0.2.0/crates/lora-database/src/database.rs +0 -105
- lora_python-0.2.0/crates/lora-store/src/graph.rs +0 -649
- lora_python-0.2.0/crates/lora-store/src/lib.rs +0 -11
- {lora_python-0.2.0 → lora_python-0.3.0}/LICENSE +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/README.md +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/src/errors.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/src/lib.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/src/resolved.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/src/scope.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-analyzer/src/symbols.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-ast/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-ast/src/ast.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-ast/src/lib.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/src/lib.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/src/logical.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/src/optimizer.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/src/pattern.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/src/physical.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-compiler/src/planner.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/advanced_benchmarks.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/engine_benchmarks.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/fixtures.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/perf_smoke_baseline.json +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/perf_smoke_benchmarks.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/scale_benchmarks.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/benches/temporal_spatial_benchmarks.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/advanced_queries.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/aggregation.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/create.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/errors.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/expressions.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/functions_extended.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/invariants.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/match.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/merge.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/ordering.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/parameters.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/parser.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/paths.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/projection.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/seeds.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/spatial.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/temporal.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/test_helpers.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/types_advanced.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/union.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/update.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/where_clause.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-database/tests/with.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-executor/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-executor/src/errors.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-executor/src/lib.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-executor/src/value.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-parser/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-parser/src/cypher.pest +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-parser/src/error.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-parser/src/lib.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-parser/src/parser.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/.gitignore +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/Cargo.toml +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/LICENSE +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/README.md +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/build.rs +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/examples/async_demo.py +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/examples/basic.py +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/tests/test_async.py +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/crates/lora-python/tests/test_sync.py +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/python/lora_python/_native.pyi +0 -0
- {lora_python-0.2.0 → lora_python-0.3.0}/python/lora_python/py.typed +0 -0
|
@@ -116,6 +116,15 @@ dependencies = [
|
|
|
116
116
|
"tracing",
|
|
117
117
|
]
|
|
118
118
|
|
|
119
|
+
[[package]]
|
|
120
|
+
name = "bincode"
|
|
121
|
+
version = "1.3.3"
|
|
122
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
123
|
+
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
|
124
|
+
dependencies = [
|
|
125
|
+
"serde",
|
|
126
|
+
]
|
|
127
|
+
|
|
119
128
|
[[package]]
|
|
120
129
|
name = "bindgen"
|
|
121
130
|
version = "0.72.1"
|
|
@@ -283,6 +292,15 @@ dependencies = [
|
|
|
283
292
|
"libc",
|
|
284
293
|
]
|
|
285
294
|
|
|
295
|
+
[[package]]
|
|
296
|
+
name = "crc32fast"
|
|
297
|
+
version = "1.5.0"
|
|
298
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
299
|
+
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
|
300
|
+
dependencies = [
|
|
301
|
+
"cfg-if",
|
|
302
|
+
]
|
|
303
|
+
|
|
286
304
|
[[package]]
|
|
287
305
|
name = "criterion"
|
|
288
306
|
version = "0.8.2"
|
|
@@ -611,7 +629,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
|
611
629
|
|
|
612
630
|
[[package]]
|
|
613
631
|
name = "lora-analyzer"
|
|
614
|
-
version = "0.
|
|
632
|
+
version = "0.3.0"
|
|
615
633
|
dependencies = [
|
|
616
634
|
"lora-ast",
|
|
617
635
|
"lora-parser",
|
|
@@ -621,14 +639,14 @@ dependencies = [
|
|
|
621
639
|
|
|
622
640
|
[[package]]
|
|
623
641
|
name = "lora-ast"
|
|
624
|
-
version = "0.
|
|
642
|
+
version = "0.3.0"
|
|
625
643
|
dependencies = [
|
|
626
644
|
"smallvec 2.0.0-alpha.12",
|
|
627
645
|
]
|
|
628
646
|
|
|
629
647
|
[[package]]
|
|
630
648
|
name = "lora-compiler"
|
|
631
|
-
version = "0.
|
|
649
|
+
version = "0.3.0"
|
|
632
650
|
dependencies = [
|
|
633
651
|
"lora-analyzer",
|
|
634
652
|
"lora-ast",
|
|
@@ -636,7 +654,7 @@ dependencies = [
|
|
|
636
654
|
|
|
637
655
|
[[package]]
|
|
638
656
|
name = "lora-database"
|
|
639
|
-
version = "0.
|
|
657
|
+
version = "0.3.0"
|
|
640
658
|
dependencies = [
|
|
641
659
|
"anyhow",
|
|
642
660
|
"criterion",
|
|
@@ -652,7 +670,7 @@ dependencies = [
|
|
|
652
670
|
|
|
653
671
|
[[package]]
|
|
654
672
|
name = "lora-executor"
|
|
655
|
-
version = "0.
|
|
673
|
+
version = "0.3.0"
|
|
656
674
|
dependencies = [
|
|
657
675
|
"lora-analyzer",
|
|
658
676
|
"lora-ast",
|
|
@@ -666,7 +684,7 @@ dependencies = [
|
|
|
666
684
|
|
|
667
685
|
[[package]]
|
|
668
686
|
name = "lora-ffi"
|
|
669
|
-
version = "0.
|
|
687
|
+
version = "0.3.0"
|
|
670
688
|
dependencies = [
|
|
671
689
|
"lora-database",
|
|
672
690
|
"lora-store",
|
|
@@ -676,7 +694,7 @@ dependencies = [
|
|
|
676
694
|
|
|
677
695
|
[[package]]
|
|
678
696
|
name = "lora-node"
|
|
679
|
-
version = "0.
|
|
697
|
+
version = "0.3.0"
|
|
680
698
|
dependencies = [
|
|
681
699
|
"anyhow",
|
|
682
700
|
"lora-database",
|
|
@@ -690,7 +708,7 @@ dependencies = [
|
|
|
690
708
|
|
|
691
709
|
[[package]]
|
|
692
710
|
name = "lora-parser"
|
|
693
|
-
version = "0.
|
|
711
|
+
version = "0.3.0"
|
|
694
712
|
dependencies = [
|
|
695
713
|
"lora-ast",
|
|
696
714
|
"pest",
|
|
@@ -701,7 +719,7 @@ dependencies = [
|
|
|
701
719
|
|
|
702
720
|
[[package]]
|
|
703
721
|
name = "lora-python"
|
|
704
|
-
version = "0.
|
|
722
|
+
version = "0.3.0"
|
|
705
723
|
dependencies = [
|
|
706
724
|
"anyhow",
|
|
707
725
|
"lora-database",
|
|
@@ -713,10 +731,11 @@ dependencies = [
|
|
|
713
731
|
|
|
714
732
|
[[package]]
|
|
715
733
|
name = "lora-server"
|
|
716
|
-
version = "0.
|
|
734
|
+
version = "0.3.0"
|
|
717
735
|
dependencies = [
|
|
718
736
|
"anyhow",
|
|
719
737
|
"axum",
|
|
738
|
+
"http-body-util",
|
|
720
739
|
"lora-database",
|
|
721
740
|
"serde",
|
|
722
741
|
"serde_json",
|
|
@@ -726,15 +745,19 @@ dependencies = [
|
|
|
726
745
|
|
|
727
746
|
[[package]]
|
|
728
747
|
name = "lora-store"
|
|
729
|
-
version = "0.
|
|
748
|
+
version = "0.3.0"
|
|
730
749
|
dependencies = [
|
|
750
|
+
"bincode",
|
|
751
|
+
"crc32fast",
|
|
731
752
|
"js-sys",
|
|
732
753
|
"lora-ast",
|
|
754
|
+
"serde",
|
|
755
|
+
"thiserror",
|
|
733
756
|
]
|
|
734
757
|
|
|
735
758
|
[[package]]
|
|
736
759
|
name = "lora-wasm"
|
|
737
|
-
version = "0.
|
|
760
|
+
version = "0.3.0"
|
|
738
761
|
dependencies = [
|
|
739
762
|
"anyhow",
|
|
740
763
|
"console_error_panic_hook",
|
|
@@ -749,7 +772,7 @@ dependencies = [
|
|
|
749
772
|
|
|
750
773
|
[[package]]
|
|
751
774
|
name = "lora_ruby"
|
|
752
|
-
version = "0.
|
|
775
|
+
version = "0.3.0"
|
|
753
776
|
dependencies = [
|
|
754
777
|
"anyhow",
|
|
755
778
|
"lora-database",
|
|
@@ -4,7 +4,7 @@ resolver = "2"
|
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
6
|
edition = "2021"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
license = "BUSL-1.1"
|
|
9
9
|
authors = ["LoraDB, Inc."]
|
|
10
10
|
repository = "https://github.com/lora-db/lora"
|
|
@@ -15,13 +15,13 @@ rust-version = "1.87"
|
|
|
15
15
|
# Internal crates — versions are kept in lockstep with [workspace.package].version
|
|
16
16
|
# by `scripts/sync-versions.mjs`. Both `path` and `version` are set so that
|
|
17
17
|
# `cargo publish` works (crates.io cannot resolve path-only deps).
|
|
18
|
-
lora-ast = { path = "crates/lora-ast", version = "=0.
|
|
19
|
-
lora-parser = { path = "crates/lora-parser", version = "=0.
|
|
20
|
-
lora-analyzer = { path = "crates/lora-analyzer", version = "=0.
|
|
21
|
-
lora-compiler = { path = "crates/lora-compiler", version = "=0.
|
|
22
|
-
lora-store = { path = "crates/lora-store", version = "=0.
|
|
23
|
-
lora-executor = { path = "crates/lora-executor", version = "=0.
|
|
24
|
-
lora-database = { path = "crates/lora-database", version = "=0.
|
|
18
|
+
lora-ast = { path = "crates/lora-ast", version = "=0.3.0" }
|
|
19
|
+
lora-parser = { path = "crates/lora-parser", version = "=0.3.0" }
|
|
20
|
+
lora-analyzer = { path = "crates/lora-analyzer", version = "=0.3.0" }
|
|
21
|
+
lora-compiler = { path = "crates/lora-compiler", version = "=0.3.0" }
|
|
22
|
+
lora-store = { path = "crates/lora-store", version = "=0.3.0" }
|
|
23
|
+
lora-executor = { path = "crates/lora-executor", version = "=0.3.0" }
|
|
24
|
+
lora-database = { path = "crates/lora-database", version = "=0.3.0" }
|
|
25
25
|
|
|
26
26
|
# External crates.
|
|
27
27
|
anyhow = "1"
|
|
@@ -37,3 +37,5 @@ serde_json = "1.0.149"
|
|
|
37
37
|
regex = "1"
|
|
38
38
|
tower = "0.5.3"
|
|
39
39
|
criterion = { version = "0.8.2", features = ["html_reports"] }
|
|
40
|
+
bincode = "1.3"
|
|
41
|
+
crc32fast = "1.4"
|
|
@@ -5,10 +5,10 @@ use lora_ast::{
|
|
|
5
5
|
ReadingClause, RelationshipPattern, Remove, RemoveItem, Return, Set, SetItem, SinglePartQuery,
|
|
6
6
|
SingleQuery, Statement, Unwind, UpdatingClause, With,
|
|
7
7
|
};
|
|
8
|
-
use lora_store::
|
|
8
|
+
use lora_store::GraphCatalog;
|
|
9
9
|
use std::collections::{BTreeMap, BTreeSet};
|
|
10
10
|
|
|
11
|
-
pub struct Analyzer<'a, S:
|
|
11
|
+
pub struct Analyzer<'a, S: GraphCatalog + ?Sized> {
|
|
12
12
|
storage: &'a S,
|
|
13
13
|
scopes: ScopeStack,
|
|
14
14
|
symbols: SymbolTable,
|
|
@@ -22,7 +22,7 @@ enum PatternContext {
|
|
|
22
22
|
Write,
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
impl<'a, S:
|
|
25
|
+
impl<'a, S: GraphCatalog + ?Sized> Analyzer<'a, S> {
|
|
26
26
|
pub fn new(storage: &'a S) -> Self {
|
|
27
27
|
Self {
|
|
28
28
|
storage,
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
use std::collections::BTreeMap;
|
|
2
|
+
use std::fs::{File, OpenOptions};
|
|
3
|
+
use std::io::{BufReader, BufWriter};
|
|
4
|
+
use std::path::{Path, PathBuf};
|
|
5
|
+
use std::sync::{Arc, Mutex, MutexGuard};
|
|
6
|
+
|
|
7
|
+
use anyhow::Result;
|
|
8
|
+
use lora_analyzer::Analyzer;
|
|
9
|
+
use lora_ast::Document;
|
|
10
|
+
use lora_compiler::{CompiledQuery, Compiler};
|
|
11
|
+
use lora_executor::{
|
|
12
|
+
ExecuteOptions, LoraValue, MutableExecutionContext, MutableExecutor, QueryResult,
|
|
13
|
+
};
|
|
14
|
+
use lora_parser::parse_query;
|
|
15
|
+
use lora_store::{GraphStorage, GraphStorageMut, InMemoryGraph, SnapshotMeta, Snapshotable};
|
|
16
|
+
|
|
17
|
+
/// Minimal abstraction any transport can depend on to run Lora queries.
|
|
18
|
+
pub trait QueryRunner: Send + Sync + 'static {
|
|
19
|
+
fn execute(&self, query: &str, options: Option<ExecuteOptions>) -> Result<QueryResult>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Owns the graph store and orchestrates parse → analyze → compile → execute.
|
|
23
|
+
pub struct Database<S> {
|
|
24
|
+
store: Arc<Mutex<S>>,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl Database<InMemoryGraph> {
|
|
28
|
+
/// Convenience constructor: a fresh, empty in-memory graph database.
|
|
29
|
+
pub fn in_memory() -> Self {
|
|
30
|
+
Self::from_graph(InMemoryGraph::new())
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl<S> Database<S>
|
|
35
|
+
where
|
|
36
|
+
S: GraphStorage + GraphStorageMut,
|
|
37
|
+
{
|
|
38
|
+
/// Build a database from a pre-wrapped, shared store.
|
|
39
|
+
pub fn new(store: Arc<Mutex<S>>) -> Self {
|
|
40
|
+
Self { store }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Build a database by taking ownership of a bare graph store.
|
|
44
|
+
pub fn from_graph(graph: S) -> Self {
|
|
45
|
+
Self::new(Arc::new(Mutex::new(graph)))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Handle to the underlying shared store — useful for callers that need
|
|
49
|
+
/// to snapshot or share the graph across multiple databases.
|
|
50
|
+
pub fn store(&self) -> &Arc<Mutex<S>> {
|
|
51
|
+
&self.store
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Parse a query string into an AST without executing it.
|
|
55
|
+
pub fn parse(&self, query: &str) -> Result<Document> {
|
|
56
|
+
Ok(parse_query(query)?)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn lock_store(&self) -> MutexGuard<'_, S> {
|
|
60
|
+
self.store
|
|
61
|
+
.lock()
|
|
62
|
+
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn compile_query(&self, query: &str) -> Result<(MutexGuard<'_, S>, CompiledQuery)> {
|
|
66
|
+
let document = self.parse(query)?;
|
|
67
|
+
let store = self.lock_store();
|
|
68
|
+
|
|
69
|
+
let resolved = {
|
|
70
|
+
let mut analyzer = Analyzer::new(&*store);
|
|
71
|
+
analyzer.analyze(&document)?
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let compiled = Compiler::compile(&resolved);
|
|
75
|
+
Ok((store, compiled))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// Execute a query and return its result.
|
|
79
|
+
pub fn execute(&self, query: &str, options: Option<ExecuteOptions>) -> Result<QueryResult> {
|
|
80
|
+
self.execute_with_params(query, options, BTreeMap::new())
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Execute a query with bound parameters.
|
|
84
|
+
pub fn execute_with_params(
|
|
85
|
+
&self,
|
|
86
|
+
query: &str,
|
|
87
|
+
options: Option<ExecuteOptions>,
|
|
88
|
+
params: BTreeMap<String, LoraValue>,
|
|
89
|
+
) -> Result<QueryResult> {
|
|
90
|
+
let (mut store, compiled) = self.compile_query(query)?;
|
|
91
|
+
|
|
92
|
+
let mut executor = MutableExecutor::new(MutableExecutionContext {
|
|
93
|
+
storage: &mut *store,
|
|
94
|
+
params,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
Ok(executor.execute_compiled(&compiled, options)?)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------- Storage-agnostic utility helpers ----------
|
|
101
|
+
//
|
|
102
|
+
// Bindings previously reached into `Arc<Mutex<InMemoryGraph>>` to answer
|
|
103
|
+
// stat / admin calls; these helpers let them depend on `Database<S>`
|
|
104
|
+
// instead, so swapping in a new backend only requires changing one type
|
|
105
|
+
// parameter.
|
|
106
|
+
|
|
107
|
+
/// Drop every node and relationship.
|
|
108
|
+
pub fn clear(&self) {
|
|
109
|
+
let mut guard = self.lock_store();
|
|
110
|
+
guard.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Number of nodes currently in the graph.
|
|
114
|
+
pub fn node_count(&self) -> usize {
|
|
115
|
+
let guard = self.lock_store();
|
|
116
|
+
guard.node_count()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Number of relationships currently in the graph.
|
|
120
|
+
pub fn relationship_count(&self) -> usize {
|
|
121
|
+
let guard = self.lock_store();
|
|
122
|
+
guard.relationship_count()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Run a closure with a shared borrow of the underlying store. Used by
|
|
126
|
+
/// bindings to answer ad-hoc queries without locking the mutex themselves.
|
|
127
|
+
pub fn with_store<R>(&self, f: impl FnOnce(&S) -> R) -> R {
|
|
128
|
+
let guard = self.lock_store();
|
|
129
|
+
f(&*guard)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Run a closure with an exclusive borrow of the underlying store. Reserved
|
|
133
|
+
/// for admin paths (restore, bulk load); regular mutation goes through
|
|
134
|
+
/// `execute_with_params`.
|
|
135
|
+
pub fn with_store_mut<R>(&self, f: impl FnOnce(&mut S) -> R) -> R {
|
|
136
|
+
let mut guard = self.lock_store();
|
|
137
|
+
f(&mut *guard)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Snapshot helpers
|
|
143
|
+
//
|
|
144
|
+
// A second impl block so the `Snapshotable` bound only constrains backends
|
|
145
|
+
// that actually need it. `Database<InMemoryGraph>` picks these up
|
|
146
|
+
// automatically; hypothetical backends that don't implement `Snapshotable`
|
|
147
|
+
// still get the core query API above.
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
impl<S> Database<S>
|
|
151
|
+
where
|
|
152
|
+
S: GraphStorage + GraphStorageMut + Snapshotable,
|
|
153
|
+
{
|
|
154
|
+
/// Serialize the current graph state to the given path. Writes are
|
|
155
|
+
/// atomic: the payload goes to `<path>.tmp`, is `fsync`'d, and then
|
|
156
|
+
/// renamed over the target; a torn write can never leave a half-written
|
|
157
|
+
/// file at `path`. If any step before the rename fails, the stale
|
|
158
|
+
/// `<path>.tmp` is removed so a crashed save never leaks scratch files.
|
|
159
|
+
///
|
|
160
|
+
/// Holds the store mutex for the duration of the save so concurrent
|
|
161
|
+
/// queries see a consistent point-in-time snapshot.
|
|
162
|
+
pub fn save_snapshot_to(&self, path: impl AsRef<Path>) -> Result<SnapshotMeta> {
|
|
163
|
+
let path = path.as_ref();
|
|
164
|
+
let tmp = snapshot_tmp_path(path);
|
|
165
|
+
|
|
166
|
+
// Acquire the lock once so the snapshot is point-in-time consistent.
|
|
167
|
+
let guard = self.lock_store();
|
|
168
|
+
|
|
169
|
+
let file = OpenOptions::new()
|
|
170
|
+
.write(true)
|
|
171
|
+
.create(true)
|
|
172
|
+
.truncate(true)
|
|
173
|
+
.open(&tmp)?;
|
|
174
|
+
// Arm cleanup immediately after `open` succeeds: every early return
|
|
175
|
+
// below must either surface an error *and* unlink the tmp, or commit
|
|
176
|
+
// the guard once the rename takes effect.
|
|
177
|
+
let tmp_guard = TempFileGuard::new(tmp.clone());
|
|
178
|
+
let mut writer = BufWriter::new(file);
|
|
179
|
+
|
|
180
|
+
let meta = guard.save_snapshot(&mut writer)?;
|
|
181
|
+
|
|
182
|
+
// Flush the BufWriter before fsync; otherwise we fsync an empty
|
|
183
|
+
// underlying file.
|
|
184
|
+
use std::io::Write;
|
|
185
|
+
writer.flush()?;
|
|
186
|
+
let file = writer.into_inner().map_err(|e| e.into_error())?;
|
|
187
|
+
file.sync_all()?;
|
|
188
|
+
drop(file);
|
|
189
|
+
|
|
190
|
+
std::fs::rename(&tmp, path)?;
|
|
191
|
+
// The tmp path no longer has a file behind it — disarm the guard so
|
|
192
|
+
// it doesn't try to remove the just-renamed target by name race.
|
|
193
|
+
tmp_guard.commit();
|
|
194
|
+
|
|
195
|
+
// Best-effort parent-dir fsync so the rename itself is durable on
|
|
196
|
+
// power loss. Non-fatal if the parent can't be opened.
|
|
197
|
+
if let Some(parent) = path.parent() {
|
|
198
|
+
if let Ok(dir) = File::open(parent) {
|
|
199
|
+
let _ = dir.sync_all();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Ok(meta)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// Replace the current graph state with a snapshot loaded from `path`.
|
|
207
|
+
/// Holds the store mutex for the duration of the load; concurrent
|
|
208
|
+
/// queries block until restore completes.
|
|
209
|
+
pub fn load_snapshot_from(&self, path: impl AsRef<Path>) -> Result<SnapshotMeta> {
|
|
210
|
+
let file = File::open(path.as_ref())?;
|
|
211
|
+
let reader = BufReader::new(file);
|
|
212
|
+
|
|
213
|
+
let mut guard = self.lock_store();
|
|
214
|
+
Ok(guard.load_snapshot(reader)?)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
impl Database<InMemoryGraph> {
|
|
219
|
+
/// Convenience constructor: open (or create) an empty in-memory database
|
|
220
|
+
/// and immediately restore it from `path`. Errors if the file cannot be
|
|
221
|
+
/// opened or the snapshot is malformed.
|
|
222
|
+
pub fn in_memory_from_snapshot(path: impl AsRef<Path>) -> Result<Self> {
|
|
223
|
+
let db = Self::in_memory();
|
|
224
|
+
db.load_snapshot_from(path)?;
|
|
225
|
+
Ok(db)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
fn snapshot_tmp_path(target: &Path) -> PathBuf {
|
|
230
|
+
let mut tmp = target.as_os_str().to_owned();
|
|
231
|
+
tmp.push(".tmp");
|
|
232
|
+
PathBuf::from(tmp)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// RAII handle that deletes its path on drop unless [`commit`] is called.
|
|
236
|
+
///
|
|
237
|
+
/// The snapshot save path creates `<target>.tmp` before the payload is
|
|
238
|
+
/// written; if any step between then and the final rename fails (or the
|
|
239
|
+
/// thread unwinds), the guard's `Drop` removes the scratch file so a crashed
|
|
240
|
+
/// save never leaves leftovers on disk.
|
|
241
|
+
///
|
|
242
|
+
/// [`commit`]: Self::commit
|
|
243
|
+
struct TempFileGuard {
|
|
244
|
+
path: Option<PathBuf>,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
impl TempFileGuard {
|
|
248
|
+
fn new(path: PathBuf) -> Self {
|
|
249
|
+
Self { path: Some(path) }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Disarm the guard. Call this once the tmp file's contents have been
|
|
253
|
+
/// handed off (e.g. renamed to their final destination) so the `Drop`
|
|
254
|
+
/// impl does not try to remove them.
|
|
255
|
+
fn commit(mut self) {
|
|
256
|
+
self.path.take();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
impl Drop for TempFileGuard {
|
|
261
|
+
fn drop(&mut self) {
|
|
262
|
+
if let Some(path) = self.path.take() {
|
|
263
|
+
// Best-effort: cleanup failure is not worth surfacing — the
|
|
264
|
+
// worst case is a leaked scratch file that the next save
|
|
265
|
+
// overwrites via `OpenOptions::truncate(true)`.
|
|
266
|
+
let _ = std::fs::remove_file(path);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/// Storage-agnostic admin surface for HTTP / binding callers that want to
|
|
272
|
+
/// drive snapshot operations without naming the backend type parameter.
|
|
273
|
+
///
|
|
274
|
+
/// `Database<S>` picks up a blanket impl when `S: Snapshotable + 'static`.
|
|
275
|
+
/// Transports (e.g. `lora-server`) type-erase on `Arc<dyn SnapshotAdmin>`.
|
|
276
|
+
pub trait SnapshotAdmin: Send + Sync + 'static {
|
|
277
|
+
fn save_snapshot(&self, path: &Path) -> Result<SnapshotMeta>;
|
|
278
|
+
fn load_snapshot(&self, path: &Path) -> Result<SnapshotMeta>;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
impl<S> SnapshotAdmin for Database<S>
|
|
282
|
+
where
|
|
283
|
+
S: GraphStorage + GraphStorageMut + Snapshotable + Send + 'static,
|
|
284
|
+
{
|
|
285
|
+
fn save_snapshot(&self, path: &Path) -> Result<SnapshotMeta> {
|
|
286
|
+
self.save_snapshot_to(path)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
fn load_snapshot(&self, path: &Path) -> Result<SnapshotMeta> {
|
|
290
|
+
self.load_snapshot_from(path)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
impl<S> QueryRunner for Database<S>
|
|
295
|
+
where
|
|
296
|
+
S: GraphStorage + GraphStorageMut + Send + 'static,
|
|
297
|
+
{
|
|
298
|
+
fn execute(&self, query: &str, options: Option<ExecuteOptions>) -> Result<QueryResult> {
|
|
299
|
+
Database::execute(self, query, options)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
mod database;
|
|
18
18
|
|
|
19
|
-
pub use database::{Database, QueryRunner};
|
|
19
|
+
pub use database::{Database, QueryRunner, SnapshotAdmin};
|
|
20
20
|
|
|
21
21
|
// Re-export the core execution types so callers don't need a direct
|
|
22
22
|
// dependency on `lora-executor`.
|
|
@@ -26,5 +26,9 @@ pub use lora_executor::{ExecuteOptions, LoraValue, QueryResult, ResultFormat};
|
|
|
26
26
|
// depend on `lora-database` for the happy path.
|
|
27
27
|
pub use lora_store::InMemoryGraph;
|
|
28
28
|
|
|
29
|
+
// Snapshot surface — re-exported so bindings/servers don't need a direct
|
|
30
|
+
// `lora-store` dependency just to name the meta / error types.
|
|
31
|
+
pub use lora_store::{SnapshotError, SnapshotMeta, Snapshotable};
|
|
32
|
+
|
|
29
33
|
// Standalone parsing entry point (does not require building a `Database`).
|
|
30
34
|
pub use lora_parser::parse_query;
|