nodedb_lite/query/
strict_provider.rs1use std::any::Any;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use datafusion::arrow::array::RecordBatch;
12use datafusion::arrow::datatypes::SchemaRef;
13use datafusion::catalog::{Session, TableProvider};
14use datafusion::error::DataFusionError;
15use datafusion::logical_expr::{Expr, TableType};
16use datafusion::physical_plan::ExecutionPlan;
17use nodedb_strict::TupleDecoder;
18use nodedb_strict::arrow_extract::extract_column_to_arrow;
19use nodedb_types::Namespace;
20use nodedb_types::columnar::StrictSchema;
21
22use crate::engine::strict::strict_schema_to_arrow;
23use crate::storage::engine::StorageEngine;
24
25pub struct StrictTableProvider<S: StorageEngine> {
30 collection: String,
31 arrow_schema: SchemaRef,
32 strict_schema: StrictSchema,
33 decoder: TupleDecoder,
34 storage: Arc<S>,
35}
36
37impl<S: StorageEngine> std::fmt::Debug for StrictTableProvider<S> {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("StrictTableProvider")
40 .field("collection", &self.collection)
41 .finish()
42 }
43}
44
45impl<S: StorageEngine> StrictTableProvider<S> {
46 pub fn new(collection: String, schema: &StrictSchema, storage: Arc<S>) -> Self {
48 let arrow_schema = strict_schema_to_arrow(schema);
49 let decoder = TupleDecoder::new(schema);
50 Self {
51 collection,
52 arrow_schema,
53 strict_schema: schema.clone(),
54 decoder,
55 storage,
56 }
57 }
58
59 fn scan_to_batches(
64 &self,
65 projection: Option<&Vec<usize>>,
66 limit: Option<usize>,
67 ) -> Result<Vec<RecordBatch>, DataFusionError> {
68 let prefix = format!("{}:", self.collection);
70
71 let tuples = tokio::task::block_in_place(|| {
74 let handle = tokio::runtime::Handle::current();
75 handle.block_on(async {
76 self.storage
77 .scan_prefix(Namespace::Strict, prefix.as_bytes())
78 .await
79 })
80 })
81 .map_err(|e| DataFusionError::Execution(format!("storage scan: {e}")))?;
82
83 let tuple_bytes: Vec<Vec<u8>> = if let Some(n) = limit {
85 tuples.into_iter().take(n).map(|(_, v)| v).collect()
86 } else {
87 tuples.into_iter().map(|(_, v)| v).collect()
88 };
89
90 if tuple_bytes.is_empty() {
91 let batch = RecordBatch::new_empty(self.arrow_schema.clone());
92 return Ok(vec![batch]);
93 }
94
95 let refs: Vec<&[u8]> = tuple_bytes.iter().map(|t| t.as_slice()).collect();
96
97 let col_indices: Vec<usize> = match projection {
99 Some(proj) => proj.to_vec(),
100 None => (0..self.strict_schema.columns.len()).collect(),
101 };
102
103 let projected_schema = if projection.is_some() {
105 Arc::new(
106 self.arrow_schema
107 .project(&col_indices)
108 .map_err(|e| DataFusionError::Execution(format!("schema projection: {e}")))?,
109 )
110 } else {
111 self.arrow_schema.clone()
112 };
113
114 let mut arrays = Vec::with_capacity(col_indices.len());
116 for &idx in &col_indices {
117 let arr = extract_column_to_arrow(&self.strict_schema, &self.decoder, &refs, idx)
118 .map_err(|e| DataFusionError::Execution(format!("extract column: {e}")))?;
119 arrays.push(arr);
120 }
121
122 let batch = RecordBatch::try_new(projected_schema, arrays)
123 .map_err(|e| DataFusionError::Execution(format!("build batch: {e}")))?;
124
125 Ok(vec![batch])
126 }
127}
128
129#[async_trait]
130impl<S: StorageEngine> TableProvider for StrictTableProvider<S> {
131 fn as_any(&self) -> &dyn Any {
132 self
133 }
134
135 fn schema(&self) -> SchemaRef {
136 Arc::clone(&self.arrow_schema)
137 }
138
139 fn table_type(&self) -> TableType {
140 TableType::Base
141 }
142
143 async fn scan(
144 &self,
145 state: &dyn Session,
146 projection: Option<&Vec<usize>>,
147 _filters: &[Expr],
148 limit: Option<usize>,
149 ) -> datafusion::error::Result<Arc<dyn ExecutionPlan>> {
150 let batches = self.scan_to_batches(projection, limit)?;
151 let schema = if let Some(proj) = projection {
152 Arc::new(self.arrow_schema.project(proj)?)
153 } else {
154 self.arrow_schema.clone()
155 };
156 let mem_table = datafusion::datasource::MemTable::try_new(schema, vec![batches])?;
157 mem_table.scan(state, None, &[], limit).await
158 }
159}