use std::time::{Duration, Instant};
use nodedb_physical::physical_plan::{DocumentOp, StorageMode};
use nodedb_types::columnar::{ColumnDef, ColumnType, StrictSchema};
use nodedb_types::{DatabaseId, Surrogate};
use crate::bridge::envelope::{PhysicalPlan, Priority, Request};
use crate::data::executor::core_loop::CoreLoop;
use crate::data::executor::core_loop::tests::make_core_with_dir;
use crate::data::executor::handlers::transaction::sub_plan_doc::{TxPointDelete, TxPointPut};
use crate::data::executor::task::ExecutionTask;
use crate::engine::document::store::CollectionConfig;
use crate::types::{ReadConsistency, RequestId, TenantId, TraceId, VShardId};
const DB: u64 = 0;
const TID: u64 = 1;
const COLL: &str = "strict_docs";
const PK: &str = "row1";
fn register_strict(core: &mut CoreLoop) {
let schema = StrictSchema::new(vec![
ColumnDef::required("_rowid", ColumnType::Int64),
ColumnDef::nullable("body", ColumnType::String),
])
.unwrap();
core.doc_configs.insert(
(DatabaseId::DEFAULT, TenantId::new(TID), COLL.to_string()),
CollectionConfig::new(COLL).with_storage_mode(StorageMode::Strict { schema }),
);
}
fn doc_bytes() -> Vec<u8> {
use nodedb_types::Value;
let mut obj = std::collections::HashMap::new();
obj.insert(
"body".to_string(),
Value::String("searchable elephant paragraph".into()),
);
zerompk::to_msgpack_vec(&Value::Object(obj)).unwrap()
}
fn fts_searchable(core: &CoreLoop) -> bool {
!core
.inverted
.search(
DB,
TenantId::new(TID),
COLL,
nodedb_fts::FtsSearchParams {
query: "elephant",
top_k: 10,
fuzzy_enabled: false,
mode: nodedb_fts::posting::QueryMode::And,
prefilter: None,
},
)
.unwrap()
.is_empty()
}
fn dummy_task() -> ExecutionTask {
ExecutionTask::new(Request {
request_id: RequestId::new(1),
tenant_id: TenantId::new(TID),
database_id: DatabaseId::DEFAULT,
vshard_id: VShardId::new(0),
plan: PhysicalPlan::Document(DocumentOp::PointGet {
collection: COLL.into(),
document_id: PK.into(),
surrogate: Surrogate::ZERO,
pk_bytes: Vec::new(),
rls_filters: Vec::new(),
system_time: nodedb_types::SystemTimeScope::Current,
valid_at_ms: None,
}),
deadline: Instant::now() + Duration::from_secs(30),
priority: Priority::Normal,
trace_id: TraceId::ZERO,
consistency: ReadConsistency::Strong,
idempotency_key: None,
event_source: crate::event::EventSource::User,
user_roles: Vec::new(),
user_id: None,
statement_digest: None,
txn_id: None,
wal_lsn: None,
resolved_now_ms: None,
admission: crate::bridge::envelope::Admission::Exempt(
crate::bridge::envelope::ExemptReason::Read,
),
})
}
fn commit_put(core: &mut CoreLoop) {
let task = dummy_task();
let value = doc_bytes();
let mut throwaway = Vec::new();
core.tx_point_put(
TxPointPut {
task: &task,
tid: TID,
collection: COLL,
document_id: PK,
surrogate: Surrogate::new(1),
value: &value,
user_roles: &[],
insert_if_absent: None,
},
&mut throwaway,
)
.unwrap();
}
#[test]
fn strict_tx_delete_rollback_restores_fts_postings() {
let dir = tempfile::tempdir().unwrap();
let (mut core, _t, _r) = make_core_with_dir(dir.path());
register_strict(&mut core);
commit_put(&mut core);
assert!(
fts_searchable(&core),
"strict body must be searchable after insert"
);
let task = dummy_task();
let mut undo_log = Vec::new();
core.tx_point_delete(
TxPointDelete {
task: &task,
tid: TID,
collection: COLL,
document_id: PK,
surrogate: Surrogate::new(1),
user_roles: &[],
},
&mut undo_log,
)
.unwrap();
assert!(
!fts_searchable(&core),
"delete cascade must remove strict FTS postings"
);
core.rollback_undo_log(DB, TID, undo_log)
.expect("rollback must succeed");
assert!(
fts_searchable(&core),
"strict FTS postings must be restored (searchable again) after delete-rollback"
);
}
#[test]
fn strict_tx_put_rollback_removes_fts_postings() {
let dir = tempfile::tempdir().unwrap();
let (mut core, _t, _r) = make_core_with_dir(dir.path());
register_strict(&mut core);
assert!(!fts_searchable(&core));
let task = dummy_task();
let value = doc_bytes();
let mut undo_log = Vec::new();
core.tx_point_put(
TxPointPut {
task: &task,
tid: TID,
collection: COLL,
document_id: PK,
surrogate: Surrogate::new(1),
value: &value,
user_roles: &[],
insert_if_absent: None,
},
&mut undo_log,
)
.unwrap();
assert!(fts_searchable(&core), "strict body searchable mid-tx");
core.rollback_undo_log(DB, TID, undo_log)
.expect("rollback must succeed");
assert!(
!fts_searchable(&core),
"strict FTS postings must be gone after put-rollback"
);
}