lora-python 0.5.4__tar.gz → 0.5.6__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.5.4 → lora_python-0.5.6}/Cargo.lock +14 -14
- {lora_python-0.5.4 → lora_python-0.5.6}/Cargo.toml +9 -9
- {lora_python-0.5.4 → lora_python-0.5.6}/PKG-INFO +1 -1
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/src/archive.rs +91 -8
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/src/database.rs +41 -22
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/wal.rs +89 -4
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/config.rs +5 -3
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/recorder_adapter.rs +6 -2
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/wal.rs +6 -13
- {lora_python-0.5.4 → lora_python-0.5.6}/pyproject.toml +1 -1
- {lora_python-0.5.4 → lora_python-0.5.6}/LICENSE +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/README.md +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/src/analyzer.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/src/errors.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/src/resolved.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/src/scope.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-analyzer/src/symbols.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-ast/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-ast/src/ast.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-ast/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/src/logical.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/src/optimizer.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/src/pattern.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/src/physical.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-compiler/src/planner.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/advanced_benchmarks.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/engine_benchmarks.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/fixtures.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/perf_smoke_baseline.json +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/perf_smoke_benchmarks.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/scale_benchmarks.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/temporal_spatial_benchmarks.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/wal_benchmarks.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/src/named.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/src/stream.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/src/transaction.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/advanced_queries.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/aggregation.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/backend_stub.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/create.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/errors.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/expressions.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/functions_extended.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/invariants.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/match.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/merge.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/ordering.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/parameters.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/parser.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/paths.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/projection.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/seeds.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/snapshot.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/spatial.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/temporal.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/test_helpers.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/transactions.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/types_advanced.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/union.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/update.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/vectors.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/where_clause.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/tests/with.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/src/errors.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/src/eval.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/src/executor.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/src/pull.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-executor/src/value.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-parser/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-parser/src/cypher.pest +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-parser/src/error.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-parser/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-parser/src/parser.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/.gitignore +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/LICENSE +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/README.md +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/build.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/examples/async_demo.py +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/examples/basic.py +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/tests/test_async.py +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-python/tests/test_sync.py +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/graph.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/memory.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/mutation.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/snapshot.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/spatial.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/temporal.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-store/src/vector.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/Cargo.toml +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/dir.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/error.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/lib.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/lock.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/lsn.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/record.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/replay.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/segment.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-wal/src/testing.rs +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/python/lora_python/__init__.py +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/python/lora_python/_async.py +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/python/lora_python/_native.pyi +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/python/lora_python/py.typed +0 -0
- {lora_python-0.5.4 → lora_python-0.5.6}/python/lora_python/types.py +0 -0
|
@@ -651,7 +651,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
|
651
651
|
|
|
652
652
|
[[package]]
|
|
653
653
|
name = "lora-analyzer"
|
|
654
|
-
version = "0.5.
|
|
654
|
+
version = "0.5.6"
|
|
655
655
|
dependencies = [
|
|
656
656
|
"lora-ast",
|
|
657
657
|
"lora-parser",
|
|
@@ -661,14 +661,14 @@ dependencies = [
|
|
|
661
661
|
|
|
662
662
|
[[package]]
|
|
663
663
|
name = "lora-ast"
|
|
664
|
-
version = "0.5.
|
|
664
|
+
version = "0.5.6"
|
|
665
665
|
dependencies = [
|
|
666
666
|
"smallvec 2.0.0-alpha.12",
|
|
667
667
|
]
|
|
668
668
|
|
|
669
669
|
[[package]]
|
|
670
670
|
name = "lora-compiler"
|
|
671
|
-
version = "0.5.
|
|
671
|
+
version = "0.5.6"
|
|
672
672
|
dependencies = [
|
|
673
673
|
"lora-analyzer",
|
|
674
674
|
"lora-ast",
|
|
@@ -676,7 +676,7 @@ dependencies = [
|
|
|
676
676
|
|
|
677
677
|
[[package]]
|
|
678
678
|
name = "lora-database"
|
|
679
|
-
version = "0.5.
|
|
679
|
+
version = "0.5.6"
|
|
680
680
|
dependencies = [
|
|
681
681
|
"anyhow",
|
|
682
682
|
"criterion",
|
|
@@ -694,7 +694,7 @@ dependencies = [
|
|
|
694
694
|
|
|
695
695
|
[[package]]
|
|
696
696
|
name = "lora-executor"
|
|
697
|
-
version = "0.5.
|
|
697
|
+
version = "0.5.6"
|
|
698
698
|
dependencies = [
|
|
699
699
|
"lora-analyzer",
|
|
700
700
|
"lora-ast",
|
|
@@ -708,7 +708,7 @@ dependencies = [
|
|
|
708
708
|
|
|
709
709
|
[[package]]
|
|
710
710
|
name = "lora-ffi"
|
|
711
|
-
version = "0.5.
|
|
711
|
+
version = "0.5.6"
|
|
712
712
|
dependencies = [
|
|
713
713
|
"lora-database",
|
|
714
714
|
"lora-store",
|
|
@@ -718,7 +718,7 @@ dependencies = [
|
|
|
718
718
|
|
|
719
719
|
[[package]]
|
|
720
720
|
name = "lora-node"
|
|
721
|
-
version = "0.5.
|
|
721
|
+
version = "0.5.6"
|
|
722
722
|
dependencies = [
|
|
723
723
|
"anyhow",
|
|
724
724
|
"lora-database",
|
|
@@ -732,7 +732,7 @@ dependencies = [
|
|
|
732
732
|
|
|
733
733
|
[[package]]
|
|
734
734
|
name = "lora-parser"
|
|
735
|
-
version = "0.5.
|
|
735
|
+
version = "0.5.6"
|
|
736
736
|
dependencies = [
|
|
737
737
|
"lora-ast",
|
|
738
738
|
"pest",
|
|
@@ -743,7 +743,7 @@ dependencies = [
|
|
|
743
743
|
|
|
744
744
|
[[package]]
|
|
745
745
|
name = "lora-python"
|
|
746
|
-
version = "0.5.
|
|
746
|
+
version = "0.5.6"
|
|
747
747
|
dependencies = [
|
|
748
748
|
"anyhow",
|
|
749
749
|
"lora-database",
|
|
@@ -755,7 +755,7 @@ dependencies = [
|
|
|
755
755
|
|
|
756
756
|
[[package]]
|
|
757
757
|
name = "lora-server"
|
|
758
|
-
version = "0.5.
|
|
758
|
+
version = "0.5.6"
|
|
759
759
|
dependencies = [
|
|
760
760
|
"anyhow",
|
|
761
761
|
"axum",
|
|
@@ -769,7 +769,7 @@ dependencies = [
|
|
|
769
769
|
|
|
770
770
|
[[package]]
|
|
771
771
|
name = "lora-store"
|
|
772
|
-
version = "0.5.
|
|
772
|
+
version = "0.5.6"
|
|
773
773
|
dependencies = [
|
|
774
774
|
"bincode",
|
|
775
775
|
"crc32fast",
|
|
@@ -781,7 +781,7 @@ dependencies = [
|
|
|
781
781
|
|
|
782
782
|
[[package]]
|
|
783
783
|
name = "lora-wal"
|
|
784
|
-
version = "0.5.
|
|
784
|
+
version = "0.5.6"
|
|
785
785
|
dependencies = [
|
|
786
786
|
"bincode",
|
|
787
787
|
"crc32fast",
|
|
@@ -792,7 +792,7 @@ dependencies = [
|
|
|
792
792
|
|
|
793
793
|
[[package]]
|
|
794
794
|
name = "lora-wasm"
|
|
795
|
-
version = "0.5.
|
|
795
|
+
version = "0.5.6"
|
|
796
796
|
dependencies = [
|
|
797
797
|
"anyhow",
|
|
798
798
|
"console_error_panic_hook",
|
|
@@ -807,7 +807,7 @@ dependencies = [
|
|
|
807
807
|
|
|
808
808
|
[[package]]
|
|
809
809
|
name = "lora_ruby"
|
|
810
|
-
version = "0.5.
|
|
810
|
+
version = "0.5.6"
|
|
811
811
|
dependencies = [
|
|
812
812
|
"anyhow",
|
|
813
813
|
"lora-database",
|
|
@@ -4,7 +4,7 @@ resolver = "2"
|
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
6
|
edition = "2021"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.6"
|
|
8
8
|
license = "BUSL-1.1"
|
|
9
9
|
authors = ["LoraDB, Inc."]
|
|
10
10
|
repository = "https://github.com/lora-db/lora"
|
|
@@ -15,14 +15,14 @@ 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.5.
|
|
19
|
-
lora-parser = { path = "crates/lora-parser", version = "=0.5.
|
|
20
|
-
lora-analyzer = { path = "crates/lora-analyzer", version = "=0.5.
|
|
21
|
-
lora-compiler = { path = "crates/lora-compiler", version = "=0.5.
|
|
22
|
-
lora-store = { path = "crates/lora-store", version = "=0.5.
|
|
23
|
-
lora-wal = { path = "crates/lora-wal", version = "=0.5.
|
|
24
|
-
lora-executor = { path = "crates/lora-executor", version = "=0.5.
|
|
25
|
-
lora-database = { path = "crates/lora-database", version = "=0.5.
|
|
18
|
+
lora-ast = { path = "crates/lora-ast", version = "=0.5.6" }
|
|
19
|
+
lora-parser = { path = "crates/lora-parser", version = "=0.5.6" }
|
|
20
|
+
lora-analyzer = { path = "crates/lora-analyzer", version = "=0.5.6" }
|
|
21
|
+
lora-compiler = { path = "crates/lora-compiler", version = "=0.5.6" }
|
|
22
|
+
lora-store = { path = "crates/lora-store", version = "=0.5.6" }
|
|
23
|
+
lora-wal = { path = "crates/lora-wal", version = "=0.5.6" }
|
|
24
|
+
lora-executor = { path = "crates/lora-executor", version = "=0.5.6" }
|
|
25
|
+
lora-database = { path = "crates/lora-database", version = "=0.5.6" }
|
|
26
26
|
|
|
27
27
|
# External crates.
|
|
28
28
|
anyhow = "1"
|
|
@@ -24,8 +24,11 @@ static ARCHIVE_TMP_COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
|
24
24
|
/// `.loradb` target. Any ZIP-compatible tool (WinRAR, Explorer, unzip, 7-Zip)
|
|
25
25
|
/// can inspect the resulting database file.
|
|
26
26
|
pub(crate) struct WalArchive {
|
|
27
|
+
archive_path: PathBuf,
|
|
27
28
|
work_dir: PathBuf,
|
|
29
|
+
max_archive_bytes: u64,
|
|
28
30
|
state: Arc<(Mutex<ArchiveState>, Condvar)>,
|
|
31
|
+
write_lock: Arc<Mutex<()>>,
|
|
29
32
|
worker: Option<JoinHandle<()>>,
|
|
30
33
|
_archive_lock: ArchiveLock,
|
|
31
34
|
}
|
|
@@ -56,16 +59,21 @@ impl WalArchive {
|
|
|
56
59
|
prepare_work_dir(&archive_path, &work_dir, max_archive_bytes)?;
|
|
57
60
|
|
|
58
61
|
let state = Arc::new((Mutex::new(ArchiveState::default()), Condvar::new()));
|
|
62
|
+
let write_lock = Arc::new(Mutex::new(()));
|
|
59
63
|
let worker = Some(spawn_archive_worker(
|
|
60
64
|
state.clone(),
|
|
65
|
+
write_lock.clone(),
|
|
61
66
|
work_dir.clone(),
|
|
62
67
|
archive_path.clone(),
|
|
63
68
|
max_archive_bytes,
|
|
64
69
|
));
|
|
65
70
|
|
|
66
71
|
Ok(Self {
|
|
72
|
+
archive_path,
|
|
67
73
|
work_dir,
|
|
74
|
+
max_archive_bytes,
|
|
68
75
|
state,
|
|
76
|
+
write_lock,
|
|
69
77
|
worker,
|
|
70
78
|
_archive_lock: archive_lock,
|
|
71
79
|
})
|
|
@@ -95,6 +103,50 @@ impl WalMirror for WalArchive {
|
|
|
95
103
|
cv.notify_one();
|
|
96
104
|
Ok(())
|
|
97
105
|
}
|
|
106
|
+
|
|
107
|
+
fn persist_force(&self, wal_dir: &Path) -> Result<(), WalError> {
|
|
108
|
+
if wal_dir != self.work_dir {
|
|
109
|
+
return Err(WalError::Malformed(format!(
|
|
110
|
+
"archive mirror received unexpected WAL dir: {}",
|
|
111
|
+
wal_dir.display()
|
|
112
|
+
)));
|
|
113
|
+
}
|
|
114
|
+
{
|
|
115
|
+
let (lock, _) = &*self.state;
|
|
116
|
+
let state = lock.lock().unwrap();
|
|
117
|
+
if let Some(failure) = &state.failure {
|
|
118
|
+
return Err(WalError::Malformed(format!(
|
|
119
|
+
"database archive writer failed: {failure}"
|
|
120
|
+
)));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let _write_guard = self.write_lock.lock().unwrap();
|
|
125
|
+
{
|
|
126
|
+
let (lock, _) = &*self.state;
|
|
127
|
+
let state = lock.lock().unwrap();
|
|
128
|
+
if let Some(failure) = &state.failure {
|
|
129
|
+
return Err(WalError::Malformed(format!(
|
|
130
|
+
"database archive writer failed: {failure}"
|
|
131
|
+
)));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let result =
|
|
135
|
+
write_archive_atomic(&self.work_dir, &self.archive_path, self.max_archive_bytes);
|
|
136
|
+
let (lock, _) = &*self.state;
|
|
137
|
+
let mut state = lock.lock().unwrap();
|
|
138
|
+
match result {
|
|
139
|
+
Ok(()) => {
|
|
140
|
+
state.dirty = false;
|
|
141
|
+
state.force = false;
|
|
142
|
+
Ok(())
|
|
143
|
+
}
|
|
144
|
+
Err(err) => {
|
|
145
|
+
state.failure = Some(err.to_string());
|
|
146
|
+
Err(err)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
98
150
|
}
|
|
99
151
|
|
|
100
152
|
impl Drop for WalArchive {
|
|
@@ -102,10 +154,9 @@ impl Drop for WalArchive {
|
|
|
102
154
|
{
|
|
103
155
|
let (lock, cv) = &*self.state;
|
|
104
156
|
let mut state = lock.lock().unwrap();
|
|
105
|
-
// The async archive worker may have
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// always take one final archive snapshot from the now-flushed work
|
|
157
|
+
// The async archive worker may not have observed the latest dirty
|
|
158
|
+
// flag yet. Drop runs after the WAL handle is dropped, so always
|
|
159
|
+
// take one final archive snapshot from the fully flushed work
|
|
109
160
|
// directory.
|
|
110
161
|
state.dirty = true;
|
|
111
162
|
state.shutdown = true;
|
|
@@ -132,6 +183,7 @@ impl Drop for WalArchive {
|
|
|
132
183
|
|
|
133
184
|
fn spawn_archive_worker(
|
|
134
185
|
state: Arc<(Mutex<ArchiveState>, Condvar)>,
|
|
186
|
+
write_lock: Arc<Mutex<()>>,
|
|
135
187
|
work_dir: PathBuf,
|
|
136
188
|
archive_path: PathBuf,
|
|
137
189
|
max_archive_bytes: u64,
|
|
@@ -157,6 +209,7 @@ fn spawn_archive_worker(
|
|
|
157
209
|
};
|
|
158
210
|
|
|
159
211
|
if should_flush {
|
|
212
|
+
let _write_guard = write_lock.lock().unwrap();
|
|
160
213
|
if let Err(err) = write_archive_atomic(&work_dir, &archive_path, max_archive_bytes) {
|
|
161
214
|
let (lock, _) = &*state;
|
|
162
215
|
let mut guard = lock.lock().unwrap();
|
|
@@ -269,7 +322,7 @@ fn write_archive_tmp(wal_dir: &Path, tmp_path: &Path) -> Result<(), WalError> {
|
|
|
269
322
|
let file = OpenOptions::new()
|
|
270
323
|
.write(true)
|
|
271
324
|
.create_new(true)
|
|
272
|
-
.open(
|
|
325
|
+
.open(tmp_path)?;
|
|
273
326
|
let writer = BufWriter::new(file);
|
|
274
327
|
let mut zip = ZipWriter::new(writer);
|
|
275
328
|
// Fast deflate keeps the ZIP broadly compatible (WinRAR, Explorer,
|
|
@@ -373,9 +426,8 @@ fn cleanup_stale_temp_paths(archive_path: &Path) -> Result<(), WalError> {
|
|
|
373
426
|
let Some(file_name) = file_name.to_str() else {
|
|
374
427
|
continue;
|
|
375
428
|
};
|
|
376
|
-
let is_archive_tmp =
|
|
377
|
-
|
|
378
|
-
let is_extract_tmp = file_name.starts_with(&extract_tmp_prefix);
|
|
429
|
+
let is_archive_tmp = is_generated_archive_tmp_name(file_name, &archive_tmp_prefix);
|
|
430
|
+
let is_extract_tmp = is_generated_extract_tmp_name(file_name, &extract_tmp_prefix);
|
|
379
431
|
if !is_archive_tmp && !is_extract_tmp {
|
|
380
432
|
continue;
|
|
381
433
|
}
|
|
@@ -390,6 +442,37 @@ fn cleanup_stale_temp_paths(archive_path: &Path) -> Result<(), WalError> {
|
|
|
390
442
|
Ok(())
|
|
391
443
|
}
|
|
392
444
|
|
|
445
|
+
fn is_generated_archive_tmp_name(file_name: &str, prefix: &str) -> bool {
|
|
446
|
+
let Some(rest) = file_name.strip_prefix(prefix) else {
|
|
447
|
+
return false;
|
|
448
|
+
};
|
|
449
|
+
let Some(rest) = rest.strip_suffix(".tmp") else {
|
|
450
|
+
return false;
|
|
451
|
+
};
|
|
452
|
+
let mut parts = rest.split('.');
|
|
453
|
+
matches!(
|
|
454
|
+
(parts.next(), parts.next(), parts.next(), parts.next()),
|
|
455
|
+
(Some(pid), Some(nanos), Some(sequence), None)
|
|
456
|
+
if is_ascii_digits(pid) && is_ascii_digits(nanos) && is_ascii_digits(sequence)
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
fn is_generated_extract_tmp_name(file_name: &str, prefix: &str) -> bool {
|
|
461
|
+
let Some(rest) = file_name.strip_prefix(prefix) else {
|
|
462
|
+
return false;
|
|
463
|
+
};
|
|
464
|
+
let mut parts = rest.split('.');
|
|
465
|
+
matches!(
|
|
466
|
+
(parts.next(), parts.next(), parts.next(), parts.next()),
|
|
467
|
+
(Some(pid), Some(nanos), Some(sequence), None)
|
|
468
|
+
if is_ascii_digits(pid) && is_ascii_digits(nanos) && is_ascii_digits(sequence)
|
|
469
|
+
)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
fn is_ascii_digits(value: &str) -> bool {
|
|
473
|
+
!value.is_empty() && value.bytes().all(|b| b.is_ascii_digit())
|
|
474
|
+
}
|
|
475
|
+
|
|
393
476
|
fn sorted_wal_files(wal_dir: &Path) -> Result<Vec<PathBuf>, WalError> {
|
|
394
477
|
let mut entries = Vec::new();
|
|
395
478
|
for entry in fs::read_dir(wal_dir)? {
|
|
@@ -140,6 +140,15 @@ impl Database<InMemoryGraph> {
|
|
|
140
140
|
Ok(Transaction::new(live, self.wal.clone(), mode))
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/// Force any pending WAL bytes to durable storage and, for archive-backed
|
|
144
|
+
/// databases, refresh the portable `.loradb` file before returning.
|
|
145
|
+
pub fn sync(&self) -> Result<()> {
|
|
146
|
+
if let Some(wal) = &self.wal {
|
|
147
|
+
wal.force_fsync()?;
|
|
148
|
+
}
|
|
149
|
+
Ok(())
|
|
150
|
+
}
|
|
151
|
+
|
|
143
152
|
/// Restore from a snapshot file then replay any WAL records past
|
|
144
153
|
/// it.
|
|
145
154
|
///
|
|
@@ -596,32 +605,42 @@ where
|
|
|
596
605
|
// instead, so swapping in a new backend only requires changing one type
|
|
597
606
|
// parameter.
|
|
598
607
|
|
|
599
|
-
/// Drop every node and relationship
|
|
608
|
+
/// Drop every node and relationship, returning WAL/archive errors to the
|
|
609
|
+
/// caller.
|
|
600
610
|
///
|
|
601
|
-
/// When a WAL is attached,
|
|
602
|
-
///
|
|
603
|
-
///
|
|
604
|
-
///
|
|
605
|
-
///
|
|
606
|
-
|
|
607
|
-
/// will surface to the next query.
|
|
608
|
-
pub fn clear(&self) {
|
|
611
|
+
/// When a WAL is attached, the clear is wrapped in `arm`/`commit` so the
|
|
612
|
+
/// `MutationEvent::Clear` fired by the store reaches the log inside a
|
|
613
|
+
/// transaction. If a failure happens after the in-memory graph has been
|
|
614
|
+
/// cleared, the recorder is poisoned by the failing WAL path and future
|
|
615
|
+
/// writes fail until the database is reopened from durable state.
|
|
616
|
+
pub fn try_clear(&self) -> Result<()> {
|
|
609
617
|
let mut guard = self.write_store();
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
let _ = rec.commit();
|
|
621
|
-
let _ = rec.flush();
|
|
622
|
-
}
|
|
618
|
+
let Some(rec) = &self.wal else {
|
|
619
|
+
guard.clear();
|
|
620
|
+
return Ok(());
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
rec.arm().map_err(|e| anyhow!("WAL arm failed: {e}"))?;
|
|
624
|
+
guard.clear();
|
|
625
|
+
match rec.commit() {
|
|
626
|
+
Ok(WroteCommit::Yes) => {
|
|
627
|
+
rec.flush().map_err(|e| anyhow!("WAL flush failed: {e}"))?;
|
|
623
628
|
}
|
|
629
|
+
Ok(WroteCommit::No) => {}
|
|
630
|
+
Err(e) => return Err(anyhow!("WAL commit failed: {e}")),
|
|
631
|
+
}
|
|
632
|
+
if let Some(reason) = rec.poisoned() {
|
|
633
|
+
return Err(anyhow!("WAL poisoned: {reason}"));
|
|
624
634
|
}
|
|
635
|
+
Ok(())
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/// Drop every node and relationship.
|
|
639
|
+
///
|
|
640
|
+
/// This compatibility helper keeps the historical infallible Rust API.
|
|
641
|
+
/// Bindings that can report errors should call [`Self::try_clear`].
|
|
642
|
+
pub fn clear(&self) {
|
|
643
|
+
let _ = self.try_clear();
|
|
625
644
|
}
|
|
626
645
|
|
|
627
646
|
/// Number of nodes currently in the graph.
|
|
@@ -251,6 +251,92 @@ fn named_database_recovers_from_durable_sidecar_when_archive_lags() {
|
|
|
251
251
|
);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
#[test]
|
|
255
|
+
fn named_database_sync_makes_archive_immediately_portable() {
|
|
256
|
+
let dir = TmpDir::new("named-sync-source");
|
|
257
|
+
let portable_dir = TmpDir::new("named-sync-copy");
|
|
258
|
+
|
|
259
|
+
let db = Database::open_named(
|
|
260
|
+
"app",
|
|
261
|
+
DatabaseOpenOptions {
|
|
262
|
+
sync_mode: SyncMode::Group {
|
|
263
|
+
interval_ms: 60_000,
|
|
264
|
+
},
|
|
265
|
+
..DatabaseOpenOptions::default().with_database_dir(dir.path())
|
|
266
|
+
},
|
|
267
|
+
)
|
|
268
|
+
.unwrap();
|
|
269
|
+
db.execute("CREATE (:Synced {id: 1})", rows()).unwrap();
|
|
270
|
+
db.sync().unwrap();
|
|
271
|
+
|
|
272
|
+
std::fs::copy(
|
|
273
|
+
dir.path().join("app.loradb"),
|
|
274
|
+
portable_dir.path().join("app.loradb"),
|
|
275
|
+
)
|
|
276
|
+
.unwrap();
|
|
277
|
+
|
|
278
|
+
let recovered = Database::open_named(
|
|
279
|
+
"app",
|
|
280
|
+
DatabaseOpenOptions::default().with_database_dir(portable_dir.path()),
|
|
281
|
+
)
|
|
282
|
+
.unwrap();
|
|
283
|
+
assert_eq!(recovered.node_count(), 1);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#[test]
|
|
287
|
+
fn named_database_clear_persists_through_archive() {
|
|
288
|
+
let dir = TmpDir::new("named-clear");
|
|
289
|
+
|
|
290
|
+
{
|
|
291
|
+
let db = Database::open_named(
|
|
292
|
+
"app",
|
|
293
|
+
DatabaseOpenOptions::default().with_database_dir(dir.path()),
|
|
294
|
+
)
|
|
295
|
+
.unwrap();
|
|
296
|
+
db.execute("CREATE (:A {id: 1})-[:R]->(:B {id: 2})", rows())
|
|
297
|
+
.unwrap();
|
|
298
|
+
db.try_clear().unwrap();
|
|
299
|
+
assert_eq!(db.node_count(), 0);
|
|
300
|
+
assert_eq!(db.relationship_count(), 0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let recovered = Database::open_named(
|
|
304
|
+
"app",
|
|
305
|
+
DatabaseOpenOptions::default().with_database_dir(dir.path()),
|
|
306
|
+
)
|
|
307
|
+
.unwrap();
|
|
308
|
+
assert_eq!(recovered.node_count(), 0);
|
|
309
|
+
assert_eq!(recovered.relationship_count(), 0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#[test]
|
|
313
|
+
fn named_database_cleanup_only_removes_generated_temp_paths() {
|
|
314
|
+
let dir = TmpDir::new("named-temp-cleanup");
|
|
315
|
+
let unrelated_file = dir.path().join("app.loradb.user.tmp");
|
|
316
|
+
let unrelated_dir = dir.path().join("app.loradb.wal.extract.user");
|
|
317
|
+
let generated_file = dir.path().join("app.loradb.1.2.3.tmp");
|
|
318
|
+
let generated_dir = dir.path().join("app.loradb.wal.extract.1.2.3");
|
|
319
|
+
|
|
320
|
+
std::fs::write(&unrelated_file, b"keep").unwrap();
|
|
321
|
+
std::fs::create_dir(&unrelated_dir).unwrap();
|
|
322
|
+
std::fs::write(&generated_file, b"delete").unwrap();
|
|
323
|
+
std::fs::create_dir(&generated_dir).unwrap();
|
|
324
|
+
|
|
325
|
+
{
|
|
326
|
+
let db = Database::open_named(
|
|
327
|
+
"app",
|
|
328
|
+
DatabaseOpenOptions::default().with_database_dir(dir.path()),
|
|
329
|
+
)
|
|
330
|
+
.unwrap();
|
|
331
|
+
db.sync().unwrap();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
assert!(unrelated_file.exists());
|
|
335
|
+
assert!(unrelated_dir.exists());
|
|
336
|
+
assert!(!generated_file.exists());
|
|
337
|
+
assert!(!generated_dir.exists());
|
|
338
|
+
}
|
|
339
|
+
|
|
254
340
|
#[test]
|
|
255
341
|
fn named_database_rejects_invalid_archive_without_publishing_partial_sidecar() {
|
|
256
342
|
let dir = TmpDir::new("named-invalid-archive");
|
|
@@ -367,10 +453,9 @@ fn named_database_final_archive_flush_captures_group_buffer() {
|
|
|
367
453
|
)
|
|
368
454
|
.unwrap();
|
|
369
455
|
|
|
370
|
-
// Let the archive debounce worker
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
// segment file.
|
|
456
|
+
// Let the archive debounce worker run before the clean shutdown path.
|
|
457
|
+
// The final archive flush on drop must still publish a complete,
|
|
458
|
+
// reopenable archive.
|
|
374
459
|
std::thread::sleep(std::time::Duration::from_millis(1_200));
|
|
375
460
|
}
|
|
376
461
|
|
|
@@ -14,9 +14,11 @@ pub enum SyncMode {
|
|
|
14
14
|
#[default]
|
|
15
15
|
PerCommit,
|
|
16
16
|
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
17
|
+
/// Write commit bytes to the OS immediately, then `fsync` on a fixed
|
|
18
|
+
/// cadence on a background thread. This survives ordinary process death
|
|
19
|
+
/// after `flush()` returns, but can still trade the last `interval_ms` of
|
|
20
|
+
/// commits on power loss or kernel crash for higher throughput on
|
|
21
|
+
/// bulk-load workloads.
|
|
20
22
|
///
|
|
21
23
|
/// A background fsync failure poisons the WAL: the next `commit` /
|
|
22
24
|
/// `flush` / `force_fsync` returns [`crate::WalError::Poisoned`] and
|
|
@@ -76,6 +76,10 @@ pub enum WroteCommit {
|
|
|
76
76
|
/// databases.
|
|
77
77
|
pub trait WalMirror: Send + Sync {
|
|
78
78
|
fn persist(&self, wal_dir: &Path) -> Result<(), WalError>;
|
|
79
|
+
|
|
80
|
+
fn persist_force(&self, wal_dir: &Path) -> Result<(), WalError> {
|
|
81
|
+
self.persist(wal_dir)
|
|
82
|
+
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
#[derive(Default)]
|
|
@@ -246,7 +250,7 @@ impl WalRecorder {
|
|
|
246
250
|
state.poisoned = Some(e.to_string());
|
|
247
251
|
})?;
|
|
248
252
|
if let Some(mirror) = &self.mirror {
|
|
249
|
-
mirror.
|
|
253
|
+
mirror.persist_force(self.wal.dir()).inspect_err(|e| {
|
|
250
254
|
state.poisoned = Some(e.to_string());
|
|
251
255
|
})?;
|
|
252
256
|
}
|
|
@@ -274,7 +278,7 @@ impl WalRecorder {
|
|
|
274
278
|
// full WAL history is the only safe way to let the archive recover by
|
|
275
279
|
// itself after a checkpoint marker.
|
|
276
280
|
if let Some(mirror) = &self.mirror {
|
|
277
|
-
mirror.
|
|
281
|
+
mirror.persist_force(self.wal.dir())?;
|
|
278
282
|
return Ok(());
|
|
279
283
|
}
|
|
280
284
|
self.wal.truncate_up_to(fence_lsn)?;
|
|
@@ -378,8 +378,8 @@ impl Wal {
|
|
|
378
378
|
/// - `PerCommit` — write the buffer to the OS, `fsync`, and
|
|
379
379
|
/// advance `durable_lsn`. The strongest contract: every
|
|
380
380
|
/// record up to `next_lsn - 1` is on disk.
|
|
381
|
-
/// - `Group` —
|
|
382
|
-
///
|
|
381
|
+
/// - `Group` — write the buffer to the OS, but let the background
|
|
382
|
+
/// flusher fsync and advance `durable_lsn` on its cadence.
|
|
383
383
|
/// - `None` — write the buffer to the OS only, but advance
|
|
384
384
|
/// `durable_lsn` anyway. The mode opts out of crash
|
|
385
385
|
/// durability, so the checkpoint fence reports
|
|
@@ -407,9 +407,9 @@ impl Wal {
|
|
|
407
407
|
let written_lsn = Lsn::new(state.next_lsn.raw().saturating_sub(1));
|
|
408
408
|
|
|
409
409
|
// Decide whether this call is allowed to advance
|
|
410
|
-
// `durable_lsn`. The bg flusher's job in Group mode is
|
|
411
|
-
//
|
|
412
|
-
//
|
|
410
|
+
// `durable_lsn`. The bg flusher's job in Group mode is to advance
|
|
411
|
+
// that fence after fsync; PerCommit and None do it inline; Group's
|
|
412
|
+
// user-driven `flush()` only pushes bytes to the OS.
|
|
413
413
|
let do_fsync = matches!(
|
|
414
414
|
(kind, self.sync_mode),
|
|
415
415
|
(FlushKind::ForceFsync, _) | (_, SyncMode::PerCommit)
|
|
@@ -419,14 +419,7 @@ impl Wal {
|
|
|
419
419
|
(FlushKind::ForceFsync, _) | (_, SyncMode::PerCommit) | (_, SyncMode::None)
|
|
420
420
|
);
|
|
421
421
|
|
|
422
|
-
if
|
|
423
|
-
(kind, self.sync_mode),
|
|
424
|
-
(FlushKind::PerConfiguredMode, SyncMode::Group { .. })
|
|
425
|
-
) {
|
|
426
|
-
// Group mode batches both the write syscall and the fsync. This
|
|
427
|
-
// keeps the write-heavy hot path close to in-memory execution; the
|
|
428
|
-
// background flusher (or Drop) will force the buffer out.
|
|
429
|
-
} else if do_fsync {
|
|
422
|
+
if do_fsync {
|
|
430
423
|
state.active_writer.flush_and_sync()?;
|
|
431
424
|
} else {
|
|
432
425
|
state.active_writer.flush_buffer()?;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/perf_smoke_baseline.json
RENAMED
|
File without changes
|
{lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/perf_smoke_benchmarks.rs
RENAMED
|
File without changes
|
|
File without changes
|
{lora_python-0.5.4 → lora_python-0.5.6}/crates/lora-database/benches/temporal_spatial_benchmarks.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|