pub mod common;
use std::sync::Arc;
use nodedb_array::schema::array_schema::ArraySchema;
use nodedb_array::schema::attr_spec::{AttrSpec, AttrType};
use nodedb_array::schema::cell_order::{CellOrder, TileOrder};
use nodedb_array::schema::dim_spec::{DimSpec, DimType};
use nodedb_array::types::cell_value::value::CellValue;
use nodedb_array::types::domain::{Domain, DomainBound};
use nodedb_lite::PagedbStorageMem;
use nodedb_lite::sync::array::inbound::outcome::InboundOutcome;
use nodedb_lite::sync::array::replica_state::ReplicaState;
use nodedb_lite::sync::array::schema_registry::SchemaRegistry;
use nodedb_types::sync::wire::array::ArraySchemaSyncMsg;
fn two_attr_schema(name: &str) -> ArraySchema {
ArraySchema {
name: name.into(),
dims: vec![DimSpec::new(
"x",
DimType::Int64,
Domain::new(DomainBound::Int64(0), DomainBound::Int64(99)),
)],
attrs: vec![
AttrSpec::new("v", AttrType::Float64, true),
AttrSpec::new("w", AttrType::Int64, true),
],
tile_extents: vec![10],
cell_order: CellOrder::RowMajor,
tile_order: TileOrder::RowMajor,
}
}
#[tokio::test(flavor = "multi_thread")]
async fn schema_import_from_origin_enables_op_apply() {
let origin_storage = Arc::new(
PagedbStorageMem::open_in_memory()
.await
.expect("origin storage"),
);
let origin_replica = Arc::new(
ReplicaState::load_or_init(&*origin_storage)
.await
.expect("origin replica"),
);
let origin_schemas =
SchemaRegistry::new(Arc::clone(&origin_storage), Arc::clone(&origin_replica));
let schema = two_attr_schema("cross");
origin_schemas
.put_schema("cross", &schema)
.await
.expect("origin put_schema");
let snapshot_payload = origin_schemas
.export_snapshot("cross")
.expect("export_snapshot")
.expect("Some snapshot");
let origin_hlc = origin_schemas
.schema_hlc("cross")
.expect("origin schema_hlc");
let receiver = common::SyncHarness::new_in_memory().await;
{
let mut state = receiver.array_state.lock().await;
state
.create_array(
&receiver.storage,
"cross",
two_attr_schema("cross"),
None,
None,
)
.await
.expect("create_array");
}
assert!(
receiver.schemas.schema_hlc("cross").is_none(),
"schema must be absent before import"
);
let schema_msg = ArraySchemaSyncMsg {
array: "cross".into(),
replica_id: 1,
schema_hlc_bytes: origin_hlc.to_bytes(),
snapshot_payload,
};
let outcome = receiver
.inbound
.handle_schema(&schema_msg)
.await
.expect("handle_schema");
assert_eq!(outcome, InboundOutcome::SchemaImported);
let imported_hlc = receiver
.schemas
.schema_hlc("cross")
.expect("schema_hlc after import");
assert!(
imported_hlc >= origin_hlc,
"imported schema_hlc must be >= origin's; got local={imported_hlc:?} origin={origin_hlc:?}",
);
}
#[tokio::test(flavor = "multi_thread")]
async fn ops_with_imported_schema_hlc_apply_correctly() {
let origin_storage = Arc::new(PagedbStorageMem::open_in_memory().await.expect("storage"));
let origin_replica = Arc::new(
ReplicaState::load_or_init(&*origin_storage)
.await
.expect("replica"),
);
let origin_schemas =
SchemaRegistry::new(Arc::clone(&origin_storage), Arc::clone(&origin_replica));
origin_schemas
.put_schema("remote", &common::simple_schema("remote"))
.await
.expect("put");
let origin_hlc = origin_schemas.schema_hlc("remote").expect("hlc");
let snapshot = origin_schemas
.export_snapshot("remote")
.expect("export")
.expect("Some");
let receiver = common::SyncHarness::new_in_memory().await;
{
let mut state = receiver.array_state.lock().await;
state
.create_array(
&receiver.storage,
"remote",
common::simple_schema("remote"),
None,
None,
)
.await
.expect("create");
}
receiver
.inbound
.handle_schema(&ArraySchemaSyncMsg {
array: "remote".into(),
replica_id: 9,
schema_hlc_bytes: origin_hlc.to_bytes(),
snapshot_payload: snapshot,
})
.await
.expect("handle_schema");
let rep = common::replica(9);
let op = common::put_op("remote", 4, 7.5, 2000, origin_hlc, rep);
let outcome = receiver.deliver(&op);
assert_eq!(
outcome,
InboundOutcome::Applied,
"op with imported schema_hlc must apply"
);
receiver.flush("remote").await;
let val = receiver.read_coord("remote", 4, i64::MAX).await;
assert_eq!(val, Some(CellValue::Float64(7.5)));
}
#[tokio::test(flavor = "multi_thread")]
async fn put_schema_again_advances_schema_hlc() {
let harness = common::SyncHarness::new_in_memory().await;
harness.create_array("evolve").await;
let hlc_v1 = harness.schema_hlc("evolve");
harness
.schemas
.put_schema("evolve", &two_attr_schema("evolve"))
.await
.expect("put_schema second call");
let hlc_v2 = harness.schema_hlc("evolve");
assert!(
hlc_v2 >= hlc_v1,
"schema_hlc must be >= v1 after re-put; v1={hlc_v1:?}, v2={hlc_v2:?}"
);
}