@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +65 -64
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
- package/dist/engine-wasm/wasm/lix_engine.js +130 -118
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +33 -26
- package/dist/open-lix.js +10 -10
- package/dist/sqlite/index.js +86 -30
- package/dist-engine-src/README.md +3 -3
- package/dist-engine-src/src/backend/capabilities.rs +67 -0
- package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
- package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
- package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
- package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
- package/dist-engine-src/src/backend/conformance/model.rs +28 -0
- package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
- package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
- package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
- package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
- package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
- package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
- package/dist-engine-src/src/backend/conformance/write.rs +16 -0
- package/dist-engine-src/src/backend/error.rs +94 -0
- package/dist-engine-src/src/backend/in_memory.rs +670 -0
- package/dist-engine-src/src/backend/mod.rs +36 -9
- package/dist-engine-src/src/backend/predicate.rs +80 -0
- package/dist-engine-src/src/backend/traits.rs +260 -0
- package/dist-engine-src/src/backend/types.rs +224 -81
- package/dist-engine-src/src/binary_cas/context.rs +8 -8
- package/dist-engine-src/src/binary_cas/kv.rs +234 -259
- package/dist-engine-src/src/{version → branch}/context.rs +12 -12
- package/dist-engine-src/src/branch/lifecycle.rs +221 -0
- package/dist-engine-src/src/branch/mod.rs +13 -0
- package/dist-engine-src/src/branch/refs.rs +321 -0
- package/dist-engine-src/src/branch/stage_rows.rs +67 -0
- package/dist-engine-src/src/branch/types.rs +21 -0
- package/dist-engine-src/src/catalog/context.rs +18 -18
- package/dist-engine-src/src/catalog/snapshot.rs +8 -8
- package/dist-engine-src/src/changelog/bench_support.rs +785 -0
- package/dist-engine-src/src/changelog/change.rs +1 -0
- package/dist-engine-src/src/changelog/codec.rs +497 -0
- package/dist-engine-src/src/changelog/commit.rs +1 -0
- package/dist-engine-src/src/changelog/context.rs +1614 -0
- package/dist-engine-src/src/changelog/mod.rs +29 -0
- package/dist-engine-src/src/changelog/store.rs +163 -0
- package/dist-engine-src/src/changelog/test_support.rs +54 -0
- package/dist-engine-src/src/changelog/types.rs +213 -0
- package/dist-engine-src/src/commit_graph/context.rs +317 -274
- package/dist-engine-src/src/commit_graph/mod.rs +2 -4
- package/dist-engine-src/src/commit_graph/types.rs +22 -42
- package/dist-engine-src/src/commit_graph/walker.rs +133 -103
- package/dist-engine-src/src/common/error.rs +52 -18
- package/dist-engine-src/src/common/identity.rs +2 -2
- package/dist-engine-src/src/common/mod.rs +1 -1
- package/dist-engine-src/src/domain.rs +42 -46
- package/dist-engine-src/src/engine.rs +74 -96
- package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
- package/dist-engine-src/src/functions/context.rs +56 -52
- package/dist-engine-src/src/functions/state.rs +51 -52
- package/dist-engine-src/src/init.rs +288 -154
- package/dist-engine-src/src/json_store/context.rs +15 -266
- package/dist-engine-src/src/json_store/mod.rs +26 -0
- package/dist-engine-src/src/json_store/store.rs +103 -718
- package/dist-engine-src/src/json_store/types.rs +4 -9
- package/dist-engine-src/src/lib.rs +49 -19
- package/dist-engine-src/src/live_state/context.rs +654 -790
- package/dist-engine-src/src/live_state/mod.rs +9 -3
- package/dist-engine-src/src/live_state/overlay.rs +4 -4
- package/dist-engine-src/src/live_state/types.rs +30 -21
- package/dist-engine-src/src/live_state/visibility.rs +514 -71
- package/dist-engine-src/src/plugin/install.rs +48 -48
- package/dist-engine-src/src/plugin/manifest.rs +7 -7
- package/dist-engine-src/src/plugin/materializer.rs +0 -275
- package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
- package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
- package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
- package/dist-engine-src/src/schema/compatibility.rs +11 -11
- package/dist-engine-src/src/schema/definition.json +2 -2
- package/dist-engine-src/src/schema/definition.rs +5 -5
- package/dist-engine-src/src/schema/key.rs +3 -3
- package/dist-engine-src/src/schema/mod.rs +1 -1
- package/dist-engine-src/src/schema/tests.rs +18 -18
- package/dist-engine-src/src/session/context.rs +803 -148
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +223 -83
- package/dist-engine-src/src/session/merge/analysis.rs +9 -3
- package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
- package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
- package/dist-engine-src/src/session/merge/mod.rs +5 -6
- package/dist-engine-src/src/session/merge/stats.rs +7 -11
- package/dist-engine-src/src/session/mod.rs +15 -12
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +495 -14
- package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
- package/dist-engine-src/src/sql2/bind/error.rs +5 -0
- package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
- package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
- package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
- package/dist-engine-src/src/sql2/bind/read.rs +65 -0
- package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
- package/dist-engine-src/src/sql2/bind/table.rs +273 -0
- package/dist-engine-src/src/sql2/bind/write.rs +86 -0
- package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
- package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
- package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
- package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
- package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
- package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
- package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
- package/dist-engine-src/src/sql2/context.rs +36 -30
- package/dist-engine-src/src/sql2/error.rs +1 -1
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
- package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
- package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
- package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
- package/dist-engine-src/src/sql2/exec/write.rs +661 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
- package/dist-engine-src/src/sql2/history_projection.rs +8 -8
- package/dist-engine-src/src/sql2/history_route.rs +35 -31
- package/dist-engine-src/src/sql2/mod.rs +28 -23
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
- package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
- package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
- package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
- package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
- package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
- package/dist-engine-src/src/sql2/plan/write.rs +147 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
- package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
- package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
- package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
- package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
- package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
- package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
- package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
- package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
- package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
- package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
- package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
- package/dist-engine-src/src/sql2/read_only.rs +2 -2
- package/dist-engine-src/src/sql2/session.rs +47 -96
- package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
- package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
- package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
- package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
- package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
- package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
- package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
- package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
- package/dist-engine-src/src/storage/conformance.rs +399 -0
- package/dist-engine-src/src/storage/context.rs +552 -288
- package/dist-engine-src/src/storage/mod.rs +48 -10
- package/dist-engine-src/src/storage/point.rs +440 -0
- package/dist-engine-src/src/storage/read_scope.rs +43 -64
- package/dist-engine-src/src/storage/reader.rs +867 -0
- package/dist-engine-src/src/storage/scan.rs +784 -0
- package/dist-engine-src/src/storage/spaces.rs +236 -0
- package/dist-engine-src/src/storage/stats.rs +80 -0
- package/dist-engine-src/src/storage/write_set.rs +962 -0
- package/dist-engine-src/src/storage_bench.rs +136 -4828
- package/dist-engine-src/src/test_support.rs +360 -138
- package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
- package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
- package/dist-engine-src/src/tracked_state/context.rs +1927 -993
- package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
- package/dist-engine-src/src/tracked_state/merge.rs +74 -88
- package/dist-engine-src/src/tracked_state/mod.rs +19 -16
- package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
- package/dist-engine-src/src/tracked_state/storage.rs +243 -191
- package/dist-engine-src/src/tracked_state/tree.rs +247 -371
- package/dist-engine-src/src/tracked_state/types.rs +49 -42
- package/dist-engine-src/src/transaction/bench_support.rs +407 -0
- package/dist-engine-src/src/transaction/commit.rs +821 -713
- package/dist-engine-src/src/transaction/context.rs +705 -600
- package/dist-engine-src/src/transaction/mod.rs +13 -2
- package/dist-engine-src/src/transaction/normalization.rs +63 -76
- package/dist-engine-src/src/transaction/prep.rs +13 -13
- package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
- package/dist-engine-src/src/transaction/staging.rs +228 -434
- package/dist-engine-src/src/transaction/types.rs +41 -98
- package/dist-engine-src/src/transaction/validation.rs +382 -446
- package/dist-engine-src/src/untracked_state/codec.rs +337 -29
- package/dist-engine-src/src/untracked_state/context.rs +7 -7
- package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
- package/dist-engine-src/src/untracked_state/mod.rs +1 -1
- package/dist-engine-src/src/untracked_state/storage.rs +659 -157
- package/dist-engine-src/src/untracked_state/types.rs +21 -21
- package/package.json +71 -68
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -110
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3533
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
use crate::json_store::compression::{compress_json_payload, decode_json_zstd_payload};
|
|
2
2
|
use crate::json_store::encoded::{EncodedJson, JsonCodec};
|
|
3
3
|
use crate::json_store::types::{JsonReadScopeRef, JsonRef};
|
|
4
|
-
use crate::storage::{
|
|
4
|
+
use crate::storage::{PointReadPlan, StorageRead, StorageSpace};
|
|
5
|
+
use crate::storage::{StorageGetOptions, StorageKey, StorageProjectedValue, StorageSpaceId};
|
|
5
6
|
use crate::LixError;
|
|
7
|
+
use bytes::Bytes;
|
|
6
8
|
use std::borrow::Cow;
|
|
7
9
|
use std::collections::HashMap;
|
|
8
10
|
|
|
9
11
|
pub(crate) const JSON_NAMESPACE: &str = "json_store.json";
|
|
10
|
-
pub(crate) const
|
|
12
|
+
pub(crate) const JSON_SPACE: StorageSpace =
|
|
13
|
+
StorageSpace::new(StorageSpaceId(0x0002_0001), JSON_NAMESPACE);
|
|
11
14
|
const STORED_JSON_MAGIC: &[u8] = b"lix-json:v1";
|
|
12
15
|
const STORED_JSON_HEADER_LEN: usize = STORED_JSON_MAGIC.len() + 1 + 8;
|
|
13
|
-
const STORED_JSON_PACK_MAGIC: &[u8] = b"lix-json-pack:v2";
|
|
14
|
-
const STORED_JSON_PACK_ENTRY_HEADER_LEN: usize = 32 + 1 + 4 + 4 + 4;
|
|
15
16
|
const ZSTD_MIN_JSON_BYTES: usize = 16 * 1024;
|
|
16
17
|
const MIN_ZSTD_SAVINGS_BYTES: usize = 128;
|
|
17
18
|
|
|
@@ -21,17 +22,6 @@ struct StoredJsonPayload<'a> {
|
|
|
21
22
|
data: &'a [u8],
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
struct JsonPackLayout {
|
|
25
|
-
directory_start: usize,
|
|
26
|
-
payload_start: usize,
|
|
27
|
-
count: usize,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
struct JsonPackEntry<'a> {
|
|
31
|
-
hash: [u8; 32],
|
|
32
|
-
payload: StoredJsonPayload<'a>,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
25
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
36
26
|
enum JsonHashCheck {
|
|
37
27
|
/// Hot reads trust the local storage layer and pack directory. Content
|
|
@@ -41,12 +31,6 @@ enum JsonHashCheck {
|
|
|
41
31
|
Verify,
|
|
42
32
|
}
|
|
43
33
|
|
|
44
|
-
enum OrderedSinglePackProbe {
|
|
45
|
-
Hit(Vec<Option<Vec<u8>>>),
|
|
46
|
-
MissPresent(Vec<u8>),
|
|
47
|
-
MissAbsent,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
34
|
fn raw_json_ref_for_content(json: &str) -> JsonRef {
|
|
51
35
|
JsonRef::from_hash(blake3::hash(json.as_bytes()))
|
|
52
36
|
}
|
|
@@ -107,76 +91,6 @@ pub(crate) fn encode_direct_json_payload(encoded_json: &EncodedJson<'_>) -> Vec<
|
|
|
107
91
|
encode_stored_json_payload(encoded_json)
|
|
108
92
|
}
|
|
109
93
|
|
|
110
|
-
pub(crate) fn pack_key(commit_id: &str, pack_id: u32) -> Vec<u8> {
|
|
111
|
-
let commit_id = commit_id.as_bytes();
|
|
112
|
-
let mut key = Vec::with_capacity(4 + commit_id.len() + 4);
|
|
113
|
-
key.extend_from_slice(&(commit_id.len() as u32).to_be_bytes());
|
|
114
|
-
key.extend_from_slice(commit_id);
|
|
115
|
-
key.extend_from_slice(&pack_id.to_be_bytes());
|
|
116
|
-
key
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
pub(crate) fn decode_json_pack_refs(bytes: &[u8]) -> Result<Vec<JsonRef>, LixError> {
|
|
120
|
-
let layout = json_pack_layout(bytes)?;
|
|
121
|
-
let mut refs = Vec::with_capacity(layout.count);
|
|
122
|
-
for index in 0..layout.count {
|
|
123
|
-
refs.push(JsonRef::from_hash_bytes(
|
|
124
|
-
json_pack_entry(bytes, &layout, index)?.hash,
|
|
125
|
-
));
|
|
126
|
-
}
|
|
127
|
-
Ok(refs)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
pub(crate) fn encode_json_pack(entries: &[&EncodedJson<'_>]) -> Result<Vec<u8>, LixError> {
|
|
131
|
-
let mut directory_len =
|
|
132
|
-
STORED_JSON_PACK_MAGIC.len() + 4 + entries.len() * STORED_JSON_PACK_ENTRY_HEADER_LEN;
|
|
133
|
-
let payload_len = entries
|
|
134
|
-
.iter()
|
|
135
|
-
.map(|entry| entry.data.as_ref().len())
|
|
136
|
-
.sum::<usize>();
|
|
137
|
-
let mut out = Vec::with_capacity(directory_len + payload_len);
|
|
138
|
-
out.extend_from_slice(STORED_JSON_PACK_MAGIC);
|
|
139
|
-
out.extend_from_slice(&(entries.len() as u32).to_be_bytes());
|
|
140
|
-
|
|
141
|
-
let mut offset = 0usize;
|
|
142
|
-
for entry in entries {
|
|
143
|
-
let data = entry.data.as_ref();
|
|
144
|
-
out.extend_from_slice(entry.json_ref.as_hash_bytes());
|
|
145
|
-
out.push(json_codec_byte(entry.codec));
|
|
146
|
-
out.extend_from_slice(&json_pack_u32(
|
|
147
|
-
entry.uncompressed_len,
|
|
148
|
-
"uncompressed length",
|
|
149
|
-
)?);
|
|
150
|
-
out.extend_from_slice(&json_pack_u32(offset, "payload offset")?);
|
|
151
|
-
out.extend_from_slice(&json_pack_u32(data.len(), "payload length")?);
|
|
152
|
-
offset = offset.checked_add(data.len()).ok_or_else(|| {
|
|
153
|
-
LixError::new(
|
|
154
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
155
|
-
"json_store pack payload offset overflow",
|
|
156
|
-
)
|
|
157
|
-
})?;
|
|
158
|
-
}
|
|
159
|
-
for entry in entries {
|
|
160
|
-
out.extend_from_slice(entry.data.as_ref());
|
|
161
|
-
}
|
|
162
|
-
directory_len = out.len() - payload_len;
|
|
163
|
-
debug_assert_eq!(
|
|
164
|
-
directory_len,
|
|
165
|
-
STORED_JSON_PACK_MAGIC.len() + 4 + entries.len() * STORED_JSON_PACK_ENTRY_HEADER_LEN
|
|
166
|
-
);
|
|
167
|
-
Ok(out)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
fn json_pack_u32(value: usize, field: &str) -> Result<[u8; 4], LixError> {
|
|
171
|
-
let value = u32::try_from(value).map_err(|_| {
|
|
172
|
-
LixError::new(
|
|
173
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
174
|
-
format!("json_store pack {field} exceeds u32"),
|
|
175
|
-
)
|
|
176
|
-
})?;
|
|
177
|
-
Ok(value.to_be_bytes())
|
|
178
|
-
}
|
|
179
|
-
|
|
180
94
|
pub(crate) fn encode_json_bytes_for_storage(bytes: &[u8]) -> Result<(JsonRef, Vec<u8>), LixError> {
|
|
181
95
|
let json = std::str::from_utf8(bytes).map_err(|error| {
|
|
182
96
|
LixError::new(
|
|
@@ -198,21 +112,13 @@ pub(crate) fn encode_json_str_for_storage_with_ref(
|
|
|
198
112
|
}
|
|
199
113
|
|
|
200
114
|
async fn load_json_bytes_direct(
|
|
201
|
-
store: &
|
|
115
|
+
store: &(impl StorageRead + ?Sized),
|
|
202
116
|
json_ref: &JsonRef,
|
|
203
117
|
) -> Result<Option<Vec<u8>>, LixError> {
|
|
204
|
-
let result = store
|
|
205
|
-
.get_values(KvGetRequest {
|
|
206
|
-
groups: vec![KvGetGroup {
|
|
207
|
-
namespace: JSON_NAMESPACE.to_string(),
|
|
208
|
-
keys: vec![json_ref.as_hash_bytes().to_vec()],
|
|
209
|
-
}],
|
|
210
|
-
})
|
|
211
|
-
.await?
|
|
212
|
-
.groups
|
|
118
|
+
let result = load_values(store, JSON_SPACE, vec![json_ref.as_hash_bytes().to_vec()])?
|
|
213
119
|
.into_iter()
|
|
214
120
|
.next()
|
|
215
|
-
.
|
|
121
|
+
.flatten();
|
|
216
122
|
let Some(bytes) = result else {
|
|
217
123
|
return Ok(None);
|
|
218
124
|
};
|
|
@@ -222,9 +128,9 @@ async fn load_json_bytes_direct(
|
|
|
222
128
|
}
|
|
223
129
|
|
|
224
130
|
pub(crate) async fn load_json_bytes_many_in_scope(
|
|
225
|
-
store: &
|
|
131
|
+
store: &(impl StorageRead + ?Sized),
|
|
226
132
|
json_refs: &[JsonRef],
|
|
227
|
-
scope: JsonReadScopeRef
|
|
133
|
+
scope: JsonReadScopeRef,
|
|
228
134
|
) -> Result<Vec<Option<Vec<u8>>>, LixError> {
|
|
229
135
|
load_json_bytes_many_in_scope_with_hash_check(
|
|
230
136
|
store,
|
|
@@ -236,39 +142,24 @@ pub(crate) async fn load_json_bytes_many_in_scope(
|
|
|
236
142
|
}
|
|
237
143
|
|
|
238
144
|
pub(crate) async fn verify_json_bytes_many_in_scope(
|
|
239
|
-
store: &
|
|
145
|
+
store: &impl StorageRead,
|
|
240
146
|
json_refs: &[JsonRef],
|
|
241
|
-
scope: JsonReadScopeRef
|
|
147
|
+
scope: JsonReadScopeRef,
|
|
242
148
|
) -> Result<Vec<Option<Vec<u8>>>, LixError> {
|
|
243
149
|
load_json_bytes_many_in_scope_with_hash_check(store, json_refs, scope, JsonHashCheck::Verify)
|
|
244
150
|
.await
|
|
245
151
|
}
|
|
246
152
|
|
|
247
153
|
async fn load_json_bytes_many_in_scope_with_hash_check(
|
|
248
|
-
store: &
|
|
154
|
+
store: &(impl StorageRead + ?Sized),
|
|
249
155
|
json_refs: &[JsonRef],
|
|
250
|
-
scope: JsonReadScopeRef
|
|
156
|
+
scope: JsonReadScopeRef,
|
|
251
157
|
hash_check: JsonHashCheck,
|
|
252
158
|
) -> Result<Vec<Option<Vec<u8>>>, LixError> {
|
|
253
159
|
if json_refs.is_empty() {
|
|
254
160
|
return Ok(Vec::new());
|
|
255
161
|
}
|
|
256
162
|
|
|
257
|
-
let ordered_single_pack_probe = if let JsonReadScopeRef::CommitPacks {
|
|
258
|
-
commit_id,
|
|
259
|
-
pack_ids: [pack_id],
|
|
260
|
-
} = scope
|
|
261
|
-
{
|
|
262
|
-
let probe =
|
|
263
|
-
load_ordered_single_pack(store, json_refs, commit_id, *pack_id, hash_check).await?;
|
|
264
|
-
if let OrderedSinglePackProbe::Hit(values) = probe {
|
|
265
|
-
return Ok(values);
|
|
266
|
-
}
|
|
267
|
-
Some(probe)
|
|
268
|
-
} else {
|
|
269
|
-
None
|
|
270
|
-
};
|
|
271
|
-
|
|
272
163
|
let mut unique_keys = Vec::new();
|
|
273
164
|
let mut unique_refs = Vec::new();
|
|
274
165
|
let mut key_indexes = HashMap::<[u8; 32], usize>::new();
|
|
@@ -292,26 +183,8 @@ async fn load_json_bytes_many_in_scope_with_hash_check(
|
|
|
292
183
|
requested_indexes.push(index);
|
|
293
184
|
}
|
|
294
185
|
|
|
295
|
-
let
|
|
296
|
-
|
|
297
|
-
JsonReadScopeRef::CommitPacks {
|
|
298
|
-
commit_id,
|
|
299
|
-
pack_ids: [pack_id],
|
|
300
|
-
} => match &ordered_single_pack_probe {
|
|
301
|
-
Some(OrderedSinglePackProbe::MissPresent(stored_pack)) => {
|
|
302
|
-
load_from_single_pack_bytes(stored_pack, &unique_refs, hash_check)?
|
|
303
|
-
}
|
|
304
|
-
Some(OrderedSinglePackProbe::MissAbsent) => vec![None; unique_refs.len()],
|
|
305
|
-
_ => {
|
|
306
|
-
let pack_ids = [*pack_id];
|
|
307
|
-
load_from_packs(store, &unique_refs, commit_id, &pack_ids, hash_check).await?
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
JsonReadScopeRef::CommitPacks {
|
|
311
|
-
commit_id,
|
|
312
|
-
pack_ids,
|
|
313
|
-
} => load_from_packs(store, &unique_refs, commit_id, pack_ids, hash_check).await?,
|
|
314
|
-
};
|
|
186
|
+
let JsonReadScopeRef::OutOfBand = scope;
|
|
187
|
+
let mut unique_values = vec![None; unique_refs.len()];
|
|
315
188
|
|
|
316
189
|
let missing = unique_values
|
|
317
190
|
.iter()
|
|
@@ -326,40 +199,31 @@ async fn load_json_bytes_many_in_scope_with_hash_check(
|
|
|
326
199
|
));
|
|
327
200
|
}
|
|
328
201
|
|
|
329
|
-
let
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
})
|
|
339
|
-
.await?;
|
|
340
|
-
let group = result.groups.into_iter().next().ok_or_else(|| {
|
|
341
|
-
LixError::new(
|
|
342
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
343
|
-
"json_store batch load returned no result group",
|
|
344
|
-
)
|
|
345
|
-
})?;
|
|
346
|
-
if group.len() != missing.len() {
|
|
202
|
+
let loaded = load_values(
|
|
203
|
+
store,
|
|
204
|
+
JSON_SPACE,
|
|
205
|
+
missing
|
|
206
|
+
.iter()
|
|
207
|
+
.map(|&index| unique_keys[index].clone())
|
|
208
|
+
.collect(),
|
|
209
|
+
)?;
|
|
210
|
+
if loaded.len() != missing.len() {
|
|
347
211
|
return Err(LixError::new(
|
|
348
212
|
LixError::CODE_INTERNAL_ERROR,
|
|
349
213
|
format!(
|
|
350
214
|
"json_store batch load returned {} values for {} requested refs",
|
|
351
|
-
|
|
215
|
+
loaded.len(),
|
|
352
216
|
missing.len()
|
|
353
217
|
),
|
|
354
218
|
));
|
|
355
219
|
}
|
|
356
220
|
|
|
357
|
-
for (index, stored_bytes) in
|
|
221
|
+
for (index, stored_bytes) in loaded.into_iter().enumerate() {
|
|
358
222
|
let unique_index = missing[index];
|
|
359
223
|
let Some(stored_bytes) = stored_bytes else {
|
|
360
224
|
continue;
|
|
361
225
|
};
|
|
362
|
-
let stored_payload = decode_stored_json_payload(stored_bytes)?;
|
|
226
|
+
let stored_payload = decode_stored_json_payload(&stored_bytes)?;
|
|
363
227
|
let _ = store;
|
|
364
228
|
unique_values[unique_index] = Some(decode_json_payload(
|
|
365
229
|
&unique_refs[unique_index],
|
|
@@ -395,117 +259,25 @@ fn json_values_in_request_order(
|
|
|
395
259
|
.collect()
|
|
396
260
|
}
|
|
397
261
|
|
|
398
|
-
|
|
399
|
-
store: &
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
pack_id: u32,
|
|
403
|
-
hash_check: JsonHashCheck,
|
|
404
|
-
) -> Result<OrderedSinglePackProbe, LixError> {
|
|
405
|
-
let result = store
|
|
406
|
-
.get_values(KvGetRequest {
|
|
407
|
-
groups: vec![KvGetGroup {
|
|
408
|
-
namespace: JSON_PACK_NAMESPACE.to_string(),
|
|
409
|
-
keys: vec![pack_key(commit_id, pack_id)],
|
|
410
|
-
}],
|
|
411
|
-
})
|
|
412
|
-
.await?;
|
|
413
|
-
let group = result.groups.into_iter().next().ok_or_else(|| {
|
|
414
|
-
LixError::new(
|
|
415
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
416
|
-
"json_store ordered pack load returned no result group",
|
|
417
|
-
)
|
|
418
|
-
})?;
|
|
419
|
-
if group.len() != 1 {
|
|
420
|
-
return Err(LixError::new(
|
|
421
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
422
|
-
format!(
|
|
423
|
-
"json_store ordered pack load returned {} values for 1 requested pack",
|
|
424
|
-
group.len()
|
|
425
|
-
),
|
|
426
|
-
));
|
|
427
|
-
}
|
|
428
|
-
let Some(stored_pack) = group.value(0).flatten() else {
|
|
429
|
-
return Ok(OrderedSinglePackProbe::MissAbsent);
|
|
430
|
-
};
|
|
431
|
-
let mut values = vec![None; requested_refs.len()];
|
|
432
|
-
if load_json_pack_values_in_request_order(stored_pack, hash_check, requested_refs, &mut values)?
|
|
433
|
-
{
|
|
434
|
-
Ok(OrderedSinglePackProbe::Hit(values))
|
|
435
|
-
} else {
|
|
436
|
-
Ok(OrderedSinglePackProbe::MissPresent(stored_pack.to_vec()))
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
fn load_from_single_pack_bytes(
|
|
441
|
-
stored_pack: &[u8],
|
|
442
|
-
unique_refs: &[JsonRef],
|
|
443
|
-
hash_check: JsonHashCheck,
|
|
262
|
+
fn load_values(
|
|
263
|
+
store: &(impl StorageRead + ?Sized),
|
|
264
|
+
space: StorageSpace,
|
|
265
|
+
keys: Vec<Vec<u8>>,
|
|
444
266
|
) -> Result<Vec<Option<Vec<u8>>>, LixError> {
|
|
445
|
-
let
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
let wanted = unique_refs
|
|
450
|
-
.iter()
|
|
451
|
-
.enumerate()
|
|
452
|
-
.map(|(index, json_ref)| (*json_ref.as_hash_array(), index))
|
|
453
|
-
.collect::<HashMap<_, _>>();
|
|
454
|
-
load_json_pack_values(stored_pack, hash_check, &wanted, &mut values)?;
|
|
455
|
-
Ok(values)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
async fn load_from_packs(
|
|
459
|
-
store: &mut impl StorageReader,
|
|
460
|
-
unique_refs: &[JsonRef],
|
|
461
|
-
commit_id: &str,
|
|
462
|
-
pack_ids: &[u32],
|
|
463
|
-
hash_check: JsonHashCheck,
|
|
464
|
-
) -> Result<Vec<Option<Vec<u8>>>, LixError> {
|
|
465
|
-
let mut values = vec![None; unique_refs.len()];
|
|
466
|
-
if pack_ids.is_empty() || unique_refs.is_empty() {
|
|
467
|
-
return Ok(values);
|
|
468
|
-
}
|
|
469
|
-
let keys = pack_ids
|
|
470
|
-
.iter()
|
|
471
|
-
.map(|&pack_id| pack_key(commit_id, pack_id))
|
|
267
|
+
let keys = keys
|
|
268
|
+
.into_iter()
|
|
269
|
+
.map(|key| StorageKey(Bytes::from(key)))
|
|
472
270
|
.collect::<Vec<_>>();
|
|
473
|
-
let result =
|
|
474
|
-
.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
271
|
+
let result =
|
|
272
|
+
PointReadPlan::new(space, &keys).materialize(store, StorageGetOptions::default())?;
|
|
273
|
+
Ok(result
|
|
274
|
+
.value
|
|
275
|
+
.into_iter()
|
|
276
|
+
.map(|value| match value {
|
|
277
|
+
Some(StorageProjectedValue::FullValue(bytes)) => Some(bytes.to_vec()),
|
|
278
|
+
Some(StorageProjectedValue::KeyOnly) | None => None,
|
|
479
279
|
})
|
|
480
|
-
.
|
|
481
|
-
let group = result.groups.into_iter().next().ok_or_else(|| {
|
|
482
|
-
LixError::new(
|
|
483
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
484
|
-
"json_store pack load returned no result group",
|
|
485
|
-
)
|
|
486
|
-
})?;
|
|
487
|
-
if pack_ids.len() == 1 && group.len() == 1 {
|
|
488
|
-
if let Some(stored_pack) = group.value(0).flatten() {
|
|
489
|
-
if load_json_pack_values_in_request_order(
|
|
490
|
-
stored_pack,
|
|
491
|
-
hash_check,
|
|
492
|
-
unique_refs,
|
|
493
|
-
&mut values,
|
|
494
|
-
)? {
|
|
495
|
-
return Ok(values);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
let wanted = unique_refs
|
|
501
|
-
.iter()
|
|
502
|
-
.enumerate()
|
|
503
|
-
.map(|(index, json_ref)| (*json_ref.as_hash_array(), index))
|
|
504
|
-
.collect::<HashMap<_, _>>();
|
|
505
|
-
for stored_pack in group.values_iter().flatten() {
|
|
506
|
-
load_json_pack_values(stored_pack, hash_check, &wanted, &mut values)?;
|
|
507
|
-
}
|
|
508
|
-
Ok(values)
|
|
280
|
+
.collect())
|
|
509
281
|
}
|
|
510
282
|
|
|
511
283
|
fn encode_stored_json_payload(encoded_json: &EncodedJson<'_>) -> Vec<u8> {
|
|
@@ -599,202 +371,38 @@ fn decode_json_payload(
|
|
|
599
371
|
Ok(data)
|
|
600
372
|
}
|
|
601
373
|
|
|
602
|
-
fn load_json_pack_values_in_request_order(
|
|
603
|
-
bytes: &[u8],
|
|
604
|
-
hash_check: JsonHashCheck,
|
|
605
|
-
requested_refs: &[JsonRef],
|
|
606
|
-
values: &mut [Option<Vec<u8>>],
|
|
607
|
-
) -> Result<bool, LixError> {
|
|
608
|
-
if values.len() < requested_refs.len() {
|
|
609
|
-
return Err(LixError::new(
|
|
610
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
611
|
-
"json_store ordered pack load has fewer result slots than refs",
|
|
612
|
-
));
|
|
613
|
-
}
|
|
614
|
-
let layout = json_pack_layout(bytes)?;
|
|
615
|
-
if layout.count != requested_refs.len() {
|
|
616
|
-
return Ok(false);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
for (index, json_ref) in requested_refs.iter().enumerate() {
|
|
620
|
-
let entry = json_pack_entry(bytes, &layout, index)?;
|
|
621
|
-
if &entry.hash != json_ref.as_hash_array() {
|
|
622
|
-
for value in &mut values[..index] {
|
|
623
|
-
*value = None;
|
|
624
|
-
}
|
|
625
|
-
return Ok(false);
|
|
626
|
-
}
|
|
627
|
-
values[index] = Some(decode_json_payload(json_ref, entry.payload, hash_check)?);
|
|
628
|
-
}
|
|
629
|
-
Ok(true)
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
fn load_json_pack_values(
|
|
633
|
-
bytes: &[u8],
|
|
634
|
-
hash_check: JsonHashCheck,
|
|
635
|
-
wanted: &HashMap<[u8; 32], usize>,
|
|
636
|
-
values: &mut [Option<Vec<u8>>],
|
|
637
|
-
) -> Result<(), LixError> {
|
|
638
|
-
let layout = json_pack_layout(bytes)?;
|
|
639
|
-
for index in 0..layout.count {
|
|
640
|
-
let entry = json_pack_entry(bytes, &layout, index)?;
|
|
641
|
-
let Some(&value_index) = wanted.get(&entry.hash) else {
|
|
642
|
-
continue;
|
|
643
|
-
};
|
|
644
|
-
let json_ref = JsonRef::from_hash_bytes(entry.hash);
|
|
645
|
-
values[value_index] = Some(decode_json_payload(&json_ref, entry.payload, hash_check)?);
|
|
646
|
-
}
|
|
647
|
-
Ok(())
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
fn json_pack_layout(bytes: &[u8]) -> Result<JsonPackLayout, LixError> {
|
|
651
|
-
if bytes.len() < STORED_JSON_PACK_MAGIC.len() + 4 {
|
|
652
|
-
return Err(LixError::new(
|
|
653
|
-
"LIX_ERROR_UNKNOWN",
|
|
654
|
-
"stored JSON pack is truncated",
|
|
655
|
-
));
|
|
656
|
-
}
|
|
657
|
-
if &bytes[..STORED_JSON_PACK_MAGIC.len()] != STORED_JSON_PACK_MAGIC {
|
|
658
|
-
return Err(LixError::new(
|
|
659
|
-
"LIX_ERROR_UNKNOWN",
|
|
660
|
-
"stored JSON pack has invalid header",
|
|
661
|
-
));
|
|
662
|
-
}
|
|
663
|
-
let count_start = STORED_JSON_PACK_MAGIC.len();
|
|
664
|
-
let count_end = count_start + 4;
|
|
665
|
-
let count = u32::from_be_bytes(
|
|
666
|
-
bytes[count_start..count_end]
|
|
667
|
-
.try_into()
|
|
668
|
-
.expect("json pack count header is fixed size"),
|
|
669
|
-
) as usize;
|
|
670
|
-
let directory_start = count_end;
|
|
671
|
-
let directory_len = count
|
|
672
|
-
.checked_mul(STORED_JSON_PACK_ENTRY_HEADER_LEN)
|
|
673
|
-
.ok_or_else(|| {
|
|
674
|
-
LixError::new(
|
|
675
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
676
|
-
"json pack directory overflow",
|
|
677
|
-
)
|
|
678
|
-
})?;
|
|
679
|
-
let payload_start = directory_start.checked_add(directory_len).ok_or_else(|| {
|
|
680
|
-
LixError::new(
|
|
681
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
682
|
-
"json pack payload offset overflow",
|
|
683
|
-
)
|
|
684
|
-
})?;
|
|
685
|
-
if bytes.len() < payload_start {
|
|
686
|
-
return Err(LixError::new(
|
|
687
|
-
"LIX_ERROR_UNKNOWN",
|
|
688
|
-
"stored JSON pack directory is truncated",
|
|
689
|
-
));
|
|
690
|
-
}
|
|
691
|
-
Ok(JsonPackLayout {
|
|
692
|
-
directory_start,
|
|
693
|
-
payload_start,
|
|
694
|
-
count,
|
|
695
|
-
})
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
fn json_pack_entry<'a>(
|
|
699
|
-
bytes: &'a [u8],
|
|
700
|
-
layout: &JsonPackLayout,
|
|
701
|
-
index: usize,
|
|
702
|
-
) -> Result<JsonPackEntry<'a>, LixError> {
|
|
703
|
-
if index >= layout.count {
|
|
704
|
-
return Err(LixError::new(
|
|
705
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
706
|
-
"json pack entry index exceeds directory count",
|
|
707
|
-
));
|
|
708
|
-
}
|
|
709
|
-
let mut cursor = layout.directory_start + index * STORED_JSON_PACK_ENTRY_HEADER_LEN;
|
|
710
|
-
let hash: [u8; 32] = bytes[cursor..cursor + 32]
|
|
711
|
-
.try_into()
|
|
712
|
-
.expect("json pack hash header is fixed size");
|
|
713
|
-
cursor += 32;
|
|
714
|
-
let codec = read_json_codec(bytes[cursor])?;
|
|
715
|
-
cursor += 1;
|
|
716
|
-
let uncompressed_len = u32::from_be_bytes(
|
|
717
|
-
bytes[cursor..cursor + 4]
|
|
718
|
-
.try_into()
|
|
719
|
-
.expect("json pack uncompressed length is fixed size"),
|
|
720
|
-
) as usize;
|
|
721
|
-
cursor += 4;
|
|
722
|
-
let offset = u32::from_be_bytes(
|
|
723
|
-
bytes[cursor..cursor + 4]
|
|
724
|
-
.try_into()
|
|
725
|
-
.expect("json pack payload offset is fixed size"),
|
|
726
|
-
) as usize;
|
|
727
|
-
cursor += 4;
|
|
728
|
-
let len = u32::from_be_bytes(
|
|
729
|
-
bytes[cursor..cursor + 4]
|
|
730
|
-
.try_into()
|
|
731
|
-
.expect("json pack payload length is fixed size"),
|
|
732
|
-
) as usize;
|
|
733
|
-
let data_start = layout.payload_start.checked_add(offset).ok_or_else(|| {
|
|
734
|
-
LixError::new(
|
|
735
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
736
|
-
"json pack entry offset overflow",
|
|
737
|
-
)
|
|
738
|
-
})?;
|
|
739
|
-
let data_end = data_start.checked_add(len).ok_or_else(|| {
|
|
740
|
-
LixError::new(
|
|
741
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
742
|
-
"json pack entry length overflow",
|
|
743
|
-
)
|
|
744
|
-
})?;
|
|
745
|
-
if data_end > bytes.len() {
|
|
746
|
-
return Err(LixError::new(
|
|
747
|
-
"LIX_ERROR_UNKNOWN",
|
|
748
|
-
"stored JSON pack entry payload is truncated",
|
|
749
|
-
));
|
|
750
|
-
}
|
|
751
|
-
Ok(JsonPackEntry {
|
|
752
|
-
hash,
|
|
753
|
-
payload: StoredJsonPayload {
|
|
754
|
-
codec,
|
|
755
|
-
uncompressed_len,
|
|
756
|
-
data: &bytes[data_start..data_end],
|
|
757
|
-
},
|
|
758
|
-
})
|
|
759
|
-
}
|
|
760
|
-
|
|
761
374
|
#[cfg(test)]
|
|
762
375
|
mod tests {
|
|
763
|
-
use std::sync::Arc;
|
|
764
|
-
|
|
765
376
|
use super::*;
|
|
766
|
-
use crate::
|
|
767
|
-
use crate::storage::{
|
|
377
|
+
use crate::storage::StorageContext;
|
|
378
|
+
use crate::storage::{
|
|
379
|
+
InMemoryStorageBackend, StorageKey, StorageReadOptions, StorageValue, StorageWriteOptions,
|
|
380
|
+
};
|
|
768
381
|
|
|
769
382
|
#[tokio::test]
|
|
770
383
|
async fn json_roundtrips_raw_payload() {
|
|
771
|
-
let storage = StorageContext::new(
|
|
384
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
772
385
|
let json = "{\"value\":\"small\"}";
|
|
773
386
|
let encoded = encode_json(json).expect("json should encode");
|
|
774
387
|
assert_eq!(encoded.codec, JsonCodec::Raw);
|
|
775
388
|
|
|
776
|
-
let mut
|
|
777
|
-
.begin_write_transaction()
|
|
778
|
-
.await
|
|
779
|
-
.expect("transaction should open");
|
|
780
|
-
let mut writes = StorageWriteSet::new();
|
|
389
|
+
let mut writes = storage.new_write_set();
|
|
781
390
|
writes.put(
|
|
782
|
-
|
|
783
|
-
encoded.json_ref.as_hash_bytes()
|
|
784
|
-
|
|
391
|
+
JSON_SPACE,
|
|
392
|
+
StorageKey(Bytes::copy_from_slice(encoded.json_ref.as_hash_bytes())),
|
|
393
|
+
StorageValue {
|
|
394
|
+
bytes: Bytes::from(encode_stored_json_payload(&encoded)),
|
|
395
|
+
},
|
|
785
396
|
);
|
|
786
|
-
|
|
787
|
-
.
|
|
788
|
-
.
|
|
789
|
-
.expect("json should store");
|
|
790
|
-
transaction
|
|
791
|
-
.commit()
|
|
792
|
-
.await
|
|
793
|
-
.expect("transaction should commit");
|
|
397
|
+
storage
|
|
398
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
399
|
+
.expect("writes should commit");
|
|
794
400
|
|
|
795
|
-
let
|
|
401
|
+
let store = storage
|
|
402
|
+
.begin_read(StorageReadOptions::default())
|
|
403
|
+
.expect("read should open");
|
|
796
404
|
assert_eq!(
|
|
797
|
-
load_json_bytes_direct(&
|
|
405
|
+
load_json_bytes_direct(&store, &encoded.json_ref)
|
|
798
406
|
.await
|
|
799
407
|
.expect("json should load"),
|
|
800
408
|
Some(json.as_bytes().to_vec())
|
|
@@ -803,37 +411,34 @@ mod tests {
|
|
|
803
411
|
|
|
804
412
|
#[tokio::test]
|
|
805
413
|
async fn json_batch_load_roundtrips_in_request_order() {
|
|
806
|
-
let storage = StorageContext::new(
|
|
414
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
807
415
|
let first = encode_json("{\"value\":\"first\"}").expect("first json should encode");
|
|
808
416
|
let second = encode_json("{\"value\":\"second\"}").expect("second json should encode");
|
|
809
417
|
|
|
810
|
-
let mut
|
|
811
|
-
.begin_write_transaction()
|
|
812
|
-
.await
|
|
813
|
-
.expect("transaction should open");
|
|
814
|
-
let mut writes = StorageWriteSet::new();
|
|
418
|
+
let mut writes = storage.new_write_set();
|
|
815
419
|
writes.put(
|
|
816
|
-
|
|
817
|
-
first.json_ref.as_hash_bytes()
|
|
818
|
-
|
|
420
|
+
JSON_SPACE,
|
|
421
|
+
StorageKey(Bytes::copy_from_slice(first.json_ref.as_hash_bytes())),
|
|
422
|
+
StorageValue {
|
|
423
|
+
bytes: Bytes::from(encode_stored_json_payload(&first)),
|
|
424
|
+
},
|
|
819
425
|
);
|
|
820
426
|
writes.put(
|
|
821
|
-
|
|
822
|
-
second.json_ref.as_hash_bytes()
|
|
823
|
-
|
|
427
|
+
JSON_SPACE,
|
|
428
|
+
StorageKey(Bytes::copy_from_slice(second.json_ref.as_hash_bytes())),
|
|
429
|
+
StorageValue {
|
|
430
|
+
bytes: Bytes::from(encode_stored_json_payload(&second)),
|
|
431
|
+
},
|
|
824
432
|
);
|
|
825
|
-
|
|
826
|
-
.
|
|
827
|
-
.
|
|
828
|
-
.expect("json should store");
|
|
829
|
-
transaction
|
|
830
|
-
.commit()
|
|
831
|
-
.await
|
|
832
|
-
.expect("transaction should commit");
|
|
433
|
+
storage
|
|
434
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
435
|
+
.expect("writes should commit");
|
|
833
436
|
|
|
834
|
-
let
|
|
437
|
+
let store = storage
|
|
438
|
+
.begin_read(StorageReadOptions::default())
|
|
439
|
+
.expect("read should open");
|
|
835
440
|
let values = load_json_bytes_many_in_scope(
|
|
836
|
-
&
|
|
441
|
+
&store,
|
|
837
442
|
&[second.json_ref, first.json_ref, second.json_ref],
|
|
838
443
|
JsonReadScopeRef::OutOfBand,
|
|
839
444
|
)
|
|
@@ -852,258 +457,38 @@ mod tests {
|
|
|
852
457
|
|
|
853
458
|
#[tokio::test]
|
|
854
459
|
async fn verified_batch_load_rejects_hash_mismatch() {
|
|
855
|
-
let storage = StorageContext::new(
|
|
460
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
856
461
|
let requested_ref = JsonRef::for_content(br#"{"value":"requested"}"#);
|
|
857
462
|
let stored = encode_json("{\"value\":\"different\"}").expect("stored json should encode");
|
|
858
463
|
|
|
859
|
-
let mut
|
|
860
|
-
.begin_write_transaction()
|
|
861
|
-
.await
|
|
862
|
-
.expect("transaction should open");
|
|
863
|
-
let mut writes = StorageWriteSet::new();
|
|
464
|
+
let mut writes = storage.new_write_set();
|
|
864
465
|
writes.put(
|
|
865
|
-
|
|
866
|
-
requested_ref.as_hash_bytes()
|
|
867
|
-
|
|
466
|
+
JSON_SPACE,
|
|
467
|
+
StorageKey(Bytes::copy_from_slice(requested_ref.as_hash_bytes())),
|
|
468
|
+
StorageValue {
|
|
469
|
+
bytes: Bytes::from(encode_stored_json_payload(&stored)),
|
|
470
|
+
},
|
|
868
471
|
);
|
|
869
|
-
|
|
870
|
-
.
|
|
871
|
-
.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
.
|
|
875
|
-
.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
&mut store,
|
|
881
|
-
&[requested_ref],
|
|
882
|
-
JsonReadScopeRef::OutOfBand,
|
|
883
|
-
)
|
|
884
|
-
.await
|
|
885
|
-
.expect("trusted hot read should not hash-check");
|
|
472
|
+
storage
|
|
473
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
474
|
+
.expect("writes should commit");
|
|
475
|
+
|
|
476
|
+
let store = storage
|
|
477
|
+
.begin_read(StorageReadOptions::default())
|
|
478
|
+
.expect("read should open");
|
|
479
|
+
let trusted =
|
|
480
|
+
load_json_bytes_many_in_scope(&store, &[requested_ref], JsonReadScopeRef::OutOfBand)
|
|
481
|
+
.await
|
|
482
|
+
.expect("trusted hot read should not hash-check");
|
|
886
483
|
assert_eq!(trusted, vec![Some(stored.data.as_ref().to_vec())]);
|
|
887
484
|
|
|
888
|
-
let
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
JsonReadScopeRef::OutOfBand,
|
|
893
|
-
)
|
|
894
|
-
.await
|
|
895
|
-
.expect_err("verified read should reject mismatched content address");
|
|
896
|
-
assert!(
|
|
897
|
-
error.to_string().contains("hash mismatch"),
|
|
898
|
-
"error should mention hash mismatch: {error}"
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
#[tokio::test]
|
|
903
|
-
async fn verified_pack_load_checks_only_requested_entries() {
|
|
904
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
905
|
-
let good = encode_json("{\"value\":\"good\"}").expect("good json should encode");
|
|
906
|
-
let bad_ref = JsonRef::for_content(br#"{"value":"expected"}"#);
|
|
907
|
-
let bad = encode_json_for_storage_with_ref("{\"value\":\"wrong\"}", bad_ref)
|
|
908
|
-
.expect("bad json should encode with mismatched ref");
|
|
909
|
-
|
|
910
|
-
let mut transaction = storage
|
|
911
|
-
.begin_write_transaction()
|
|
912
|
-
.await
|
|
913
|
-
.expect("transaction should open");
|
|
914
|
-
let mut writes = StorageWriteSet::new();
|
|
915
|
-
writes.put(
|
|
916
|
-
JSON_PACK_NAMESPACE,
|
|
917
|
-
pack_key("commit-a", 0),
|
|
918
|
-
encode_json_pack(&[&good, &bad]).expect("pack should encode"),
|
|
919
|
-
);
|
|
920
|
-
writes
|
|
921
|
-
.apply(&mut transaction.as_mut())
|
|
922
|
-
.await
|
|
923
|
-
.expect("json pack should store");
|
|
924
|
-
transaction
|
|
925
|
-
.commit()
|
|
926
|
-
.await
|
|
927
|
-
.expect("transaction should commit");
|
|
928
|
-
|
|
929
|
-
let pack_ids = [0];
|
|
930
|
-
let mut store = storage.clone();
|
|
931
|
-
let good_values = verify_json_bytes_many_in_scope(
|
|
932
|
-
&mut store,
|
|
933
|
-
&[good.json_ref],
|
|
934
|
-
JsonReadScopeRef::CommitPacks {
|
|
935
|
-
commit_id: "commit-a",
|
|
936
|
-
pack_ids: &pack_ids,
|
|
937
|
-
},
|
|
938
|
-
)
|
|
939
|
-
.await
|
|
940
|
-
.expect("unrequested bad pack entry should not be decoded");
|
|
941
|
-
assert_eq!(good_values, vec![Some(good.data.as_ref().to_vec())]);
|
|
942
|
-
|
|
943
|
-
let mut store = storage.clone();
|
|
944
|
-
let error = verify_json_bytes_many_in_scope(
|
|
945
|
-
&mut store,
|
|
946
|
-
&[bad_ref],
|
|
947
|
-
JsonReadScopeRef::CommitPacks {
|
|
948
|
-
commit_id: "commit-a",
|
|
949
|
-
pack_ids: &pack_ids,
|
|
950
|
-
},
|
|
951
|
-
)
|
|
952
|
-
.await
|
|
953
|
-
.expect_err("requested bad pack entry should be verified");
|
|
485
|
+
let error =
|
|
486
|
+
verify_json_bytes_many_in_scope(&store, &[requested_ref], JsonReadScopeRef::OutOfBand)
|
|
487
|
+
.await
|
|
488
|
+
.expect_err("verified read should reject mismatched content address");
|
|
954
489
|
assert!(
|
|
955
490
|
error.to_string().contains("hash mismatch"),
|
|
956
491
|
"error should mention hash mismatch: {error}"
|
|
957
492
|
);
|
|
958
493
|
}
|
|
959
|
-
|
|
960
|
-
#[test]
|
|
961
|
-
fn json_pack_directory_uses_compact_u32_fields() {
|
|
962
|
-
let first = encode_json("{\"value\":\"first\"}").expect("first json should encode");
|
|
963
|
-
let second = encode_json("{\"value\":\"second\"}").expect("second json should encode");
|
|
964
|
-
let pack = encode_json_pack(&[&first, &second]).expect("pack should encode");
|
|
965
|
-
let payload_len = first.data.as_ref().len() + second.data.as_ref().len();
|
|
966
|
-
|
|
967
|
-
assert_eq!(STORED_JSON_PACK_ENTRY_HEADER_LEN, 32 + 1 + 4 + 4 + 4);
|
|
968
|
-
assert_eq!(
|
|
969
|
-
pack.len(),
|
|
970
|
-
STORED_JSON_PACK_MAGIC.len() + 4 + 2 * STORED_JSON_PACK_ENTRY_HEADER_LEN + payload_len
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
#[test]
|
|
975
|
-
fn json_pack_u32_rejects_oversized_directory_fields() {
|
|
976
|
-
let error = json_pack_u32((u32::MAX as usize) + 1, "payload offset")
|
|
977
|
-
.expect_err("oversized pack directory field should reject");
|
|
978
|
-
assert!(
|
|
979
|
-
error.to_string().contains("payload offset exceeds u32"),
|
|
980
|
-
"error should identify oversized field: {error}"
|
|
981
|
-
);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
#[test]
|
|
985
|
-
fn ordered_pack_load_fast_path_requires_exact_pack_order() {
|
|
986
|
-
let first = encode_json("{\"value\":\"first\"}").expect("first json should encode");
|
|
987
|
-
let second = encode_json("{\"value\":\"second\"}").expect("second json should encode");
|
|
988
|
-
let pack = encode_json_pack(&[&first, &second]).expect("pack should encode");
|
|
989
|
-
|
|
990
|
-
let mut values = vec![None, None];
|
|
991
|
-
let loaded = load_json_pack_values_in_request_order(
|
|
992
|
-
&pack,
|
|
993
|
-
JsonHashCheck::Verify,
|
|
994
|
-
&[first.json_ref, second.json_ref],
|
|
995
|
-
&mut values,
|
|
996
|
-
)
|
|
997
|
-
.expect("ordered pack load should parse");
|
|
998
|
-
assert!(loaded);
|
|
999
|
-
assert_eq!(
|
|
1000
|
-
values,
|
|
1001
|
-
vec![
|
|
1002
|
-
Some(first.data.as_ref().to_vec()),
|
|
1003
|
-
Some(second.data.as_ref().to_vec()),
|
|
1004
|
-
]
|
|
1005
|
-
);
|
|
1006
|
-
|
|
1007
|
-
let mut values = vec![None, None];
|
|
1008
|
-
let loaded = load_json_pack_values_in_request_order(
|
|
1009
|
-
&pack,
|
|
1010
|
-
JsonHashCheck::Verify,
|
|
1011
|
-
&[second.json_ref, first.json_ref],
|
|
1012
|
-
&mut values,
|
|
1013
|
-
)
|
|
1014
|
-
.expect("unordered refs should fall back without error");
|
|
1015
|
-
assert!(!loaded);
|
|
1016
|
-
assert_eq!(values, vec![None, None]);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
#[tokio::test]
|
|
1020
|
-
async fn pack_batch_load_falls_back_for_unordered_refs() {
|
|
1021
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
1022
|
-
let first = encode_json("{\"value\":\"first\"}").expect("first json should encode");
|
|
1023
|
-
let second = encode_json("{\"value\":\"second\"}").expect("second json should encode");
|
|
1024
|
-
|
|
1025
|
-
let mut transaction = storage
|
|
1026
|
-
.begin_write_transaction()
|
|
1027
|
-
.await
|
|
1028
|
-
.expect("transaction should open");
|
|
1029
|
-
let mut writes = StorageWriteSet::new();
|
|
1030
|
-
writes.put(
|
|
1031
|
-
JSON_PACK_NAMESPACE,
|
|
1032
|
-
pack_key("commit-a", 0),
|
|
1033
|
-
encode_json_pack(&[&first, &second]).expect("pack should encode"),
|
|
1034
|
-
);
|
|
1035
|
-
writes
|
|
1036
|
-
.apply(&mut transaction.as_mut())
|
|
1037
|
-
.await
|
|
1038
|
-
.expect("json pack should store");
|
|
1039
|
-
transaction
|
|
1040
|
-
.commit()
|
|
1041
|
-
.await
|
|
1042
|
-
.expect("transaction should commit");
|
|
1043
|
-
|
|
1044
|
-
let pack_ids = [0];
|
|
1045
|
-
let mut store = storage.clone();
|
|
1046
|
-
let values = load_json_bytes_many_in_scope(
|
|
1047
|
-
&mut store,
|
|
1048
|
-
&[second.json_ref, first.json_ref],
|
|
1049
|
-
JsonReadScopeRef::CommitPacks {
|
|
1050
|
-
commit_id: "commit-a",
|
|
1051
|
-
pack_ids: &pack_ids,
|
|
1052
|
-
},
|
|
1053
|
-
)
|
|
1054
|
-
.await
|
|
1055
|
-
.expect("unordered refs should load through fallback");
|
|
1056
|
-
assert_eq!(
|
|
1057
|
-
values,
|
|
1058
|
-
vec![
|
|
1059
|
-
Some(second.data.as_ref().to_vec()),
|
|
1060
|
-
Some(first.data.as_ref().to_vec()),
|
|
1061
|
-
]
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
#[tokio::test]
|
|
1066
|
-
async fn ordered_pack_probe_falls_back_to_direct_rows() {
|
|
1067
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
1068
|
-
let packed = encode_json("{\"value\":\"packed\"}").expect("packed json should encode");
|
|
1069
|
-
let direct = encode_json("{\"value\":\"direct\"}").expect("direct json should encode");
|
|
1070
|
-
|
|
1071
|
-
let mut transaction = storage
|
|
1072
|
-
.begin_write_transaction()
|
|
1073
|
-
.await
|
|
1074
|
-
.expect("transaction should open");
|
|
1075
|
-
let mut writes = StorageWriteSet::new();
|
|
1076
|
-
writes.put(
|
|
1077
|
-
JSON_PACK_NAMESPACE,
|
|
1078
|
-
pack_key("commit-a", 0),
|
|
1079
|
-
encode_json_pack(&[&packed]).expect("pack should encode"),
|
|
1080
|
-
);
|
|
1081
|
-
writes.put(
|
|
1082
|
-
JSON_NAMESPACE,
|
|
1083
|
-
direct.json_ref.as_hash_bytes().to_vec(),
|
|
1084
|
-
encode_stored_json_payload(&direct),
|
|
1085
|
-
);
|
|
1086
|
-
writes
|
|
1087
|
-
.apply(&mut transaction.as_mut())
|
|
1088
|
-
.await
|
|
1089
|
-
.expect("json rows should store");
|
|
1090
|
-
transaction
|
|
1091
|
-
.commit()
|
|
1092
|
-
.await
|
|
1093
|
-
.expect("transaction should commit");
|
|
1094
|
-
|
|
1095
|
-
let pack_ids = [0];
|
|
1096
|
-
let mut store = storage.clone();
|
|
1097
|
-
let values = load_json_bytes_many_in_scope(
|
|
1098
|
-
&mut store,
|
|
1099
|
-
&[direct.json_ref],
|
|
1100
|
-
JsonReadScopeRef::CommitPacks {
|
|
1101
|
-
commit_id: "commit-a",
|
|
1102
|
-
pack_ids: &pack_ids,
|
|
1103
|
-
},
|
|
1104
|
-
)
|
|
1105
|
-
.await
|
|
1106
|
-
.expect("mismatched ordered pack probe should fall back to direct rows");
|
|
1107
|
-
assert_eq!(values, vec![Some(direct.data.as_ref().to_vec())]);
|
|
1108
|
-
}
|
|
1109
494
|
}
|