lora-python 0.3.0__tar.gz → 0.4.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.3.0 → lora_python-0.4.0}/Cargo.lock +25 -13
- {lora_python-0.3.0 → lora_python-0.4.0}/Cargo.toml +10 -9
- {lora_python-0.3.0 → lora_python-0.4.0}/PKG-INFO +36 -2
- {lora_python-0.3.0 → lora_python-0.4.0}/README.md +35 -1
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/Cargo.toml +5 -0
- lora_python-0.4.0/crates/lora-database/benches/wal_benchmarks.rs +205 -0
- lora_python-0.4.0/crates/lora-database/src/database.rs +770 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/src/lib.rs +6 -1
- lora_python-0.4.0/crates/lora-database/tests/wal.rs +465 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/README.md +35 -1
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/examples/basic.py +2 -1
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/src/lib.rs +65 -23
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/tests/test_async.py +26 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/tests/test_sync.py +58 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/memory.rs +118 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/mutation.rs +24 -6
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/snapshot.rs +17 -0
- lora_python-0.4.0/crates/lora-wal/Cargo.toml +19 -0
- lora_python-0.4.0/crates/lora-wal/src/config.rs +64 -0
- lora_python-0.4.0/crates/lora-wal/src/dir.rs +194 -0
- lora_python-0.4.0/crates/lora-wal/src/error.rs +51 -0
- lora_python-0.4.0/crates/lora-wal/src/lib.rs +50 -0
- lora_python-0.4.0/crates/lora-wal/src/lock.rs +104 -0
- lora_python-0.4.0/crates/lora-wal/src/lsn.rs +87 -0
- lora_python-0.4.0/crates/lora-wal/src/record.rs +443 -0
- lora_python-0.4.0/crates/lora-wal/src/recorder_adapter.rs +479 -0
- lora_python-0.4.0/crates/lora-wal/src/replay.rs +260 -0
- lora_python-0.4.0/crates/lora-wal/src/segment.rs +543 -0
- lora_python-0.4.0/crates/lora-wal/src/testing.rs +39 -0
- lora_python-0.4.0/crates/lora-wal/src/wal.rs +1002 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/pyproject.toml +1 -1
- {lora_python-0.3.0 → lora_python-0.4.0}/python/lora_python/__init__.py +5 -1
- {lora_python-0.3.0 → lora_python-0.4.0}/python/lora_python/_async.py +15 -5
- {lora_python-0.3.0 → lora_python-0.4.0}/python/lora_python/_native.pyi +6 -3
- lora_python-0.3.0/crates/lora-database/src/database.rs +0 -301
- {lora_python-0.3.0 → lora_python-0.4.0}/LICENSE +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/src/analyzer.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/src/errors.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/src/lib.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/src/resolved.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/src/scope.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-analyzer/src/symbols.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-ast/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-ast/src/ast.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-ast/src/lib.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/src/lib.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/src/logical.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/src/optimizer.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/src/pattern.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/src/physical.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-compiler/src/planner.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/advanced_benchmarks.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/engine_benchmarks.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/fixtures.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/perf_smoke_baseline.json +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/perf_smoke_benchmarks.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/scale_benchmarks.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/benches/temporal_spatial_benchmarks.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/advanced_queries.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/aggregation.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/backend_stub.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/create.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/errors.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/expressions.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/functions_extended.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/invariants.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/match.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/merge.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/ordering.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/parameters.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/parser.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/paths.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/projection.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/seeds.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/snapshot.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/spatial.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/temporal.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/test_helpers.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/types_advanced.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/union.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/update.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/vectors.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/where_clause.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-database/tests/with.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-executor/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-executor/src/errors.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-executor/src/eval.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-executor/src/executor.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-executor/src/lib.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-executor/src/value.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-parser/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-parser/src/cypher.pest +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-parser/src/error.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-parser/src/lib.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-parser/src/parser.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/.gitignore +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/LICENSE +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/build.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-python/examples/async_demo.py +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/Cargo.toml +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/graph.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/lib.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/spatial.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/temporal.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/crates/lora-store/src/vector.rs +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/python/lora_python/py.typed +0 -0
- {lora_python-0.3.0 → lora_python-0.4.0}/python/lora_python/types.py +0 -0
|
@@ -629,7 +629,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
|
629
629
|
|
|
630
630
|
[[package]]
|
|
631
631
|
name = "lora-analyzer"
|
|
632
|
-
version = "0.
|
|
632
|
+
version = "0.4.0"
|
|
633
633
|
dependencies = [
|
|
634
634
|
"lora-ast",
|
|
635
635
|
"lora-parser",
|
|
@@ -639,14 +639,14 @@ dependencies = [
|
|
|
639
639
|
|
|
640
640
|
[[package]]
|
|
641
641
|
name = "lora-ast"
|
|
642
|
-
version = "0.
|
|
642
|
+
version = "0.4.0"
|
|
643
643
|
dependencies = [
|
|
644
644
|
"smallvec 2.0.0-alpha.12",
|
|
645
645
|
]
|
|
646
646
|
|
|
647
647
|
[[package]]
|
|
648
648
|
name = "lora-compiler"
|
|
649
|
-
version = "0.
|
|
649
|
+
version = "0.4.0"
|
|
650
650
|
dependencies = [
|
|
651
651
|
"lora-analyzer",
|
|
652
652
|
"lora-ast",
|
|
@@ -654,7 +654,7 @@ dependencies = [
|
|
|
654
654
|
|
|
655
655
|
[[package]]
|
|
656
656
|
name = "lora-database"
|
|
657
|
-
version = "0.
|
|
657
|
+
version = "0.4.0"
|
|
658
658
|
dependencies = [
|
|
659
659
|
"anyhow",
|
|
660
660
|
"criterion",
|
|
@@ -664,13 +664,14 @@ dependencies = [
|
|
|
664
664
|
"lora-executor",
|
|
665
665
|
"lora-parser",
|
|
666
666
|
"lora-store",
|
|
667
|
+
"lora-wal",
|
|
667
668
|
"serde_json",
|
|
668
669
|
"tower",
|
|
669
670
|
]
|
|
670
671
|
|
|
671
672
|
[[package]]
|
|
672
673
|
name = "lora-executor"
|
|
673
|
-
version = "0.
|
|
674
|
+
version = "0.4.0"
|
|
674
675
|
dependencies = [
|
|
675
676
|
"lora-analyzer",
|
|
676
677
|
"lora-ast",
|
|
@@ -684,7 +685,7 @@ dependencies = [
|
|
|
684
685
|
|
|
685
686
|
[[package]]
|
|
686
687
|
name = "lora-ffi"
|
|
687
|
-
version = "0.
|
|
688
|
+
version = "0.4.0"
|
|
688
689
|
dependencies = [
|
|
689
690
|
"lora-database",
|
|
690
691
|
"lora-store",
|
|
@@ -694,7 +695,7 @@ dependencies = [
|
|
|
694
695
|
|
|
695
696
|
[[package]]
|
|
696
697
|
name = "lora-node"
|
|
697
|
-
version = "0.
|
|
698
|
+
version = "0.4.0"
|
|
698
699
|
dependencies = [
|
|
699
700
|
"anyhow",
|
|
700
701
|
"lora-database",
|
|
@@ -708,7 +709,7 @@ dependencies = [
|
|
|
708
709
|
|
|
709
710
|
[[package]]
|
|
710
711
|
name = "lora-parser"
|
|
711
|
-
version = "0.
|
|
712
|
+
version = "0.4.0"
|
|
712
713
|
dependencies = [
|
|
713
714
|
"lora-ast",
|
|
714
715
|
"pest",
|
|
@@ -719,7 +720,7 @@ dependencies = [
|
|
|
719
720
|
|
|
720
721
|
[[package]]
|
|
721
722
|
name = "lora-python"
|
|
722
|
-
version = "0.
|
|
723
|
+
version = "0.4.0"
|
|
723
724
|
dependencies = [
|
|
724
725
|
"anyhow",
|
|
725
726
|
"lora-database",
|
|
@@ -731,7 +732,7 @@ dependencies = [
|
|
|
731
732
|
|
|
732
733
|
[[package]]
|
|
733
734
|
name = "lora-server"
|
|
734
|
-
version = "0.
|
|
735
|
+
version = "0.4.0"
|
|
735
736
|
dependencies = [
|
|
736
737
|
"anyhow",
|
|
737
738
|
"axum",
|
|
@@ -745,7 +746,7 @@ dependencies = [
|
|
|
745
746
|
|
|
746
747
|
[[package]]
|
|
747
748
|
name = "lora-store"
|
|
748
|
-
version = "0.
|
|
749
|
+
version = "0.4.0"
|
|
749
750
|
dependencies = [
|
|
750
751
|
"bincode",
|
|
751
752
|
"crc32fast",
|
|
@@ -755,9 +756,20 @@ dependencies = [
|
|
|
755
756
|
"thiserror",
|
|
756
757
|
]
|
|
757
758
|
|
|
759
|
+
[[package]]
|
|
760
|
+
name = "lora-wal"
|
|
761
|
+
version = "0.4.0"
|
|
762
|
+
dependencies = [
|
|
763
|
+
"bincode",
|
|
764
|
+
"crc32fast",
|
|
765
|
+
"lora-store",
|
|
766
|
+
"serde",
|
|
767
|
+
"thiserror",
|
|
768
|
+
]
|
|
769
|
+
|
|
758
770
|
[[package]]
|
|
759
771
|
name = "lora-wasm"
|
|
760
|
-
version = "0.
|
|
772
|
+
version = "0.4.0"
|
|
761
773
|
dependencies = [
|
|
762
774
|
"anyhow",
|
|
763
775
|
"console_error_panic_hook",
|
|
@@ -772,7 +784,7 @@ dependencies = [
|
|
|
772
784
|
|
|
773
785
|
[[package]]
|
|
774
786
|
name = "lora_ruby"
|
|
775
|
-
version = "0.
|
|
787
|
+
version = "0.4.0"
|
|
776
788
|
dependencies = [
|
|
777
789
|
"anyhow",
|
|
778
790
|
"lora-database",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[workspace]
|
|
2
|
-
members = ["crates/lora-ast", "crates/lora-parser", "crates/lora-analyzer", "crates/lora-compiler", "crates/lora-store", "crates/lora-executor", "crates/lora-database", "crates/lora-python"]
|
|
2
|
+
members = ["crates/lora-ast", "crates/lora-parser", "crates/lora-analyzer", "crates/lora-compiler", "crates/lora-store", "crates/lora-wal", "crates/lora-executor", "crates/lora-database", "crates/lora-python"]
|
|
3
3
|
resolver = "2"
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
6
|
edition = "2021"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
license = "BUSL-1.1"
|
|
9
9
|
authors = ["LoraDB, Inc."]
|
|
10
10
|
repository = "https://github.com/lora-db/lora"
|
|
@@ -15,13 +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.
|
|
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-
|
|
24
|
-
lora-
|
|
18
|
+
lora-ast = { path = "crates/lora-ast", version = "=0.4.0" }
|
|
19
|
+
lora-parser = { path = "crates/lora-parser", version = "=0.4.0" }
|
|
20
|
+
lora-analyzer = { path = "crates/lora-analyzer", version = "=0.4.0" }
|
|
21
|
+
lora-compiler = { path = "crates/lora-compiler", version = "=0.4.0" }
|
|
22
|
+
lora-store = { path = "crates/lora-store", version = "=0.4.0" }
|
|
23
|
+
lora-wal = { path = "crates/lora-wal", version = "=0.4.0" }
|
|
24
|
+
lora-executor = { path = "crates/lora-executor", version = "=0.4.0" }
|
|
25
|
+
lora-database = { path = "crates/lora-database", version = "=0.4.0" }
|
|
25
26
|
|
|
26
27
|
# External crates.
|
|
27
28
|
anyhow = "1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lora-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -30,7 +30,7 @@ Project-URL: Repository, https://github.com/lora-db/lora
|
|
|
30
30
|
|
|
31
31
|
# lora-python
|
|
32
32
|
|
|
33
|
-
Python bindings for the [Lora](../../README.md)
|
|
33
|
+
Python bindings for the [Lora](../../README.md) graph
|
|
34
34
|
engine. Ships both a synchronous PyO3 `Database` class and an
|
|
35
35
|
asyncio-compatible `AsyncDatabase` wrapper that never blocks the event loop.
|
|
36
36
|
|
|
@@ -64,6 +64,18 @@ for row in res["rows"]:
|
|
|
64
64
|
print(n["properties"]["name"])
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
Initialization rule:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from lora_python import Database
|
|
71
|
+
|
|
72
|
+
scratch = Database.create() # in-memory
|
|
73
|
+
persistent = Database.create("./app") # persistent: directory string
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If you want persistence, pass a directory string to `Database.create(...)`
|
|
77
|
+
or `Database(...)`.
|
|
78
|
+
|
|
67
79
|
## Async usage (non-blocking)
|
|
68
80
|
|
|
69
81
|
```python
|
|
@@ -79,6 +91,13 @@ async def main():
|
|
|
79
91
|
asyncio.run(main())
|
|
80
92
|
```
|
|
81
93
|
|
|
94
|
+
Async initialization follows the same rule:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
db = await AsyncDatabase.create() # in-memory
|
|
98
|
+
db = await AsyncDatabase.create("./app") # persistent: directory string
|
|
99
|
+
```
|
|
100
|
+
|
|
82
101
|
`AsyncDatabase.execute` dispatches the query onto the default asyncio
|
|
83
102
|
thread pool via `asyncio.to_thread`. The PyO3 `Database.execute` releases
|
|
84
103
|
the Python GIL for the duration of engine work, so other coroutines on the
|
|
@@ -125,6 +144,21 @@ Constructors and guards are exported from `lora_python.types`:
|
|
|
125
144
|
|
|
126
145
|
All three are available as `lora_python.LoraError`, etc.
|
|
127
146
|
|
|
147
|
+
## Persistence
|
|
148
|
+
|
|
149
|
+
`Database.create("./app")`, `Database("./app")`, and
|
|
150
|
+
`await AsyncDatabase.create("./app")` open or create a WAL-backed
|
|
151
|
+
persistent database rooted at that directory. Reopening the same path
|
|
152
|
+
replays committed writes before returning the handle.
|
|
153
|
+
|
|
154
|
+
Call `db.close()` / `await db.close()` before reopening the same WAL
|
|
155
|
+
directory inside one process.
|
|
156
|
+
|
|
157
|
+
This first Python persistence slice intentionally stays small: the
|
|
158
|
+
binding exposes WAL-backed initialization plus the existing
|
|
159
|
+
`save_snapshot` / `load_snapshot` APIs, but not checkpoint, truncate,
|
|
160
|
+
status, or sync-mode controls.
|
|
161
|
+
|
|
128
162
|
## Architecture
|
|
129
163
|
|
|
130
164
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# lora-python
|
|
2
2
|
|
|
3
|
-
Python bindings for the [Lora](../../README.md)
|
|
3
|
+
Python bindings for the [Lora](../../README.md) graph
|
|
4
4
|
engine. Ships both a synchronous PyO3 `Database` class and an
|
|
5
5
|
asyncio-compatible `AsyncDatabase` wrapper that never blocks the event loop.
|
|
6
6
|
|
|
@@ -34,6 +34,18 @@ for row in res["rows"]:
|
|
|
34
34
|
print(n["properties"]["name"])
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
Initialization rule:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from lora_python import Database
|
|
41
|
+
|
|
42
|
+
scratch = Database.create() # in-memory
|
|
43
|
+
persistent = Database.create("./app") # persistent: directory string
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If you want persistence, pass a directory string to `Database.create(...)`
|
|
47
|
+
or `Database(...)`.
|
|
48
|
+
|
|
37
49
|
## Async usage (non-blocking)
|
|
38
50
|
|
|
39
51
|
```python
|
|
@@ -49,6 +61,13 @@ async def main():
|
|
|
49
61
|
asyncio.run(main())
|
|
50
62
|
```
|
|
51
63
|
|
|
64
|
+
Async initialization follows the same rule:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
db = await AsyncDatabase.create() # in-memory
|
|
68
|
+
db = await AsyncDatabase.create("./app") # persistent: directory string
|
|
69
|
+
```
|
|
70
|
+
|
|
52
71
|
`AsyncDatabase.execute` dispatches the query onto the default asyncio
|
|
53
72
|
thread pool via `asyncio.to_thread`. The PyO3 `Database.execute` releases
|
|
54
73
|
the Python GIL for the duration of engine work, so other coroutines on the
|
|
@@ -95,6 +114,21 @@ Constructors and guards are exported from `lora_python.types`:
|
|
|
95
114
|
|
|
96
115
|
All three are available as `lora_python.LoraError`, etc.
|
|
97
116
|
|
|
117
|
+
## Persistence
|
|
118
|
+
|
|
119
|
+
`Database.create("./app")`, `Database("./app")`, and
|
|
120
|
+
`await AsyncDatabase.create("./app")` open or create a WAL-backed
|
|
121
|
+
persistent database rooted at that directory. Reopening the same path
|
|
122
|
+
replays committed writes before returning the handle.
|
|
123
|
+
|
|
124
|
+
Call `db.close()` / `await db.close()` before reopening the same WAL
|
|
125
|
+
directory inside one process.
|
|
126
|
+
|
|
127
|
+
This first Python persistence slice intentionally stays small: the
|
|
128
|
+
binding exposes WAL-backed initialization plus the existing
|
|
129
|
+
`save_snapshot` / `load_snapshot` APIs, but not checkpoint, truncate,
|
|
130
|
+
status, or sync-mode controls.
|
|
131
|
+
|
|
98
132
|
## Architecture
|
|
99
133
|
|
|
100
134
|
```
|
|
@@ -23,6 +23,7 @@ lora-analyzer.workspace = true
|
|
|
23
23
|
lora-compiler.workspace = true
|
|
24
24
|
lora-executor.workspace = true
|
|
25
25
|
lora-store.workspace = true
|
|
26
|
+
lora-wal.workspace = true
|
|
26
27
|
|
|
27
28
|
serde_json.workspace = true
|
|
28
29
|
tower.workspace = true
|
|
@@ -49,3 +50,7 @@ harness = false
|
|
|
49
50
|
[[bench]]
|
|
50
51
|
name = "perf_smoke_benchmarks"
|
|
51
52
|
harness = false
|
|
53
|
+
|
|
54
|
+
[[bench]]
|
|
55
|
+
name = "wal_benchmarks"
|
|
56
|
+
harness = false
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
//! WAL-aware microbenchmarks.
|
|
2
|
+
//!
|
|
3
|
+
//! These exercise the four durability profiles end-to-end through
|
|
4
|
+
//! `Database::execute_with_params`:
|
|
5
|
+
//!
|
|
6
|
+
//! - **`no_wal`** — `Database::in_memory()` (the existing fast path;
|
|
7
|
+
//! serves as the baseline the others are compared against).
|
|
8
|
+
//! - **`per_commit`** — `WalConfig::Enabled` with `SyncMode::PerCommit`
|
|
9
|
+
//! (fsync before every commit returns).
|
|
10
|
+
//! - **`group`** — `WalConfig::Enabled` with `SyncMode::Group`
|
|
11
|
+
//! (write-only on commit, bg flusher fsyncs).
|
|
12
|
+
//! - **`none`** — `WalConfig::Enabled` with `SyncMode::None`
|
|
13
|
+
//! (no fsync at all, OS-buffered).
|
|
14
|
+
//!
|
|
15
|
+
//! The shape that matters is *commit latency* — every iteration runs a
|
|
16
|
+
//! single tiny `CREATE` statement so the engine work is negligible and
|
|
17
|
+
//! the WAL path dominates. On NVMe the gap between `no_wal` and
|
|
18
|
+
//! `per_commit` is roughly the cost of one `fsync` (50–200 µs); the
|
|
19
|
+
//! gap between `per_commit` and `group` / `none` measures how much
|
|
20
|
+
//! you save by deferring durability.
|
|
21
|
+
//!
|
|
22
|
+
//! There is also a small **`recovery`** bench that times opening a WAL
|
|
23
|
+
//! with N committed transactions and re-applying them. This is the
|
|
24
|
+
//! number that bounds startup time on a fresh process.
|
|
25
|
+
//!
|
|
26
|
+
//! Run with:
|
|
27
|
+
//! `cargo bench -p lora-database --bench wal_benchmarks`
|
|
28
|
+
|
|
29
|
+
use std::path::PathBuf;
|
|
30
|
+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
31
|
+
|
|
32
|
+
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
|
33
|
+
use lora_database::{Database, ExecuteOptions, ResultFormat, SyncMode, WalConfig};
|
|
34
|
+
use std::hint::black_box;
|
|
35
|
+
|
|
36
|
+
fn opts() -> Option<ExecuteOptions> {
|
|
37
|
+
Some(ExecuteOptions {
|
|
38
|
+
format: ResultFormat::Rows,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Per-iteration scratch directory. Criterion's `iter_batched_setup`
|
|
43
|
+
/// runs setup once per batch, so this is called once per Criterion
|
|
44
|
+
/// "sample" — fine even at sample_size=20.
|
|
45
|
+
struct ScratchDir {
|
|
46
|
+
path: PathBuf,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
impl ScratchDir {
|
|
50
|
+
fn new(tag: &str) -> Self {
|
|
51
|
+
let mut path = std::env::temp_dir();
|
|
52
|
+
path.push(format!(
|
|
53
|
+
"lora-wal-bench-{}-{}-{}",
|
|
54
|
+
tag,
|
|
55
|
+
std::process::id(),
|
|
56
|
+
SystemTime::now()
|
|
57
|
+
.duration_since(UNIX_EPOCH)
|
|
58
|
+
.unwrap()
|
|
59
|
+
.as_nanos()
|
|
60
|
+
));
|
|
61
|
+
std::fs::create_dir_all(&path).unwrap();
|
|
62
|
+
Self { path }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl Drop for ScratchDir {
|
|
67
|
+
fn drop(&mut self) {
|
|
68
|
+
let _ = std::fs::remove_dir_all(&self.path);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn enabled(dir: &std::path::Path, sync_mode: SyncMode) -> WalConfig {
|
|
73
|
+
WalConfig::Enabled {
|
|
74
|
+
dir: dir.to_path_buf(),
|
|
75
|
+
sync_mode,
|
|
76
|
+
segment_target_bytes: 8 * 1024 * 1024,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn smoke_config() -> Criterion {
|
|
81
|
+
// Per-bench budget of ~3 s. fsync cost dominates per_commit so we
|
|
82
|
+
// cannot run this as cheaply as the in-memory smoke suite, but we
|
|
83
|
+
// also don't want a 30-second bench on every CI run.
|
|
84
|
+
Criterion::default()
|
|
85
|
+
.warm_up_time(Duration::from_millis(500))
|
|
86
|
+
.measurement_time(Duration::from_millis(2_500))
|
|
87
|
+
.sample_size(20)
|
|
88
|
+
.noise_threshold(0.10)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fn bench_commit_latency(c: &mut Criterion) {
|
|
92
|
+
let mut group = c.benchmark_group("wal/commit_latency");
|
|
93
|
+
|
|
94
|
+
// ---- baseline: no WAL ---------------------------------------------------
|
|
95
|
+
{
|
|
96
|
+
group.bench_function("no_wal", |b| {
|
|
97
|
+
b.iter_batched(
|
|
98
|
+
Database::in_memory,
|
|
99
|
+
|db| {
|
|
100
|
+
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
|
|
101
|
+
},
|
|
102
|
+
BatchSize::SmallInput,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- PerCommit (fsync per commit) --------------------------------------
|
|
108
|
+
{
|
|
109
|
+
group.bench_function("per_commit", |b| {
|
|
110
|
+
b.iter_batched(
|
|
111
|
+
|| {
|
|
112
|
+
let dir = ScratchDir::new("per-commit");
|
|
113
|
+
let db =
|
|
114
|
+
Database::open_with_wal(enabled(&dir.path, SyncMode::PerCommit)).unwrap();
|
|
115
|
+
(dir, db)
|
|
116
|
+
},
|
|
117
|
+
|(_dir, db)| {
|
|
118
|
+
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
|
|
119
|
+
},
|
|
120
|
+
BatchSize::SmallInput,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- Group (write-only on commit, bg flusher fsyncs) -------------------
|
|
126
|
+
{
|
|
127
|
+
group.bench_function("group", |b| {
|
|
128
|
+
b.iter_batched(
|
|
129
|
+
|| {
|
|
130
|
+
let dir = ScratchDir::new("group");
|
|
131
|
+
let db = Database::open_with_wal(enabled(
|
|
132
|
+
&dir.path,
|
|
133
|
+
SyncMode::Group { interval_ms: 50 },
|
|
134
|
+
))
|
|
135
|
+
.unwrap();
|
|
136
|
+
(dir, db)
|
|
137
|
+
},
|
|
138
|
+
|(_dir, db)| {
|
|
139
|
+
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
|
|
140
|
+
},
|
|
141
|
+
BatchSize::SmallInput,
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---- None (no fsync at all) --------------------------------------------
|
|
147
|
+
{
|
|
148
|
+
group.bench_function("none", |b| {
|
|
149
|
+
b.iter_batched(
|
|
150
|
+
|| {
|
|
151
|
+
let dir = ScratchDir::new("none");
|
|
152
|
+
let db = Database::open_with_wal(enabled(&dir.path, SyncMode::None)).unwrap();
|
|
153
|
+
(dir, db)
|
|
154
|
+
},
|
|
155
|
+
|(_dir, db)| {
|
|
156
|
+
black_box(db.execute("CREATE (:N {v: 1})", opts()).unwrap());
|
|
157
|
+
},
|
|
158
|
+
BatchSize::SmallInput,
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
group.finish();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn bench_recovery(c: &mut Criterion) {
|
|
167
|
+
let mut group = c.benchmark_group("wal/recovery");
|
|
168
|
+
|
|
169
|
+
// Time how long Database::open_with_wal takes on a directory that
|
|
170
|
+
// has N committed transactions waiting to be replayed. This bounds
|
|
171
|
+
// startup time on a crash-recovery boot.
|
|
172
|
+
for n in [100usize, 1_000].iter().copied() {
|
|
173
|
+
// Build the WAL once outside the iter loop.
|
|
174
|
+
let dir = ScratchDir::new(&format!("recovery-{}", n));
|
|
175
|
+
{
|
|
176
|
+
let db = Database::open_with_wal(enabled(&dir.path, SyncMode::PerCommit)).unwrap();
|
|
177
|
+
for _ in 0..n {
|
|
178
|
+
db.execute("CREATE (:N {v: 1})", opts()).unwrap();
|
|
179
|
+
}
|
|
180
|
+
// Drop to release file handles; bg flusher (none here) joins.
|
|
181
|
+
drop(db);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
group.bench_function(format!("replay_{}", n), |b| {
|
|
185
|
+
b.iter(|| {
|
|
186
|
+
let db = Database::open_with_wal(enabled(&dir.path, SyncMode::None)).unwrap();
|
|
187
|
+
black_box(db.node_count());
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Keep `dir` alive across the iter so the WAL files persist
|
|
192
|
+
// for every iteration. ScratchDir's Drop will clean up at
|
|
193
|
+
// bench end.
|
|
194
|
+
std::mem::forget(dir);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
group.finish();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
criterion_group! {
|
|
201
|
+
name = benches;
|
|
202
|
+
config = smoke_config();
|
|
203
|
+
targets = bench_commit_latency, bench_recovery,
|
|
204
|
+
}
|
|
205
|
+
criterion_main!(benches);
|