use tracing::{debug, warn};
use crate::bridge::envelope::{ErrorCode, Response};
use crate::bridge::scan_filter::ScanFilter;
use crate::data::executor::core_loop::CoreLoop;
use crate::data::executor::core_loop::filter_match::matches_with_resolved_schema;
use crate::data::executor::task::ExecutionTask;
pub(in crate::data::executor) struct IndexedFetchParams<'a> {
pub tid: u64,
pub collection: &'a str,
pub path: &'a str,
pub value: &'a str,
pub filters: &'a [u8],
pub projection: &'a [String],
pub limit: usize,
pub offset: usize,
}
impl CoreLoop {
pub(in crate::data::executor) fn execute_document_index_lookup(
&mut self,
task: &ExecutionTask,
tid: u64,
collection: &str,
path: &str,
value: &str,
) -> Response {
debug!(
core = self.core_id,
%collection,
%path,
%value,
"document index lookup"
);
let bitemporal = self.is_bitemporal(task.request.database_id.as_u64(), tid, collection);
let doc_engine = crate::engine::document::store::DocumentEngine::new(
&self.sparse,
task.request.database_id.as_u64(),
tid,
);
match doc_engine.index_lookup(collection, path, value, bitemporal) {
Ok(mut doc_ids) => {
if let Some(txn_id) = task.request.txn_id {
let config_key = (
task.request.database_id,
crate::types::TenantId::new(tid),
collection.to_string(),
);
let (is_array, case_insensitive) = self.index_path_flags(&config_key, path);
let coll_key = (
task.request.database_id,
crate::types::TenantId::new(tid),
collection.to_string(),
);
self.merge_overlay_into_index_lookup(
super::super::transaction::overlay::IndexOverlayMergeParams {
txn_id,
coll_key: &coll_key,
path,
value,
is_array,
case_insensitive,
residual: &[],
strict_schema: None,
},
&mut doc_ids,
&|body| self.decode_indexed_body(&config_key, body),
);
}
let payload = serde_json::json!(doc_ids);
match sonic_rs::to_vec(&payload) {
Ok(bytes) => self.response_with_payload(task, bytes),
Err(e) => self.response_error(
task,
ErrorCode::Internal {
detail: format!("index lookup encode: {e}"),
},
),
}
}
Err(e) => self.response_error(
task,
ErrorCode::Internal {
detail: e.to_string(),
},
),
}
}
pub(in crate::data::executor) fn execute_document_indexed_fetch(
&mut self,
task: &ExecutionTask,
params: IndexedFetchParams<'_>,
) -> Response {
let IndexedFetchParams {
tid,
collection,
path,
value,
filters,
projection: _projection,
limit,
offset,
} = params;
debug!(
core = self.core_id,
%collection,
%path,
%value,
limit,
offset,
"document indexed fetch"
);
let database_id = task.request.database_id.as_u64();
let bitemporal = self.is_bitemporal(database_id, tid, collection);
let doc_engine =
crate::engine::document::store::DocumentEngine::new(&self.sparse, database_id, tid);
let mut doc_ids = match doc_engine.index_lookup(collection, path, value, bitemporal) {
Ok(ids) => ids,
Err(e) => {
return self.response_error(
task,
ErrorCode::Internal {
detail: format!("indexed fetch: {e}"),
},
);
}
};
let config_key = (
task.request.database_id,
crate::types::TenantId::new(tid),
collection.to_string(),
);
let strict_schema = self.doc_configs.get(&config_key).and_then(|c| {
if let nodedb_physical::physical_plan::StorageMode::Strict { ref schema } =
c.storage_mode
{
Some(schema.clone())
} else {
None
}
});
let coll_key = (
task.request.database_id,
crate::types::TenantId::new(tid),
collection.to_string(),
);
let residual: Vec<ScanFilter> = if filters.is_empty() {
Vec::new()
} else {
match zerompk::from_msgpack(filters) {
Ok(f) => f,
Err(e) => {
warn!(core = self.core_id, error = %e, "failed to parse indexed-fetch residual filters");
return self.response_error(
task,
ErrorCode::Internal {
detail: format!("malformed indexed-fetch filters: {e}"),
},
);
}
}
};
if let Some(txn_id) = task.request.txn_id {
let (is_array, case_insensitive) = self.index_path_flags(&config_key, path);
self.merge_overlay_into_index_lookup(
super::super::transaction::overlay::IndexOverlayMergeParams {
txn_id,
coll_key: &coll_key,
path,
value,
is_array,
case_insensitive,
residual: &residual,
strict_schema: strict_schema.as_ref(),
},
&mut doc_ids,
&|body| self.decode_indexed_body(&config_key, body),
);
}
let mut rows: Vec<(String, Vec<u8>)> = Vec::new();
for doc_id in doc_ids.iter().skip(offset).take(limit) {
let fetched = self.overlay_or_base_body(task.request.txn_id, &coll_key, doc_id, || {
if bitemporal {
self.sparse
.versioned_get_current(database_id, tid, collection, doc_id)
} else {
self.sparse.get(database_id, tid, collection, doc_id)
}
});
match fetched {
Ok(Some(bytes)) => {
if !residual.is_empty()
&& !matches_with_resolved_schema(strict_schema.as_ref(), &residual, &bytes)
{
continue;
}
let payload = if let Some(ref schema) = strict_schema {
match super::super::super::strict_format::binary_tuple_to_msgpack(
&bytes, schema,
) {
Some(mp) => mp,
None => bytes,
}
} else {
bytes
};
rows.push((doc_id.clone(), payload));
}
Ok(None) => {
}
Err(e) => {
return self.response_error(
task,
ErrorCode::Internal {
detail: format!("fetch doc {doc_id}: {e}"),
},
);
}
}
}
match super::super::super::response_codec::encode_raw_document_rows(&rows) {
Ok(bytes) => self.response_with_payload(task, bytes),
Err(e) => self.response_error(
task,
ErrorCode::Internal {
detail: format!("indexed fetch encode: {e}"),
},
),
}
}
}