tensorfs 0.3.39__tar.gz → 0.3.41__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.
- {tensorfs-0.3.39 → tensorfs-0.3.41}/Cargo.lock +2 -2
- {tensorfs-0.3.39 → tensorfs-0.3.41}/Cargo.toml +1 -1
- {tensorfs-0.3.39 → tensorfs-0.3.41}/PKG-INFO +1 -1
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/resume_tests.rs +150 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived.rs +121 -72
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/meta.rs +74 -5
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-py/src/store.rs +36 -1
- {tensorfs-0.3.39 → tensorfs-0.3.41}/pyproject.toml +1 -1
- {tensorfs-0.3.39 → tensorfs-0.3.41}/python/tensorfs/__init__.py +6 -0
- tensorfs-0.3.41/python/tensorfs/_derived_channel.py +502 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/python/tensorfs/_ext.pyi +2 -0
- tensorfs-0.3.41/python/tensorfs/_source_channel.py +105 -0
- tensorfs-0.3.41/python/tensorfs/derived.py +233 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/Cargo.toml +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/build.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/examples/h3-swiglu-layout.md +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/examples/optional-cache-backfill.md +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/examples/resident-conversion.md +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/examples/resident_conversion_profile.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/b64.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/ckpt.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/conform.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/fetch.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/ingest.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/main.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/rd.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/repo.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/snap.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/bin/tfs/watch.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/cache_roots.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/canon.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/capability.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/catalog.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/cbor.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/checkpoint.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/corpus.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/adoption.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/estimate.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/graft_tests.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/progress.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/retention.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/derived/roots.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/dtype.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/durability.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/err.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/fetch.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/fill.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/fit.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/gc.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/header.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/home.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ids.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/carrier.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/convert.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/fingerprint.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/gguf.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/journal.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/mod.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/preflight.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/source.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/stamp.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/ingest/transaction.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/jcs.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/lib.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/limits.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/machine.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/manifest.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/project.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/providers.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/read.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/receipt.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/reclaim.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/registry.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/relation.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/repo_cache.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/repository.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/sha256.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/source_artifact.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/spec.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/staging.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/storage.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/store.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/api.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/http.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/hub.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/ledger.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/mod.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/policy.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/prefix.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/pull.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/push.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/ranged.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/transport/tests.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/src/vectors.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fetch_url_progress.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fit.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/blobs.jsonl +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_list.jsonl +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_repository.json +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/expected_rows.jsonl +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/header.cbor +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/manifest.json +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/fixtures/repo_apply/mutation.json +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/h3_swiglu.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/home.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/import_tree.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/instrument_profiles.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/liveness.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/read_plan.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/reclaim.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/reclaim_run.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/repo_cache.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/repo_store.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/resident_admission.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/sha256_backends.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/sharded_cas.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_artifacts.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_plan.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_prefixes.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/source_preflight.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/staging.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/store_boundaries.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/tensor_schema.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/version.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-core/tests/write_through.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-py/Cargo.toml +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-py/src/docs.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-py/src/errors.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/crates/tensorfs-py/src/lib.rs +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/python/tensorfs/errors.py +0 -0
- {tensorfs-0.3.39 → tensorfs-0.3.41}/python/tensorfs/py.typed +0 -0
|
@@ -421,7 +421,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
421
421
|
|
|
422
422
|
[[package]]
|
|
423
423
|
name = "tensorfs-core"
|
|
424
|
-
version = "0.3.
|
|
424
|
+
version = "0.3.41"
|
|
425
425
|
dependencies = [
|
|
426
426
|
"fs2",
|
|
427
427
|
"minicbor",
|
|
@@ -434,7 +434,7 @@ dependencies = [
|
|
|
434
434
|
|
|
435
435
|
[[package]]
|
|
436
436
|
name = "tensorfs-py"
|
|
437
|
-
version = "0.3.
|
|
437
|
+
version = "0.3.41"
|
|
438
438
|
dependencies = [
|
|
439
439
|
"pyo3",
|
|
440
440
|
"tensorfs-core",
|
|
@@ -918,3 +918,153 @@ fn source_free_restore_holds_survive_gc_until_begin_or_explicit_abandon() {
|
|
|
918
918
|
release(&source_meta, writer);
|
|
919
919
|
std::fs::remove_dir_all(source.root()).unwrap();
|
|
920
920
|
}
|
|
921
|
+
|
|
922
|
+
/// Opt-in generated-data profile. Payloads are tiny so metadata scaling is visible.
|
|
923
|
+
/// Run in release mode; elapsed time is evidence, never a flaky unit-test threshold.
|
|
924
|
+
#[test]
|
|
925
|
+
#[ignore]
|
|
926
|
+
fn profile_derived_transaction_scaling() {
|
|
927
|
+
use std::time::Instant;
|
|
928
|
+
let scenarios = [
|
|
929
|
+
(219, 1, 1),
|
|
930
|
+
(219, 1, 10),
|
|
931
|
+
(219, 4, 10),
|
|
932
|
+
(219, 32, 10),
|
|
933
|
+
(219, 128, 10),
|
|
934
|
+
(3968, 1, 10),
|
|
935
|
+
(3968, 4, 10),
|
|
936
|
+
(219, 1, 219),
|
|
937
|
+
];
|
|
938
|
+
for (keys, histories, writes) in scenarios {
|
|
939
|
+
let (store, meta, id, _) = fixture("row-profile");
|
|
940
|
+
let mut declaration = super::tests::created_declaration();
|
|
941
|
+
let prototype = declaration.components[0].add[0].clone();
|
|
942
|
+
declaration.components[0].add = (0..keys)
|
|
943
|
+
.map(|index| {
|
|
944
|
+
let mut tensor = prototype.clone();
|
|
945
|
+
tensor.key = format!("weight_{index:04}");
|
|
946
|
+
tensor.shape = vec![4];
|
|
947
|
+
tensor.parts[0].shape = vec![4];
|
|
948
|
+
tensor
|
|
949
|
+
})
|
|
950
|
+
.collect();
|
|
951
|
+
declaration.order = declaration.components[0]
|
|
952
|
+
.add
|
|
953
|
+
.iter()
|
|
954
|
+
.map(|tensor| ("model".into(), tensor.key.clone()))
|
|
955
|
+
.collect();
|
|
956
|
+
declaration.max_new_bytes = (keys * 16) as u64;
|
|
957
|
+
let writer = begin(&store, &meta, &id, 1, declaration, None).unwrap();
|
|
958
|
+
// Populate representative unrelated transactions without timing fixture creation.
|
|
959
|
+
let mut txn = meta.txn().unwrap();
|
|
960
|
+
let prototype = txn.rows[0].clone();
|
|
961
|
+
let row_bytes = crate::canon::write(&prototype.to_value()).len();
|
|
962
|
+
for index in 1..histories {
|
|
963
|
+
let mut row = prototype.clone();
|
|
964
|
+
row.id = ObjectRef::of(format!("history-{index}").as_bytes()).id();
|
|
965
|
+
txn.rows.push(row);
|
|
966
|
+
}
|
|
967
|
+
txn.commit().unwrap();
|
|
968
|
+
let started = Instant::now();
|
|
969
|
+
for _ in 0..20 {
|
|
970
|
+
check_writer(&meta, &id, 1).unwrap();
|
|
971
|
+
}
|
|
972
|
+
let fence_ms = started.elapsed().as_secs_f64() * 1000.0;
|
|
973
|
+
let mut part_ms = 0.0;
|
|
974
|
+
let mut checkpoint_ms = 0.0;
|
|
975
|
+
let mut prior = None;
|
|
976
|
+
for index in 0..writes {
|
|
977
|
+
let key = format!("weight_{index:04}");
|
|
978
|
+
let started = Instant::now();
|
|
979
|
+
add_part(
|
|
980
|
+
&store,
|
|
981
|
+
&meta,
|
|
982
|
+
&id,
|
|
983
|
+
1,
|
|
984
|
+
("model", &key, "value"),
|
|
985
|
+
&mut [index as u8; 16].as_slice(),
|
|
986
|
+
)
|
|
987
|
+
.unwrap();
|
|
988
|
+
part_ms += started.elapsed().as_secs_f64() * 1000.0;
|
|
989
|
+
let started = Instant::now();
|
|
990
|
+
let (_, head) =
|
|
991
|
+
checkpoint_progress(&store, &meta, &id, 1, "profile", "model", prior.as_ref())
|
|
992
|
+
.unwrap();
|
|
993
|
+
prior = head.head;
|
|
994
|
+
checkpoint_ms += started.elapsed().as_secs_f64() * 1000.0;
|
|
995
|
+
}
|
|
996
|
+
eprintln!("PROFILE {{\"keys\":{keys},\"histories\":{histories},\"writes\":{writes},\"row_bytes\":{row_bytes},\"fence20_ms\":{fence_ms:.3},\"add_ms\":{part_ms:.3},\"checkpoint_ms\":{checkpoint_ms:.3}}}");
|
|
997
|
+
fence(&meta, &id, 1).unwrap();
|
|
998
|
+
release(&meta, writer);
|
|
999
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
#[test]
|
|
1004
|
+
fn named_writer_operations_leave_other_rows_and_writer_lock_alone() {
|
|
1005
|
+
use rusqlite::{params, Connection};
|
|
1006
|
+
let (store, meta, first_id, declaration) = fixture("scoped-row");
|
|
1007
|
+
let second_id = ObjectRef::of(b"independent writer").id();
|
|
1008
|
+
let first = begin(&store, &meta, &first_id, 1, declaration.clone(), None).unwrap();
|
|
1009
|
+
let second = begin(&store, &meta, &second_id, 1, declaration.clone(), None).unwrap();
|
|
1010
|
+
let connection = Connection::open(crate::catalog::Catalog::path(store.root())).unwrap();
|
|
1011
|
+
let untouched: Vec<u8> = connection
|
|
1012
|
+
.query_row(
|
|
1013
|
+
"SELECT bytes FROM tensorfs_derived_transactions WHERE id=?1",
|
|
1014
|
+
[&second_id],
|
|
1015
|
+
|row| row.get(0),
|
|
1016
|
+
)
|
|
1017
|
+
.unwrap();
|
|
1018
|
+
// Corrupt unrelated history makes an accidental whole-table read fail, while
|
|
1019
|
+
// a scoped mutation must preserve it byte-for-byte and mutate only this writer.
|
|
1020
|
+
let damaged = ObjectRef::of(b"unrelated damaged history").id();
|
|
1021
|
+
connection
|
|
1022
|
+
.execute(
|
|
1023
|
+
"INSERT INTO tensorfs_derived_transactions(id,bytes) VALUES(?1,?2)",
|
|
1024
|
+
params![damaged, b"broken".as_slice()],
|
|
1025
|
+
)
|
|
1026
|
+
.unwrap();
|
|
1027
|
+
data(&store, &meta, &first_id, 1, 0x31);
|
|
1028
|
+
let after: Vec<u8> = connection
|
|
1029
|
+
.query_row(
|
|
1030
|
+
"SELECT bytes FROM tensorfs_derived_transactions WHERE id=?1",
|
|
1031
|
+
[&second_id],
|
|
1032
|
+
|row| row.get(0),
|
|
1033
|
+
)
|
|
1034
|
+
.unwrap();
|
|
1035
|
+
assert_eq!(untouched, after);
|
|
1036
|
+
// A read fence must succeed while an unrelated SQLite writer is active. The old
|
|
1037
|
+
// BEGIN IMMEDIATE observation instead hit the five-second busy timeout.
|
|
1038
|
+
connection.execute_batch("BEGIN IMMEDIATE").unwrap();
|
|
1039
|
+
check_writer(&meta, &second_id, 1).unwrap();
|
|
1040
|
+
assert!(completed(&meta, &second_id, 1).unwrap().0.is_empty());
|
|
1041
|
+
connection.execute_batch("ROLLBACK").unwrap();
|
|
1042
|
+
// A named operation does not parse unrelated history. Global inventory still
|
|
1043
|
+
// reports the damaged row rather than silently skipping it.
|
|
1044
|
+
check_writer(&meta, &first_id, 1).unwrap();
|
|
1045
|
+
assert!(meta.derived_rows().is_err());
|
|
1046
|
+
connection
|
|
1047
|
+
.execute(
|
|
1048
|
+
"DELETE FROM tensorfs_derived_transactions WHERE id=?1",
|
|
1049
|
+
[&damaged],
|
|
1050
|
+
)
|
|
1051
|
+
.unwrap();
|
|
1052
|
+
assert!(fence(&meta, &first_id, 1).unwrap());
|
|
1053
|
+
assert_eq!(
|
|
1054
|
+
check_writer(&meta, &first_id, 1).unwrap_err().code,
|
|
1055
|
+
Code::WRITER_FENCED
|
|
1056
|
+
);
|
|
1057
|
+
check_writer(&meta, &second_id, 1).unwrap();
|
|
1058
|
+
release(&meta, first);
|
|
1059
|
+
let resumed = begin(&store, &meta, &first_id, 2, declaration, None).unwrap();
|
|
1060
|
+
assert_eq!(completed(&meta, &first_id, 2).unwrap().0.len(), 1);
|
|
1061
|
+
assert_eq!(
|
|
1062
|
+
check_writer(&meta, &first_id, 1).unwrap_err().code,
|
|
1063
|
+
Code::WRITER_FENCED
|
|
1064
|
+
);
|
|
1065
|
+
fence(&meta, &first_id, 2).unwrap();
|
|
1066
|
+
fence(&meta, &second_id, 1).unwrap();
|
|
1067
|
+
release(&meta, resumed);
|
|
1068
|
+
release(&meta, second);
|
|
1069
|
+
std::fs::remove_dir_all(store.root()).unwrap();
|
|
1070
|
+
}
|
|
@@ -2005,6 +2005,19 @@ fn inspect_retained_source(
|
|
|
2005
2005
|
code: Code::ROOT_ABSENT,
|
|
2006
2006
|
detail: "source inspection resolved no source".into(),
|
|
2007
2007
|
})?;
|
|
2008
|
+
for component in &component_names {
|
|
2009
|
+
if !source
|
|
2010
|
+
.header
|
|
2011
|
+
.components
|
|
2012
|
+
.iter()
|
|
2013
|
+
.any(|(name, _)| name == component)
|
|
2014
|
+
{
|
|
2015
|
+
return refuse(
|
|
2016
|
+
Code::MISSING_FIELD,
|
|
2017
|
+
format!("source has no component {component:?}"),
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2008
2021
|
let configs = config_names
|
|
2009
2022
|
.iter()
|
|
2010
2023
|
.map(|name| {
|
|
@@ -2188,10 +2201,7 @@ pub fn begin(
|
|
|
2188
2201
|
};
|
|
2189
2202
|
// Byte verification can update the verification catalog. Keep it outside the SQL
|
|
2190
2203
|
// writer transaction, then compare the observed row before assigning this epoch.
|
|
2191
|
-
let initial = meta
|
|
2192
|
-
.derived_rows()?
|
|
2193
|
-
.into_iter()
|
|
2194
|
-
.find(|row| row.id == transaction);
|
|
2204
|
+
let initial = meta.derived_row(&transaction)?;
|
|
2195
2205
|
let mut row = match planned_row(
|
|
2196
2206
|
initial.as_ref(),
|
|
2197
2207
|
&transaction,
|
|
@@ -2220,7 +2230,7 @@ pub fn begin(
|
|
|
2220
2230
|
return Err(error);
|
|
2221
2231
|
}
|
|
2222
2232
|
let committed = (|| {
|
|
2223
|
-
let mut txn = meta.
|
|
2233
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
2224
2234
|
let current = txn.rows.iter_mut().find(|row| row.id == transaction);
|
|
2225
2235
|
if current.as_deref() != initial.as_ref() {
|
|
2226
2236
|
return refuse(
|
|
@@ -2430,8 +2440,8 @@ pub fn completed(
|
|
|
2430
2440
|
transaction: &str,
|
|
2431
2441
|
writer_session: u64,
|
|
2432
2442
|
) -> Result<(Vec<(String, String, String)>, Vec<String>)> {
|
|
2433
|
-
let mut
|
|
2434
|
-
let row = open_row(&mut
|
|
2443
|
+
let mut rows: Vec<_> = meta.derived_row(transaction)?.into_iter().collect();
|
|
2444
|
+
let row = open_row(&mut rows, transaction, writer_session)?;
|
|
2435
2445
|
Ok((
|
|
2436
2446
|
row.added_parts
|
|
2437
2447
|
.iter()
|
|
@@ -2451,7 +2461,7 @@ pub fn checkpoint_progress(
|
|
|
2451
2461
|
previous: Option<&ObjectRef>,
|
|
2452
2462
|
) -> Result<(String, crate::durability::Head)> {
|
|
2453
2463
|
let progress = {
|
|
2454
|
-
let mut txn = meta.
|
|
2464
|
+
let mut txn = meta.txn_for(transaction)?;
|
|
2455
2465
|
progress_row(open_row(&mut txn.rows, transaction, writer_session)?)
|
|
2456
2466
|
};
|
|
2457
2467
|
let plan = progress_plan(&progress);
|
|
@@ -2513,7 +2523,7 @@ pub fn checkpoint_progress(
|
|
|
2513
2523
|
objects,
|
|
2514
2524
|
None,
|
|
2515
2525
|
)?;
|
|
2516
|
-
let mut txn = meta.
|
|
2526
|
+
let mut txn = meta.txn_for(transaction)?;
|
|
2517
2527
|
let row = open_row(&mut txn.rows, transaction, writer_session)?;
|
|
2518
2528
|
retain_progress(store, row, exported.head.head.as_ref())?;
|
|
2519
2529
|
Ok((plan, exported.head))
|
|
@@ -2581,7 +2591,7 @@ pub(crate) fn retain_restore(
|
|
|
2581
2591
|
if let Some(object) = object {
|
|
2582
2592
|
require_payload_record(store, object, "restored checkpoint object")?;
|
|
2583
2593
|
}
|
|
2584
|
-
let txn = meta.
|
|
2594
|
+
let txn = meta.txn_for(&transaction)?;
|
|
2585
2595
|
let row = txn.rows.iter().find(|row| row.id == transaction);
|
|
2586
2596
|
let active = if let Some(row) = row {
|
|
2587
2597
|
if row.state != TransactionState::Open {
|
|
@@ -2704,8 +2714,8 @@ fn open_row<'a>(
|
|
|
2704
2714
|
|
|
2705
2715
|
pub fn check_writer(meta: &Meta, transaction: &str, writer_session: u64) -> Result<()> {
|
|
2706
2716
|
let transaction = transaction_id(transaction)?;
|
|
2707
|
-
let mut
|
|
2708
|
-
open_row(&mut
|
|
2717
|
+
let mut rows: Vec<_> = meta.derived_row(&transaction)?.into_iter().collect();
|
|
2718
|
+
open_row(&mut rows, &transaction, writer_session)?;
|
|
2709
2719
|
Ok(())
|
|
2710
2720
|
}
|
|
2711
2721
|
|
|
@@ -2716,6 +2726,83 @@ fn declaration(row: &TransactionRow) -> Result<Declaration> {
|
|
|
2716
2726
|
})?)
|
|
2717
2727
|
}
|
|
2718
2728
|
|
|
2729
|
+
/// The exact native payload limit, checked against the current writer before
|
|
2730
|
+
/// an embedding owner admits storage or asks a producer to supply any bytes.
|
|
2731
|
+
pub fn part_write_bound(
|
|
2732
|
+
meta: &Meta,
|
|
2733
|
+
transaction: &str,
|
|
2734
|
+
writer_session: u64,
|
|
2735
|
+
value: (&str, &str, &str),
|
|
2736
|
+
) -> Result<u64> {
|
|
2737
|
+
let transaction = transaction_id(transaction)?;
|
|
2738
|
+
let mut rows: Vec<_> = meta.derived_row(&transaction)?.into_iter().collect();
|
|
2739
|
+
let row = open_row(&mut rows, &transaction, writer_session)?;
|
|
2740
|
+
let part = declared_addition(&declaration(row)?, value)?;
|
|
2741
|
+
checked_bytes("derived part write", &part.shape, part.dtype)
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
pub fn config_write_bound(
|
|
2745
|
+
meta: &Meta,
|
|
2746
|
+
transaction: &str,
|
|
2747
|
+
writer_session: u64,
|
|
2748
|
+
name: &str,
|
|
2749
|
+
) -> Result<u64> {
|
|
2750
|
+
let transaction = transaction_id(transaction)?;
|
|
2751
|
+
let mut rows: Vec<_> = meta.derived_row(&transaction)?.into_iter().collect();
|
|
2752
|
+
let row = open_row(&mut rows, &transaction, writer_session)?;
|
|
2753
|
+
let declaration = declaration(row)?;
|
|
2754
|
+
require_added_config(&declaration, name)?;
|
|
2755
|
+
Ok(declaration.max_new_bytes.min(limits::DOC_MAX_BYTES as u64))
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
fn declared_addition(
|
|
2759
|
+
declaration: &Declaration,
|
|
2760
|
+
(component, tensor_key, role): (&str, &str, &str),
|
|
2761
|
+
) -> Result<PartDeclaration> {
|
|
2762
|
+
let part = declaration
|
|
2763
|
+
.components
|
|
2764
|
+
.iter()
|
|
2765
|
+
.find(|candidate| candidate.target == component)
|
|
2766
|
+
.and_then(|candidate| {
|
|
2767
|
+
candidate
|
|
2768
|
+
.add
|
|
2769
|
+
.iter()
|
|
2770
|
+
.find(|candidate| candidate.key == tensor_key)
|
|
2771
|
+
})
|
|
2772
|
+
.and_then(|candidate| {
|
|
2773
|
+
candidate
|
|
2774
|
+
.parts
|
|
2775
|
+
.iter()
|
|
2776
|
+
.find(|candidate| candidate.role == role)
|
|
2777
|
+
})
|
|
2778
|
+
.cloned()
|
|
2779
|
+
.ok_or_else(|| Refusal {
|
|
2780
|
+
code: Code::MISSING_FIELD,
|
|
2781
|
+
detail: format!("{component}/{tensor_key}#{role} is not a declared addition"),
|
|
2782
|
+
})?;
|
|
2783
|
+
if part.source.is_some() {
|
|
2784
|
+
return refuse(
|
|
2785
|
+
Code::TRANSACTION_CONFLICT,
|
|
2786
|
+
"grafted part cannot receive byte writes",
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
Ok(part)
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
fn require_added_config(declaration: &Declaration, name: &str) -> Result<()> {
|
|
2793
|
+
if declaration.configs.iter().any(|config| {
|
|
2794
|
+
matches!(config, ConfigDeclaration::Add { target }
|
|
2795
|
+
| ConfigDeclaration::Derive { target, .. } if target == name)
|
|
2796
|
+
}) {
|
|
2797
|
+
Ok(())
|
|
2798
|
+
} else {
|
|
2799
|
+
refuse(
|
|
2800
|
+
Code::MISSING_FIELD,
|
|
2801
|
+
format!("{name:?} is not a declared added config"),
|
|
2802
|
+
)
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2719
2806
|
pub fn add_part<R: Read>(
|
|
2720
2807
|
store: &Store,
|
|
2721
2808
|
meta: &Meta,
|
|
@@ -2726,40 +2813,11 @@ pub fn add_part<R: Read>(
|
|
|
2726
2813
|
) -> Result<crate::checkpoint::Written> {
|
|
2727
2814
|
let (component, tensor_key, role) = value;
|
|
2728
2815
|
let transaction = transaction_id(transaction)?;
|
|
2729
|
-
let
|
|
2730
|
-
let mut txn = meta.
|
|
2816
|
+
let expected = {
|
|
2817
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
2731
2818
|
let row = open_row(&mut txn.rows, &transaction, writer_session)?;
|
|
2732
|
-
|
|
2733
|
-
let expected = declaration
|
|
2734
|
-
.components
|
|
2735
|
-
.iter()
|
|
2736
|
-
.find(|candidate| candidate.target == component)
|
|
2737
|
-
.and_then(|candidate| {
|
|
2738
|
-
candidate
|
|
2739
|
-
.add
|
|
2740
|
-
.iter()
|
|
2741
|
-
.find(|candidate| candidate.key == tensor_key)
|
|
2742
|
-
})
|
|
2743
|
-
.and_then(|candidate| {
|
|
2744
|
-
candidate
|
|
2745
|
-
.parts
|
|
2746
|
-
.iter()
|
|
2747
|
-
.find(|candidate| candidate.role == role)
|
|
2748
|
-
})
|
|
2749
|
-
.cloned()
|
|
2750
|
-
.ok_or_else(|| Refusal {
|
|
2751
|
-
code: Code::MISSING_FIELD,
|
|
2752
|
-
detail: format!("{component}/{tensor_key}#{role} is not a declared addition"),
|
|
2753
|
-
})?;
|
|
2754
|
-
(declaration, expected)
|
|
2819
|
+
declared_addition(&declaration(row)?, value)?
|
|
2755
2820
|
};
|
|
2756
|
-
let _ = declaration;
|
|
2757
|
-
if expected.source.is_some() {
|
|
2758
|
-
return refuse(
|
|
2759
|
-
Code::TRANSACTION_CONFLICT,
|
|
2760
|
-
"grafted part cannot receive byte writes",
|
|
2761
|
-
);
|
|
2762
|
-
}
|
|
2763
2821
|
|
|
2764
2822
|
let written = checkpoint::objectize(
|
|
2765
2823
|
store,
|
|
@@ -2791,7 +2849,7 @@ pub fn add_part<R: Read>(
|
|
|
2791
2849
|
}
|
|
2792
2850
|
}
|
|
2793
2851
|
|
|
2794
|
-
let mut txn = meta.
|
|
2852
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
2795
2853
|
let row = open_row(&mut txn.rows, &transaction, writer_session)?;
|
|
2796
2854
|
if let Some(existing) = row
|
|
2797
2855
|
.added_parts
|
|
@@ -2825,39 +2883,31 @@ pub fn add_config<R: Read>(
|
|
|
2825
2883
|
reader: &mut R,
|
|
2826
2884
|
) -> Result<()> {
|
|
2827
2885
|
let transaction = transaction_id(transaction)?;
|
|
2828
|
-
{
|
|
2829
|
-
let mut txn = meta.
|
|
2886
|
+
let maximum = {
|
|
2887
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
2830
2888
|
let row = open_row(&mut txn.rows, &transaction, writer_session)?;
|
|
2831
2889
|
let declaration = declaration(row)?;
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
ConfigDeclaration::Add { target }
|
|
2836
|
-
| ConfigDeclaration::Derive { target, .. }
|
|
2837
|
-
if target == config_name
|
|
2838
|
-
)
|
|
2839
|
-
}) {
|
|
2840
|
-
return refuse(
|
|
2841
|
-
Code::MISSING_FIELD,
|
|
2842
|
-
format!("{config_name:?} is not a declared added config"),
|
|
2843
|
-
);
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2890
|
+
require_added_config(&declaration, config_name)?;
|
|
2891
|
+
declaration.max_new_bytes.min(limits::DOC_MAX_BYTES as u64)
|
|
2892
|
+
};
|
|
2846
2893
|
|
|
2847
2894
|
let mut input = Vec::new();
|
|
2848
2895
|
reader
|
|
2849
|
-
.take(
|
|
2896
|
+
.take(maximum + 1)
|
|
2850
2897
|
.read_to_end(&mut input)
|
|
2851
2898
|
.map_err(|error| Refusal {
|
|
2852
2899
|
code: Code::IO_FAILED,
|
|
2853
2900
|
detail: format!("read config: {error}"),
|
|
2854
2901
|
})?;
|
|
2855
|
-
if input.len() >
|
|
2856
|
-
return refuse(
|
|
2902
|
+
if input.len() as u64 > maximum {
|
|
2903
|
+
return refuse(
|
|
2904
|
+
Code::SIZE_CAP,
|
|
2905
|
+
"config exceeds the output or document byte cap",
|
|
2906
|
+
);
|
|
2857
2907
|
}
|
|
2858
2908
|
let config = crate::header::canonical_config(&format!("config {config_name:?}"), &input)?;
|
|
2859
2909
|
|
|
2860
|
-
let mut txn = meta.
|
|
2910
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
2861
2911
|
let row = open_row(&mut txn.rows, &transaction, writer_session)?;
|
|
2862
2912
|
if let Some(existing) = row
|
|
2863
2913
|
.added_configs
|
|
@@ -3006,7 +3056,7 @@ pub fn commit(
|
|
|
3006
3056
|
) -> Result<ReceiptFacts> {
|
|
3007
3057
|
let transaction = transaction_id(transaction)?;
|
|
3008
3058
|
let row_before = {
|
|
3009
|
-
let mut txn = meta.
|
|
3059
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
3010
3060
|
open_row(&mut txn.rows, &transaction, writer_session)?.clone()
|
|
3011
3061
|
};
|
|
3012
3062
|
let declaration = declaration(&row_before)?;
|
|
@@ -3028,7 +3078,7 @@ pub fn commit(
|
|
|
3028
3078
|
let (inherited_payload_objects, inherited_payload_bytes) =
|
|
3029
3079
|
inherit_observation(&header, &row_before)?;
|
|
3030
3080
|
|
|
3031
|
-
let mut txn = meta.
|
|
3081
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
3032
3082
|
let row = open_row(&mut txn.rows, &transaction, writer_session)?;
|
|
3033
3083
|
if row.added_parts != row_before.added_parts || row.added_configs != row_before.added_configs {
|
|
3034
3084
|
return refuse(
|
|
@@ -3058,8 +3108,7 @@ pub enum Lookup {
|
|
|
3058
3108
|
|
|
3059
3109
|
pub fn lookup(store: &Store, meta: &Meta, transaction: &str) -> Result<Lookup> {
|
|
3060
3110
|
let transaction = transaction_id(transaction)?;
|
|
3061
|
-
let
|
|
3062
|
-
let Some(row) = rows.iter().find(|row| row.id == transaction) else {
|
|
3111
|
+
let Some(ref row) = meta.derived_row(&transaction)? else {
|
|
3063
3112
|
return Ok(Lookup::Absent);
|
|
3064
3113
|
};
|
|
3065
3114
|
match row.state {
|
|
@@ -3079,7 +3128,7 @@ pub fn lookup(store: &Store, meta: &Meta, transaction: &str) -> Result<Lookup> {
|
|
|
3079
3128
|
|
|
3080
3129
|
pub fn fence(meta: &Meta, transaction: &str, writer_session: u64) -> Result<bool> {
|
|
3081
3130
|
let transaction = transaction_id(transaction)?;
|
|
3082
|
-
let mut txn = meta.
|
|
3131
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
3083
3132
|
let row = txn
|
|
3084
3133
|
.rows
|
|
3085
3134
|
.iter_mut()
|
|
@@ -3098,7 +3147,7 @@ pub fn fence(meta: &Meta, transaction: &str, writer_session: u64) -> Result<bool
|
|
|
3098
3147
|
|
|
3099
3148
|
pub fn abandon(store: &Store, meta: &Meta, transaction: &str) -> Result<Option<ReceiptFacts>> {
|
|
3100
3149
|
let transaction = transaction_id(transaction)?;
|
|
3101
|
-
let mut txn = meta.
|
|
3150
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
3102
3151
|
let result = match txn.rows.iter_mut().find(|row| row.id == transaction) {
|
|
3103
3152
|
None => {
|
|
3104
3153
|
txn.rows.push(TransactionRow {
|
|
@@ -3270,7 +3319,7 @@ fn effective_disposition(
|
|
|
3270
3319
|
|
|
3271
3320
|
pub fn dispose(store: &Store, meta: &Meta, transaction: &str) -> Result<DispositionResult> {
|
|
3272
3321
|
let transaction = transaction_id(transaction)?;
|
|
3273
|
-
let mut txn = meta.
|
|
3322
|
+
let mut txn = meta.txn_for(&transaction)?;
|
|
3274
3323
|
let index = txn
|
|
3275
3324
|
.rows
|
|
3276
3325
|
.iter()
|
|
@@ -8,7 +8,7 @@ use std::path::PathBuf;
|
|
|
8
8
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
9
9
|
|
|
10
10
|
use fs2::FileExt;
|
|
11
|
-
use rusqlite::{params, Connection};
|
|
11
|
+
use rusqlite::{params, Connection, OptionalExtension};
|
|
12
12
|
|
|
13
13
|
use crate::err::{refuse, Code, Refusal, Result};
|
|
14
14
|
use crate::ids::ascii_name;
|
|
@@ -92,7 +92,33 @@ impl Meta {
|
|
|
92
92
|
.execute_batch("BEGIN IMMEDIATE")
|
|
93
93
|
.map_err(|error| sql("begin derived transaction", error))?;
|
|
94
94
|
let rows = read_derived(&connection)?;
|
|
95
|
-
Ok(Txn {
|
|
95
|
+
Ok(Txn {
|
|
96
|
+
connection,
|
|
97
|
+
rows,
|
|
98
|
+
selected: None,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// A writer mutates its own primary-key row; unrelated writers and retained history
|
|
103
|
+
/// must neither be decoded nor rewritten. The existing write lock still serializes
|
|
104
|
+
/// epoch changes, roots and accepted parts against global lifecycle operations.
|
|
105
|
+
pub(crate) fn txn_for(&self, id: &str) -> Result<Txn> {
|
|
106
|
+
let connection = self.connection()?;
|
|
107
|
+
connection
|
|
108
|
+
.execute_batch("BEGIN IMMEDIATE")
|
|
109
|
+
.map_err(|error| sql("begin derived transaction", error))?;
|
|
110
|
+
let rows = read_derived_row(&connection, id)?.into_iter().collect();
|
|
111
|
+
Ok(Txn {
|
|
112
|
+
connection,
|
|
113
|
+
rows,
|
|
114
|
+
selected: Some(id.to_string()),
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Observations do not acquire SQLite's writer lock. Source reads check this both
|
|
119
|
+
/// before and after I/O, so a concurrent fence still invalidates the read.
|
|
120
|
+
pub(crate) fn derived_row(&self, id: &str) -> Result<Option<crate::derived::TransactionRow>> {
|
|
121
|
+
read_derived_row(&self.connection()?, id)
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
pub(crate) fn derived_rows(&self) -> Result<Vec<crate::derived::TransactionRow>> {
|
|
@@ -162,6 +188,7 @@ impl Meta {
|
|
|
162
188
|
pub(crate) struct Txn {
|
|
163
189
|
connection: Connection,
|
|
164
190
|
pub(crate) rows: Vec<crate::derived::TransactionRow>,
|
|
191
|
+
selected: Option<String>,
|
|
165
192
|
}
|
|
166
193
|
|
|
167
194
|
impl Txn {
|
|
@@ -176,9 +203,24 @@ impl Txn {
|
|
|
176
203
|
"duplicate derived transaction id",
|
|
177
204
|
);
|
|
178
205
|
}
|
|
179
|
-
self.
|
|
180
|
-
.
|
|
181
|
-
|
|
206
|
+
if let Some(id) = &self.selected {
|
|
207
|
+
if self.rows.len() > 1 || self.rows.iter().any(|row| &row.id != id) {
|
|
208
|
+
return refuse(
|
|
209
|
+
Code::TRANSACTION_CONFLICT,
|
|
210
|
+
"scoped transaction changed another writer",
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
self.connection
|
|
214
|
+
.execute(
|
|
215
|
+
"DELETE FROM tensorfs_derived_transactions WHERE id=?1",
|
|
216
|
+
[id],
|
|
217
|
+
)
|
|
218
|
+
.map_err(|error| sql("replace derived transaction", error))?;
|
|
219
|
+
} else {
|
|
220
|
+
self.connection
|
|
221
|
+
.execute("DELETE FROM tensorfs_derived_transactions", [])
|
|
222
|
+
.map_err(|error| sql("clear derived transactions", error))?;
|
|
223
|
+
}
|
|
182
224
|
for row in &self.rows {
|
|
183
225
|
let bytes = crate::canon::write(&row.to_value());
|
|
184
226
|
self.connection
|
|
@@ -194,6 +236,33 @@ impl Txn {
|
|
|
194
236
|
}
|
|
195
237
|
}
|
|
196
238
|
|
|
239
|
+
fn read_derived_row(
|
|
240
|
+
connection: &Connection,
|
|
241
|
+
id: &str,
|
|
242
|
+
) -> Result<Option<crate::derived::TransactionRow>> {
|
|
243
|
+
let bytes = connection
|
|
244
|
+
.query_row(
|
|
245
|
+
"SELECT bytes FROM tensorfs_derived_transactions WHERE id=?1",
|
|
246
|
+
[id],
|
|
247
|
+
|row| row.get::<_, Vec<u8>>(0),
|
|
248
|
+
)
|
|
249
|
+
.optional()
|
|
250
|
+
.map_err(|error| sql("read derived transaction", error))?;
|
|
251
|
+
bytes
|
|
252
|
+
.map(|bytes| {
|
|
253
|
+
let value = crate::canon::parse_canonical(&bytes, limits::DOC_MAX_BYTES)?;
|
|
254
|
+
let row = crate::derived::TransactionRow::from_value(&value)?;
|
|
255
|
+
if row.id != id {
|
|
256
|
+
return refuse(
|
|
257
|
+
Code::TRANSACTION_CONFLICT,
|
|
258
|
+
"derived row differs from its primary key",
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
Ok(row)
|
|
262
|
+
})
|
|
263
|
+
.transpose()
|
|
264
|
+
}
|
|
265
|
+
|
|
197
266
|
fn read_derived(connection: &Connection) -> Result<Vec<crate::derived::TransactionRow>> {
|
|
198
267
|
let mut statement = connection
|
|
199
268
|
.prepare("SELECT bytes FROM tensorfs_derived_transactions ORDER BY id")
|
|
@@ -1864,7 +1864,12 @@ impl PyStore {
|
|
|
1864
1864
|
let identity = source_facts(py, std::slice::from_ref(&inspection.view))?;
|
|
1865
1865
|
out.set_item("source", identity.get_item(0)?)?;
|
|
1866
1866
|
let component_rows = PyList::empty(py);
|
|
1867
|
-
|
|
1867
|
+
// The selected fact is a normalized set. Author inspection must retain
|
|
1868
|
+
// the header's construction traversal, including nonlexical components.
|
|
1869
|
+
for (component, _) in &inspection.view.header.components {
|
|
1870
|
+
if !inspection.view.fact.components.contains(component) {
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1868
1873
|
let row = PyDict::new(py);
|
|
1869
1874
|
row.set_item("name", component)?;
|
|
1870
1875
|
row.set_item(
|
|
@@ -2338,6 +2343,36 @@ impl PyDerivedWriter {
|
|
|
2338
2343
|
|
|
2339
2344
|
#[pymethods]
|
|
2340
2345
|
impl PyDerivedWriter {
|
|
2346
|
+
fn part_write_bound(
|
|
2347
|
+
&self,
|
|
2348
|
+
py: Python<'_>,
|
|
2349
|
+
component: &str,
|
|
2350
|
+
key: &str,
|
|
2351
|
+
role: &str,
|
|
2352
|
+
) -> PyResult<u64> {
|
|
2353
|
+
py.detach(|| {
|
|
2354
|
+
derived::part_write_bound(
|
|
2355
|
+
&self.meta,
|
|
2356
|
+
&self.transaction_id,
|
|
2357
|
+
self.writer_session_id,
|
|
2358
|
+
(component, key, role),
|
|
2359
|
+
)
|
|
2360
|
+
})
|
|
2361
|
+
.or_refuse(py)
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
fn config_write_bound(&self, py: Python<'_>, name: &str) -> PyResult<u64> {
|
|
2365
|
+
py.detach(|| {
|
|
2366
|
+
derived::config_write_bound(
|
|
2367
|
+
&self.meta,
|
|
2368
|
+
&self.transaction_id,
|
|
2369
|
+
self.writer_session_id,
|
|
2370
|
+
name,
|
|
2371
|
+
)
|
|
2372
|
+
})
|
|
2373
|
+
.or_refuse(py)
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2341
2376
|
/// Effective exact sources after TensorFS has checked installed identity and closure.
|
|
2342
2377
|
fn sources<'p>(&self, py: Python<'p>) -> PyResult<Bound<'p, PyList>> {
|
|
2343
2378
|
source_facts(py, &self.source_views)
|