mod canonical;
use std::{collections::BTreeSet, fs, ops::Bound, path::Path};
use crate::{
db::{
access::{
AccessPathKind, AccessPlan, SemanticIndexAccessContract, SemanticIndexKeyItemsRef,
SemanticIndexRangeSpec, lower_access,
},
index::{
EncodedValue, IndexBoundsSpec, IndexId, IndexKeyKind, build_index_bounds_for_arity,
build_index_prefix_bounds_for_encoded_components,
},
test_support::source_guard::{
collect_rust_sources, relative_rust_source_path, runtime_source_without_test_items,
},
},
model::index::{IndexKeyItemsRef, IndexModel},
types::EntityTag,
value::Value,
};
const CAPABILITY_TEST_INDEX_FIELDS: [&str; 2] = ["rank", "name"];
const CAPABILITY_TEST_INDEX: IndexModel = IndexModel::generated(
"access::tests::capability_idx_rank_name",
"access::tests::Store",
&CAPABILITY_TEST_INDEX_FIELDS,
false,
);
#[test]
fn access_shape_facts_preserve_pure_index_range_details() {
let spec = SemanticIndexRangeSpec::new(
CAPABILITY_TEST_INDEX,
vec![0, 1],
vec![Value::Nat64(7)],
Bound::Included(Value::Text("a".to_string())),
Bound::Excluded(Value::Text("z".to_string())),
);
let plan: AccessPlan<Value> = AccessPlan::index_range(spec);
let shape_facts = plan.shape_facts();
let path = shape_facts
.single_path_facts()
.expect("index-range test plan should remain a single access path");
let range_details = shape_facts
.single_path_index_range_details()
.expect("index-range test plan should expose index range details");
assert_eq!(path.kind(), AccessPathKind::IndexRange);
assert_eq!(range_details.name(), CAPABILITY_TEST_INDEX.name());
assert_eq!(range_details.slot_arity(), 1);
assert_eq!(
path.index_key_items_for_slot_map()
.expect("index range should expose slot-map key items")
.key_items(),
SemanticIndexKeyItemsRef::Static(IndexKeyItemsRef::Fields(
&CAPABILITY_TEST_INDEX_FIELDS[..]
))
);
assert_eq!(path.index_prefix_spec_count(), 0);
assert!(path.consumes_index_range_spec());
assert!(shape_facts.all_paths_support_reverse_traversal());
assert!(shape_facts.has_single_path_index_range_access_path());
assert_eq!(shape_facts.first_index_range_details(), Some(range_details));
}
#[test]
fn access_shape_facts_keep_branch_tree_closeout_families_distinct() {
let multi_lookup: AccessPlan<Value> = AccessPlan::index_multi_lookup_from_contract(
SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX),
vec![Value::Nat64(1), Value::Nat64(2)],
);
let multi_lookup_facts = multi_lookup.shape_facts();
let multi_lookup_path = multi_lookup_facts
.single_path_facts()
.expect("multi-lookup should remain a single access path");
let multi_lookup_index = multi_lookup_facts
.single_path_index_prefix_details()
.expect("multi-lookup should expose leading-prefix index details");
assert_eq!(multi_lookup_path.kind(), AccessPathKind::IndexMultiLookup);
assert_eq!(
multi_lookup_path.index_prefix_spec_count(),
2,
"multi-lookup consumes one exact prefix spec per lookup value",
);
assert_eq!(
multi_lookup_index.slot_arity(),
1,
"multi-lookup prefixes target the leading index slot only",
);
assert!(multi_lookup_facts.has_selected_index_access_path());
let branch_set: AccessPlan<Value> = AccessPlan::index_branch_set_from_contract(
SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX),
vec![Value::Nat64(7)],
vec![
Value::Text("draft".to_string()),
Value::Text("review".to_string()),
],
);
let branch_set_facts = branch_set.shape_facts();
let branch_set_path = branch_set_facts
.single_path_facts()
.expect("branch-set should remain a single access path");
let branch_set_index = branch_set_facts
.single_path_index_prefix_details()
.expect("branch-set should expose composite-prefix index details");
assert_eq!(branch_set_path.kind(), AccessPathKind::IndexBranchSet);
assert_eq!(
branch_set_path.index_prefix_spec_count(),
2,
"branch-set consumes one exact prefix spec per branch value",
);
assert_eq!(
branch_set_index.slot_arity(),
2,
"branch-set prefixes include fixed leading slots plus the branch slot",
);
assert!(branch_set_facts.has_selected_index_access_path());
let union: AccessPlan<Value> = AccessPlan::union(vec![
AccessPlan::index_prefix_from_contract(
SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX),
vec![Value::Nat64(1)],
),
AccessPlan::index_prefix_from_contract(
SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX),
vec![Value::Nat64(2)],
),
]);
let union_facts = union.shape_facts();
assert!(union_facts.is_composite());
assert!(union_facts.single_path_facts().is_none());
assert!(
!union_facts.has_selected_index_access_path(),
"general set-union access must not masquerade as one selected index path",
);
let intersection: AccessPlan<Value> = AccessPlan::intersection(vec![
AccessPlan::index_prefix_from_contract(
SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX),
vec![Value::Nat64(1)],
),
AccessPlan::by_key(Value::Text("primary".to_string())),
]);
let intersection_facts = intersection.shape_facts();
assert!(intersection_facts.is_composite());
assert!(intersection_facts.single_path_facts().is_none());
assert!(
!intersection_facts.has_selected_index_access_path(),
"general set-intersection access must not masquerade as one selected index path",
);
}
#[test]
fn lower_access_multi_lookup_matches_individual_prefix_bounds() {
let index = SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX);
let values = vec![Value::Nat64(1), Value::Nat64(2), Value::Nat64(3)];
let multi_lookup: AccessPlan<Value> =
AccessPlan::index_multi_lookup_from_contract(index.clone(), values.clone());
let lowered_multi_lookup =
lower_access(EntityTag::new(0xA11C), &multi_lookup).expect("multi-lookup should lower");
let expected_prefix_specs = values
.iter()
.map(|value| {
let prefix: AccessPlan<Value> =
AccessPlan::index_prefix_from_contract(index.clone(), vec![value.clone()]);
lower_access(EntityTag::new(0xA11C), &prefix)
.expect("single prefix should lower")
.index_prefix_specs()
.first()
.expect("single prefix should emit one lowered spec")
.clone()
})
.collect::<Vec<_>>();
let lowered_specs = lowered_multi_lookup.index_prefix_specs();
assert_eq!(
lowered_specs.len(),
expected_prefix_specs.len(),
"batched multi-lookup lowering must preserve the per-prefix contract count",
);
for (actual, expected) in lowered_specs.iter().zip(expected_prefix_specs.iter()) {
assert!(
!actual.has_deferred_raw_bounds_for_tests(),
"small multi-lookup prefix raw bounds should stay on the eager path",
);
assert_eq!(
actual.prefix_components(),
expected.prefix_components(),
"multi-lookup should preserve encoded prefix components",
);
assert_eq!(
actual
.raw_bounds()
.expect("multi-lookup bounds should lower"),
expected.raw_bounds().expect("prefix bounds should lower"),
"batched multi-lookup lowering must preserve the exact per-prefix bounds contract",
);
assert!(
actual.deferred_raw_bounds_materialized_for_tests(),
"eager small multi-lookup prefix bounds should already be materialized",
);
}
}
#[test]
fn lower_access_large_multi_lookup_defers_prefix_bounds_until_requested() {
let index = SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX);
let values = (0..40).map(Value::Nat64).collect::<Vec<_>>();
let multi_lookup: AccessPlan<Value> =
AccessPlan::index_multi_lookup_from_contract(index, values);
let lowered_multi_lookup =
lower_access(EntityTag::new(0xA11C), &multi_lookup).expect("multi-lookup should lower");
let lowered_specs = lowered_multi_lookup.index_prefix_specs();
assert_eq!(lowered_specs.len(), 40);
assert!(
lowered_specs
.iter()
.all(super::lowering::LoweredIndexPrefixSpec::has_deferred_raw_bounds_for_tests),
"large multi-lookup prefix raw bounds should use the deferred path",
);
assert!(
lowered_specs
.iter()
.all(|spec| !spec.deferred_raw_bounds_materialized_for_tests()),
"large multi-lookup lowering should not materialize raw prefix bounds",
);
let first = lowered_specs
.first()
.expect("large multi-lookup should lower at least one prefix");
let _ = first
.raw_bounds()
.expect("deferred prefix bounds should materialize on demand");
assert!(
first.deferred_raw_bounds_materialized_for_tests(),
"explicit raw-bound access should materialize the deferred multi-lookup bounds",
);
assert!(
lowered_specs
.iter()
.skip(1)
.all(|spec| !spec.deferred_raw_bounds_materialized_for_tests()),
"materializing one deferred prefix should not materialize sibling branches",
);
}
#[test]
fn lower_access_index_prefix_matches_canonical_prefix_bounds() {
let index = SemanticIndexAccessContract::model_only_from_generated_index(CAPABILITY_TEST_INDEX);
let values = vec![Value::Nat64(7), Value::Text("alpha".to_string())];
let plan: AccessPlan<Value> =
AccessPlan::index_prefix_from_contract(index.clone(), values.clone());
let lowered = lower_access(EntityTag::new(0xA11C), &plan).expect("prefix should lower");
let spec = lowered
.index_prefix_specs()
.first()
.expect("index prefix should emit one lowered spec");
let encoded_values =
EncodedValue::try_encode_all(values.as_slice()).expect("prefix values should encode");
let expected = build_index_prefix_bounds_for_encoded_components(
&IndexId::new(EntityTag::new(0xA11C), index.ordinal()),
IndexKeyKind::User,
index.key_arity(),
encoded_values.as_slice(),
)
.expect("canonical prefix bounds should build");
assert_eq!(
spec.lower().expect("prefix lower bound should lower"),
&expected.0
);
assert_eq!(
spec.upper().expect("prefix upper bound should lower"),
&expected.1
);
}
#[test]
fn lower_access_index_range_carries_canonical_prefix_components() {
let prefix_values = vec![Value::Nat64(7)];
let lower = Bound::Included(Value::Text("alpha".to_string()));
let upper = Bound::Excluded(Value::Text("omega".to_string()));
let plan: AccessPlan<Value> = AccessPlan::index_range(SemanticIndexRangeSpec::new(
CAPABILITY_TEST_INDEX,
vec![0, 1],
prefix_values.clone(),
lower.clone(),
upper.clone(),
));
let lowered = lower_access(EntityTag::new(0xA11C), &plan).expect("range should lower");
let [spec] = lowered.index_range_specs() else {
panic!("index range should emit one lowered range spec");
};
let encoded_prefix =
EncodedValue::try_encode_all(prefix_values.as_slice()).expect("prefix should encode");
let expected_prefix_components = encoded_prefix
.into_iter()
.map(EncodedValue::into_bytes)
.collect::<Vec<_>>();
let expected_bounds = build_index_bounds_for_arity(
&IndexId::new(EntityTag::new(0xA11C), CAPABILITY_TEST_INDEX.ordinal()),
CAPABILITY_TEST_INDEX.fields().len(),
IndexBoundsSpec::component_range(prefix_values.as_slice(), &lower, &upper),
)
.expect("canonical range bounds should build");
assert_eq!(spec.lower(), &expected_bounds.0);
assert_eq!(spec.upper(), &expected_bounds.1);
assert_eq!(
spec.prefix_components(),
expected_prefix_components.as_slice(),
"lowered range specs should retain canonical equality-prefix bytes",
);
}
fn contains_raw_access_path_token(source: &str) -> bool {
let needle = "AccessPath::";
let mut offset = 0usize;
while let Some(found) = source[offset..].find(needle) {
let absolute = offset + found;
let previous = if absolute == 0 {
None
} else {
source[..absolute].chars().next_back()
};
let preceded_by_identifier =
previous.is_some_and(|ch| ch.is_ascii_alphanumeric() || ch == '_');
if !preceded_by_identifier {
return true;
}
offset = absolute + needle.len();
}
false
}
#[test]
fn runtime_raw_access_path_references_stay_within_access_boundary() {
let source_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/db");
let mut sources = Vec::new();
collect_rust_sources(source_root.as_path(), &mut sources);
sources.sort();
let allowed: BTreeSet<&str> = BTreeSet::from([
"src/db/access/canonical.rs",
"src/db/access/execution_contract/mod.rs",
"src/db/access/lowering.rs",
"src/db/access/path.rs",
"src/db/access/plan.rs",
]);
let mut violations = Vec::new();
for source_path in sources {
if source_path
.components()
.any(|part| part.as_os_str() == "tests")
|| source_path
.file_name()
.is_some_and(|name| name == "tests.rs")
{
continue;
}
let source = fs::read_to_string(&source_path)
.unwrap_or_else(|err| panic!("failed to read {}: {err}", source_path.display()));
let runtime_source = runtime_source_without_test_items(source.as_str());
if !contains_raw_access_path_token(runtime_source.as_str()) {
continue;
}
let relative =
relative_rust_source_path(Path::new(env!("CARGO_MANIFEST_DIR")), source_path.as_path());
if !allowed.contains(relative.as_str()) {
violations.push(relative);
}
}
assert!(
violations.is_empty(),
"runtime AccessPath variant references must stay access-boundary-local; violations: {}",
violations.join(", "),
);
}