use nodedb_physical::PhysicalTaskVisitor;
use nodedb_physical::physical_plan::QueryOp;
use nodedb_sql::types::filter::Filter;
use nodedb_sql::{RecursiveScanVisitArgs, RecursiveValueVisitArgs};
use crate::error::LiteError;
use crate::query::engine::LiteQueryEngine;
use crate::query::filter_convert::sql_filters_to_metadata;
use crate::query::physical_visitor::LiteDataPlaneVisitor;
use crate::storage::engine::StorageEngine;
use super::adapter::LiteFut;
fn encode_filters(filters: &[Filter]) -> Result<Vec<u8>, LiteError> {
if filters.is_empty() {
return Ok(Vec::new());
}
match sql_filters_to_metadata(filters, &[])?.meta {
None => Ok(Vec::new()),
Some(mf) => zerompk::to_msgpack_vec(&mf).map_err(|e| LiteError::Serialization {
detail: format!("encode recursive filters: {e}"),
}),
}
}
pub(super) fn lower_recursive_scan<'a, S: StorageEngine + 'a>(
engine: &'a LiteQueryEngine<S>,
args: RecursiveScanVisitArgs<'_>,
) -> Result<LiteFut<'a>, LiteError> {
let RecursiveScanVisitArgs {
collection,
base_filters,
recursive_filters,
join_link,
max_iterations,
distinct,
limit,
} = args;
let base_bytes = encode_filters(base_filters)?;
let rec_bytes = encode_filters(recursive_filters)?;
let op = QueryOp::RecursiveScan {
collection: nodedb_types::QualifiedCollection::new(
nodedb_types::DatabaseId::DEFAULT,
collection,
),
base_filters: base_bytes,
recursive_filters: rec_bytes,
join_link: join_link.cloned(),
max_iterations,
distinct,
limit,
};
let mut phys = LiteDataPlaneVisitor::new(engine);
let fut = phys.query(&op)?;
Ok(Box::pin(fut))
}
pub(super) fn lower_recursive_value<'a, S: StorageEngine + 'a>(
engine: &'a LiteQueryEngine<S>,
args: RecursiveValueVisitArgs<'_>,
) -> Result<LiteFut<'a>, LiteError> {
let RecursiveValueVisitArgs {
cte_name,
columns,
init_exprs,
step_exprs,
condition,
max_depth,
distinct,
} = args;
let op = QueryOp::RecursiveValue {
cte_name: cte_name.to_string(),
columns: columns.to_vec(),
init_exprs: init_exprs.to_vec(),
step_exprs: step_exprs.to_vec(),
condition: condition.map(|s| s.to_string()),
max_depth,
distinct,
};
let mut phys = LiteDataPlaneVisitor::new(engine);
let fut = phys.query(&op)?;
Ok(Box::pin(fut))
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use nodedb_sql::{RecursiveScanVisitArgs, RecursiveValueVisitArgs};
use crate::PagedbStorageMem;
use crate::query::engine::{LiteQueryEngine, LiteQueryEngineParams};
async fn make_engine() -> LiteQueryEngine<PagedbStorageMem> {
use std::sync::Mutex;
let storage = Arc::new(
PagedbStorageMem::open_in_memory()
.await
.expect("in-memory pagedb"),
);
let crdt = Arc::new(Mutex::new(
crate::engine::crdt::CrdtEngine::new(1).expect("crdt"),
));
let governor = crate::query::engine::test_governor();
let strict = Arc::new(crate::engine::strict::StrictEngine::new(Arc::clone(
&storage,
)));
let columnar = Arc::new(crate::engine::columnar::ColumnarEngine::new(
Arc::clone(&storage),
crate::query::engine::test_scoped_memory(&governor, nodedb_mem::EngineId::Columnar),
));
let htap = Arc::new(crate::engine::htap::HtapBridge::new());
let timeseries = Arc::new(Mutex::new(
crate::engine::timeseries::engine::TimeseriesEngine::new(),
));
let vector_state = Arc::new(crate::engine::vector::VectorState::new(
Arc::clone(&storage),
100,
crate::query::engine::test_scoped_memory(&governor, nodedb_mem::EngineId::Vector),
));
let array_state = Arc::new(tokio::sync::Mutex::new(
crate::engine::array::engine::ArrayEngineState::open(&storage)
.await
.expect("array"),
));
let fts_state = Arc::new(crate::engine::fts::FtsState::new(Arc::clone(&governor)));
let spatial = Arc::new(Mutex::new(
crate::engine::spatial::SpatialIndexManager::new(
crate::query::engine::test_scoped_memory(&governor, nodedb_mem::EngineId::Spatial),
),
));
LiteQueryEngine::new(LiteQueryEngineParams {
crdt,
strict,
columnar,
htap,
storage,
timeseries,
vector_state,
array_state,
fts_state,
sparse_state: Arc::new(crate::engine::sparse_vector::SparseVectorState::new()),
spatial,
csr: Arc::new(Mutex::new(std::collections::HashMap::new())),
governor,
kv_local: crate::query::engine::test_kv_local(),
})
}
#[tokio::test]
async fn test_recursive_value_counting() {
let engine = make_engine().await;
let result = super::lower_recursive_value(
&engine,
RecursiveValueVisitArgs {
cte_name: "counter",
columns: &["n".to_string()],
init_exprs: &["1".to_string()],
step_exprs: &["n + 1".to_string()],
condition: Some("n < 5"),
max_depth: 100,
distinct: false,
},
);
assert!(result.is_ok());
let fut = result.unwrap();
let qr = fut.await.expect("recursive value should execute");
assert_eq!(qr.columns, vec!["n".to_string()]);
assert_eq!(qr.rows.len(), 5);
}
#[tokio::test]
async fn test_recursive_scan_lower() {
let engine = make_engine().await;
let result = super::lower_recursive_scan(
&engine,
RecursiveScanVisitArgs {
collection: "nodes",
base_filters: &[],
recursive_filters: &[],
join_link: Some(&("parent_id".to_string(), "id".to_string())),
max_iterations: 100,
distinct: true,
limit: 500,
},
);
assert!(result.is_ok());
}
}