1use crate::extended_sql_functions::{json_table_batches_from_text, JsonTableMode};
2use crate::{arrow_conv, MongrelQueryError, Result};
3use arrow::array::{ArrayRef, Int64Array, StringArray};
4use arrow::datatypes::{DataType, Field, Schema as ArrowSchema, SchemaRef};
5use arrow::record_batch::{RecordBatch, RecordBatchOptions};
6use datafusion::catalog::{Session, TableProvider};
7use datafusion::common::{DataFusionError, Result as DFResult, ScalarValue};
8use datafusion::execution::TaskContext;
9use datafusion::logical_expr::{
10 BinaryExpr, Expr, Operator, TableProviderFilterPushDown, TableType,
11};
12use datafusion::physical_expr::EquivalenceProperties;
13use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
14use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
15use datafusion::physical_plan::{
16 DisplayAs, DisplayFormatType, ExecutionPlan, Partitioning, PlanProperties,
17 SendableRecordBatchStream,
18};
19use mongreldb_core::catalog::TableState;
20use mongreldb_core::database::{TABLES_DIR, VTAB_DIR};
21use mongreldb_core::schema::{
22 ColumnDef as CoreColumnDef, ColumnFlags, Schema as CoreSchema, TypeId,
23};
24use mongreldb_core::{
25 Database, ExecutionControl, ExternalTableEntry, ModuleArg, ModuleCapabilities, Value,
26};
27use serde::{Deserialize, Serialize};
28use std::any::Any;
29use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
30use std::fs;
31use std::path::{Path, PathBuf};
32use std::sync::Arc;
33
34use crate::query_registry::{RegisteredSqlQuery, SqlTaskContext};
35
36const EXTERNAL_STATE_BYTES_LIMIT: usize = 64 * 1024 * 1024;
37const EXTERNAL_ROWS_LIMIT: usize = 1_000_000;
38const EXTERNAL_ROWS_BYTES_LIMIT: usize = 64 * 1024 * 1024;
39const EXTERNAL_BASE_WRITES_LIMIT: usize = 1_000_000;
40const EXTERNAL_BASE_WRITE_BYTES_LIMIT: usize = 64 * 1024 * 1024;
41
42fn external_resource_limit(
43 resource: &'static str,
44 requested: usize,
45 limit: usize,
46) -> MongrelQueryError {
47 MongrelQueryError::Core(mongreldb_core::MongrelError::ResourceLimitExceeded {
48 resource,
49 requested,
50 limit,
51 })
52}
53
54fn external_df_checkpoint(control: &ExecutionControl) -> DFResult<()> {
55 control
56 .checkpoint()
57 .map_err(|error| DataFusionError::Execution(error.to_string()))
58}
59
60fn controlled_module_result<T>(query: Option<&RegisteredSqlQuery>, result: Result<T>) -> Result<T> {
61 match result {
62 Ok(value) => {
63 query.map(RegisteredSqlQuery::checkpoint).transpose()?;
64 Ok(value)
65 }
66 Err(error) => match query.and_then(|query| query.checkpoint().err()) {
67 Some(cancellation) => Err(cancellation),
68 None => Err(error),
69 },
70 }
71}
72
73fn external_value_bytes(value: &Value) -> usize {
74 match value {
75 Value::Null => 0,
76 Value::Bool(_) => 1,
77 Value::Int64(_) | Value::Float64(_) => 8,
78 Value::Bytes(value) | Value::Json(value) => value.len(),
79 Value::Embedding(value) => value.len().saturating_mul(std::mem::size_of::<f32>()),
80 Value::Decimal(_) | Value::Uuid(_) => 16,
81 Value::Interval { .. } => 20,
82 }
83}
84
85fn external_row_bytes(row: &HashMap<u16, Value>) -> usize {
86 let bucket_bytes =
87 std::mem::size_of::<(u16, Value)>().saturating_add(2 * std::mem::size_of::<usize>());
88 row.capacity().saturating_mul(bucket_bytes).saturating_add(
89 row.values()
90 .map(external_value_bytes)
91 .fold(0_usize, usize::saturating_add),
92 )
93}
94
95pub(crate) fn enforce_external_rows_limit(
96 rows: &[HashMap<u16, Value>],
97 query: Option<&RegisteredSqlQuery>,
98) -> Result<()> {
99 if rows.len() > EXTERNAL_ROWS_LIMIT {
100 return Err(external_resource_limit(
101 "external table rows",
102 rows.len(),
103 EXTERNAL_ROWS_LIMIT,
104 ));
105 }
106 let mut bytes = 0_usize;
107 for (index, row) in rows.iter().enumerate() {
108 if index % 256 == 0 {
109 query.map(RegisteredSqlQuery::checkpoint).transpose()?;
110 }
111 bytes = bytes.saturating_add(external_row_bytes(row));
112 if bytes > EXTERNAL_ROWS_BYTES_LIMIT {
113 return Err(external_resource_limit(
114 "external table row bytes",
115 bytes,
116 EXTERNAL_ROWS_BYTES_LIMIT,
117 ));
118 }
119 }
120 Ok(())
121}
122
123pub(crate) fn enforce_external_state_limit(state: &[u8]) -> Result<()> {
124 if state.len() > EXTERNAL_STATE_BYTES_LIMIT {
125 return Err(external_resource_limit(
126 "external table state bytes",
127 state.len(),
128 EXTERNAL_STATE_BYTES_LIMIT,
129 ));
130 }
131 Ok(())
132}
133
134pub trait ExternalTableModule: Send + Sync {
135 fn name(&self) -> &str;
136 fn descriptor(&self) -> ExternalModuleDescriptor;
137 fn indexes_with_control(
138 &self,
139 context: &ExternalExecutionContext<'_>,
140 _entry: &ExternalTableEntry,
141 ) -> Result<Vec<ExternalModuleIndex>> {
142 context.control.checkpoint()?;
143 Ok(Vec::new())
144 }
145 fn connect_with_control(
146 &self,
147 context: &ExternalExecutionContext<'_>,
148 entry: &ExternalTableEntry,
149 ) -> Result<Arc<dyn ExternalTable>>;
150 fn read_rows_with_control(
151 &self,
152 context: &ExternalExecutionContext<'_>,
153 entry: &ExternalTableEntry,
154 ) -> Result<Vec<HashMap<u16, Value>>> {
155 context.control.checkpoint()?;
156 Err(MongrelQueryError::Schema(format!(
157 "external table {:?} using module {:?} is not row-writable",
158 entry.name,
159 self.name()
160 )))
161 }
162 fn prepare_rows_with_control(
163 &self,
164 context: &ExternalExecutionContext<'_>,
165 entry: &ExternalTableEntry,
166 _rows: Vec<HashMap<u16, Value>>,
167 ) -> Result<Vec<u8>> {
168 context.control.checkpoint()?;
169 Err(MongrelQueryError::Schema(format!(
170 "external table {:?} using module {:?} is not row-writable",
171 entry.name,
172 self.name()
173 )))
174 }
175 fn rows_from_state_with_control(
176 &self,
177 context: &ExternalExecutionContext<'_>,
178 state: &[u8],
179 ) -> Result<Vec<HashMap<u16, Value>>> {
180 context.control.checkpoint()?;
181 if state.is_empty() {
182 Ok(Vec::new())
183 } else {
184 decode_state_rows(state)
185 }
186 }
187 fn write_with_control(
188 &self,
189 context: &ExternalExecutionContext<'_>,
190 entry: &ExternalTableEntry,
191 op: ExternalWriteOp,
192 txn: &mut ExternalTxn,
193 ) -> Result<ExternalWriteResult> {
194 context.control.checkpoint()?;
195 let (rows, changes) = match op {
196 ExternalWriteOp::Insert { rows: inserted } => {
197 let changes = inserted.len() as u64;
198 let mut rows = txn.read_rows()?;
199 rows.extend(inserted);
200 (rows, changes)
201 }
202 ExternalWriteOp::ReplaceRows { rows, changes } => (rows, changes),
203 };
204 txn.replace_state(self.prepare_rows_with_control(context, entry, rows)?);
205 context.control.checkpoint()?;
206 Ok(ExternalWriteResult { changes })
207 }
208 fn destroy_with_control(
209 &self,
210 context: &ExternalExecutionContext<'_>,
211 _entry: &ExternalTableEntry,
212 ) -> Result<()> {
213 context.control.checkpoint()?;
214 Ok(())
215 }
216}
217
218pub trait ExternalTable: std::fmt::Debug + Send + Sync {
219 fn schema(&self) -> SchemaRef;
220 fn plan_with_control(
221 &self,
222 request: &ExternalPlanRequest<'_>,
223 control: &ExecutionControl,
224 ) -> DFResult<ExternalPlan>;
225 fn scan_with_control(
226 &self,
227 request: &ExternalPlanRequest<'_>,
228 control: &mongreldb_core::ExecutionControl,
229 ) -> DFResult<ExternalScan>;
230}
231
232#[derive(Clone)]
233pub struct ExternalModuleDescriptor {
234 pub schema: CoreSchema,
235 pub hidden_columns: Vec<String>,
236 pub capabilities: ModuleCapabilities,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct ExternalModuleIndex {
241 pub name: String,
242 pub column_ids: Vec<u16>,
243 pub unique: bool,
244 pub partial: bool,
245}
246
247impl ExternalModuleIndex {
248 pub fn new(name: impl Into<String>, column_ids: Vec<u16>) -> Self {
249 Self {
250 name: name.into(),
251 column_ids,
252 unique: false,
253 partial: false,
254 }
255 }
256}
257
258pub struct ExternalExecutionContext<'a> {
259 pub database: &'a Arc<Database>,
260 pub control: &'a ExecutionControl,
261 pub query_id: Option<crate::QueryId>,
262}
263
264impl ExternalExecutionContext<'_> {
265 pub fn raw_state(&self, entry: &ExternalTableEntry) -> Result<Vec<u8>> {
266 self.control.checkpoint()?;
267 let state = external_table_state_bytes(self.database, entry)?;
268 self.control.checkpoint()?;
269 Ok(state)
270 }
271
272 pub fn read_state(&self, entry: &ExternalTableEntry, key: &[u8]) -> Result<Option<Vec<u8>>> {
273 ExternalTxn::read_state_from_bytes(&self.raw_state(entry)?, key)
274 }
275}
276
277pub struct ExternalPlanRequest<'a> {
278 pub projection: Option<Vec<usize>>,
279 pub filters: Vec<ExternalFilter>,
280 pub raw_filters: Vec<&'a Expr>,
281 pub order_by: Vec<ExternalOrder>,
282 pub limit: Option<usize>,
283 pub offset: Option<usize>,
284}
285
286pub struct ExternalScan {
287 pub schema: SchemaRef,
288 pub batches: Vec<RecordBatch>,
289}
290
291#[derive(Clone)]
292pub struct ExternalPlan {
293 pub filter_pushdown: Vec<TableProviderFilterPushDown>,
294 pub accepted_filters: Vec<AcceptedFilter>,
295 pub residual_filters_required: bool,
296 pub estimated_rows: Option<u64>,
297 pub estimated_cost: f64,
298 pub order_satisfied: bool,
299 pub opaque: Arc<dyn Any + Send + Sync>,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Hash)]
303pub struct ExternalOrder {
304 pub column_index: usize,
305 pub descending: bool,
306 pub nulls_first: Option<bool>,
307}
308
309#[derive(Debug, Clone, PartialEq)]
310pub enum ExternalFilter {
311 And(Vec<ExternalFilter>),
312 Compare {
313 column_index: usize,
314 op: ExternalFilterOp,
315 value: ScalarValue,
316 },
317 Unsupported {
318 expr: String,
319 },
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
323pub enum ExternalFilterOp {
324 Eq,
325 NotEq,
326 Gt,
327 GtEq,
328 Lt,
329 LtEq,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq)]
333pub struct AcceptedFilter {
334 pub filter_index: usize,
335 pub pushdown: TableProviderFilterPushDown,
336}
337
338impl ExternalPlan {
339 pub fn new(
340 filter_pushdown: Vec<TableProviderFilterPushDown>,
341 estimated_rows: Option<u64>,
342 estimated_cost: f64,
343 order_satisfied: bool,
344 ) -> Self {
345 let accepted_filters = filter_pushdown
346 .iter()
347 .enumerate()
348 .filter_map(|(filter_index, pushdown)| match pushdown {
349 TableProviderFilterPushDown::Exact | TableProviderFilterPushDown::Inexact => {
350 Some(AcceptedFilter {
351 filter_index,
352 pushdown: pushdown.clone(),
353 })
354 }
355 TableProviderFilterPushDown::Unsupported => None,
356 })
357 .collect();
358 let residual_filters_required = filter_pushdown.iter().any(|pushdown| {
359 matches!(
360 pushdown,
361 TableProviderFilterPushDown::Inexact | TableProviderFilterPushDown::Unsupported
362 )
363 });
364 Self {
365 filter_pushdown,
366 accepted_filters,
367 residual_filters_required,
368 estimated_rows,
369 estimated_cost,
370 order_satisfied,
371 opaque: Arc::new(()),
372 }
373 }
374
375 pub fn with_opaque(mut self, opaque: Arc<dyn Any + Send + Sync>) -> Self {
376 self.opaque = opaque;
377 self
378 }
379}
380
381fn external_filter_from_expr(expr: &Expr, schema: &ArrowSchema) -> ExternalFilter {
382 match expr {
383 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::And => {
384 ExternalFilter::And(vec![
385 external_filter_from_expr(left, schema),
386 external_filter_from_expr(right, schema),
387 ])
388 }
389 Expr::BinaryExpr(BinaryExpr { left, op, right }) => {
390 let Some(op) = external_filter_op(*op) else {
391 return ExternalFilter::Unsupported {
392 expr: expr.to_string(),
393 };
394 };
395 match (left.as_ref(), right.as_ref()) {
396 (Expr::Column(column), Expr::Literal(value, _)) => schema
397 .index_of(&column.name)
398 .map(|column_index| ExternalFilter::Compare {
399 column_index,
400 op,
401 value: value.clone(),
402 })
403 .unwrap_or_else(|_| ExternalFilter::Unsupported {
404 expr: expr.to_string(),
405 }),
406 (Expr::Literal(value, _), Expr::Column(column)) => schema
407 .index_of(&column.name)
408 .map(|column_index| ExternalFilter::Compare {
409 column_index,
410 op: op.flipped(),
411 value: value.clone(),
412 })
413 .unwrap_or_else(|_| ExternalFilter::Unsupported {
414 expr: expr.to_string(),
415 }),
416 _ => ExternalFilter::Unsupported {
417 expr: expr.to_string(),
418 },
419 }
420 }
421 _ => ExternalFilter::Unsupported {
422 expr: expr.to_string(),
423 },
424 }
425}
426
427fn external_filter_op(op: Operator) -> Option<ExternalFilterOp> {
428 match op {
429 Operator::Eq => Some(ExternalFilterOp::Eq),
430 Operator::NotEq => Some(ExternalFilterOp::NotEq),
431 Operator::Gt => Some(ExternalFilterOp::Gt),
432 Operator::GtEq => Some(ExternalFilterOp::GtEq),
433 Operator::Lt => Some(ExternalFilterOp::Lt),
434 Operator::LtEq => Some(ExternalFilterOp::LtEq),
435 _ => None,
436 }
437}
438
439impl ExternalFilterOp {
440 fn flipped(self) -> Self {
441 match self {
442 ExternalFilterOp::Eq => ExternalFilterOp::Eq,
443 ExternalFilterOp::NotEq => ExternalFilterOp::NotEq,
444 ExternalFilterOp::Gt => ExternalFilterOp::Lt,
445 ExternalFilterOp::GtEq => ExternalFilterOp::LtEq,
446 ExternalFilterOp::Lt => ExternalFilterOp::Gt,
447 ExternalFilterOp::LtEq => ExternalFilterOp::GtEq,
448 }
449 }
450}
451
452#[derive(Clone)]
453pub enum ExternalWriteOp {
454 Insert {
455 rows: Vec<HashMap<u16, Value>>,
456 },
457 ReplaceRows {
458 rows: Vec<HashMap<u16, Value>>,
459 changes: u64,
460 },
461}
462
463#[derive(Debug, Clone, PartialEq)]
464pub enum ExternalBaseWrite {
465 Put {
466 table: String,
467 cells: Vec<(u16, Value)>,
468 },
469 Delete {
470 table: String,
471 row_id: u64,
472 },
473}
474
475fn external_base_write_bytes(op: &ExternalBaseWrite) -> usize {
476 match op {
477 ExternalBaseWrite::Put { table, cells } => table.len().saturating_add(
478 cells
479 .iter()
480 .map(|(_, value)| {
481 std::mem::size_of::<(u16, Value)>().saturating_add(external_value_bytes(value))
482 })
483 .fold(0_usize, usize::saturating_add),
484 ),
485 ExternalBaseWrite::Delete { table, .. } => {
486 table.len().saturating_add(std::mem::size_of::<u64>())
487 }
488 }
489}
490
491#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
492pub struct ExternalWriteResult {
493 pub changes: u64,
494}
495
496impl ExternalWriteResult {
497 pub fn new(changes: u64) -> Self {
498 Self { changes }
499 }
500}
501
502pub struct ExternalTxn {
503 state: Vec<u8>,
504 base_writes: Vec<ExternalBaseWrite>,
505 base_write_bytes: usize,
506}
507
508impl ExternalTxn {
509 pub fn new(state: Vec<u8>) -> Self {
510 Self {
511 state,
512 base_writes: Vec::new(),
513 base_write_bytes: 0,
514 }
515 }
516
517 pub fn read_state_from_bytes(state: &[u8], key: &[u8]) -> Result<Option<Vec<u8>>> {
518 enforce_external_state_limit(state)?;
519 Ok(decode_kv_state(state)?.remove(key))
520 }
521
522 pub fn read_state(&self, key: &[u8]) -> Result<Option<Vec<u8>>> {
523 Self::read_state_from_bytes(&self.state, key)
524 }
525
526 pub fn put_state(&mut self, key: &[u8], value: &[u8]) -> Result<()> {
527 enforce_external_state_limit(&self.state)?;
528 if key.len().saturating_add(value.len()) > EXTERNAL_STATE_BYTES_LIMIT {
529 return Err(external_resource_limit(
530 "external transaction key/value bytes",
531 key.len().saturating_add(value.len()),
532 EXTERNAL_STATE_BYTES_LIMIT,
533 ));
534 }
535 let mut state = decode_kv_state(&self.state)?;
536 state.insert(key.to_vec(), value.to_vec());
537 self.state = encode_kv_state(&state)?;
538 Ok(())
539 }
540
541 pub fn delete_state(&mut self, key: &[u8]) -> Result<()> {
542 enforce_external_state_limit(&self.state)?;
543 let mut state = decode_kv_state(&self.state)?;
544 state.remove(key);
545 self.state = encode_kv_state(&state)?;
546 Ok(())
547 }
548
549 pub fn replace_state(&mut self, state: Vec<u8>) {
550 self.state = state;
551 }
552
553 pub fn read_rows(&self) -> Result<Vec<HashMap<u16, Value>>> {
554 enforce_external_state_limit(&self.state)?;
555 if self.state.is_empty() {
556 Ok(Vec::new())
557 } else {
558 decode_state_rows(&self.state)
559 }
560 }
561
562 pub fn replace_rows(
563 &mut self,
564 schema: &CoreSchema,
565 rows: Vec<HashMap<u16, Value>>,
566 ) -> Result<()> {
567 enforce_external_rows_limit(&rows, None)?;
568 validate_external_rows(schema, &rows)?;
569 self.state = encode_state_rows(&rows)?;
570 Ok(())
571 }
572
573 pub fn emit_base_write(&mut self, op: ExternalBaseWrite) -> Result<()> {
574 if self.base_writes.len() >= EXTERNAL_BASE_WRITES_LIMIT {
575 return Err(external_resource_limit(
576 "external base write count",
577 self.base_writes.len().saturating_add(1),
578 EXTERNAL_BASE_WRITES_LIMIT,
579 ));
580 }
581 let bytes = external_base_write_bytes(&op);
582 let requested = self.base_write_bytes.saturating_add(bytes);
583 if requested > EXTERNAL_BASE_WRITE_BYTES_LIMIT {
584 return Err(external_resource_limit(
585 "external base write bytes",
586 requested,
587 EXTERNAL_BASE_WRITE_BYTES_LIMIT,
588 ));
589 }
590 self.base_write_bytes = requested;
591 self.base_writes.push(op);
592 Ok(())
593 }
594
595 fn into_parts(self) -> (Vec<u8>, Vec<ExternalBaseWrite>) {
596 (self.state, self.base_writes)
597 }
598}
599
600pub struct ExternalModuleRegistry {
601 modules: parking_lot::RwLock<HashMap<String, Arc<dyn ExternalTableModule>>>,
602}
603
604impl Default for ExternalModuleRegistry {
605 fn default() -> Self {
606 let registry = Self {
607 modules: parking_lot::RwLock::new(HashMap::new()),
608 };
609 for module in builtin_modules() {
610 registry.register(module).expect("valid built-in module");
611 }
612 registry
613 }
614}
615
616impl ExternalModuleRegistry {
617 pub fn register(&self, module: Arc<dyn ExternalTableModule>) -> Result<()> {
618 let name = normalize_module_name(module.name())?;
619 self.modules.write().insert(name, module);
620 Ok(())
621 }
622
623 pub fn names(&self) -> Vec<String> {
624 let mut names = self.modules.read().keys().cloned().collect::<Vec<_>>();
625 names.sort();
626 names
627 }
628
629 pub(crate) fn contains(&self, name: &str) -> bool {
630 normalize_module_name(name)
631 .ok()
632 .is_some_and(|name| self.modules.read().contains_key(&name))
633 }
634
635 pub(crate) fn descriptor(&self, name: &str) -> Result<ExternalModuleDescriptor> {
636 Ok(self.module(name)?.descriptor())
637 }
638
639 pub(crate) fn external_table_provider(
640 &self,
641 db: &Arc<Database>,
642 entry: &ExternalTableEntry,
643 query: Option<&RegisteredSqlQuery>,
644 ) -> Result<Arc<dyn TableProvider>> {
645 let module = self.module(&entry.module)?;
646 let fallback = ExecutionControl::new(None);
647 let context = ExternalExecutionContext {
648 database: db,
649 control: query.map_or(&fallback, RegisteredSqlQuery::control),
650 query_id: query.map(RegisteredSqlQuery::id),
651 };
652 query.map(RegisteredSqlQuery::checkpoint).transpose()?;
653 let table = controlled_module_result(query, module.connect_with_control(&context, entry))?;
654 Ok(Arc::new(ExternalTableProvider::new(
655 Arc::clone(db),
656 table,
657 entry.capabilities,
658 )))
659 }
660
661 pub(crate) fn external_table_indexes(
662 &self,
663 db: &Arc<Database>,
664 entry: &ExternalTableEntry,
665 query: &RegisteredSqlQuery,
666 ) -> Result<Vec<ExternalModuleIndex>> {
667 let module = self.module(&entry.module)?;
668 let context = ExternalExecutionContext {
669 database: db,
670 control: query.control(),
671 query_id: Some(query.id()),
672 };
673 query.checkpoint()?;
674 let indexes =
675 controlled_module_result(Some(query), module.indexes_with_control(&context, entry))?;
676 Ok(indexes)
677 }
678
679 pub(crate) fn external_table_rows(
680 &self,
681 db: &Arc<Database>,
682 entry: &ExternalTableEntry,
683 query: &RegisteredSqlQuery,
684 ) -> Result<Vec<HashMap<u16, Value>>> {
685 let module = self.module(&entry.module)?;
686 let context = ExternalExecutionContext {
687 database: db,
688 control: query.control(),
689 query_id: Some(query.id()),
690 };
691 query.checkpoint()?;
692 let rows =
693 controlled_module_result(Some(query), module.read_rows_with_control(&context, entry))?;
694 enforce_external_rows_limit(&rows, Some(query))?;
695 query.checkpoint()?;
696 Ok(rows)
697 }
698
699 pub(crate) fn external_table_rows_from_state(
700 &self,
701 db: &Arc<Database>,
702 entry: &ExternalTableEntry,
703 state: &[u8],
704 query: &RegisteredSqlQuery,
705 ) -> Result<Vec<HashMap<u16, Value>>> {
706 enforce_external_state_limit(state)?;
707 let module = self.module(&entry.module)?;
708 let context = ExternalExecutionContext {
709 database: db,
710 control: query.control(),
711 query_id: Some(query.id()),
712 };
713 query.checkpoint()?;
714 let rows = controlled_module_result(
715 Some(query),
716 module.rows_from_state_with_control(&context, state),
717 )?;
718 enforce_external_rows_limit(&rows, Some(query))?;
719 query.checkpoint()?;
720 Ok(rows)
721 }
722
723 pub(crate) fn external_table_write(
724 &self,
725 db: &Arc<Database>,
726 entry: &ExternalTableEntry,
727 base_state: Vec<u8>,
728 op: ExternalWriteOp,
729 query: &RegisteredSqlQuery,
730 ) -> Result<(Vec<u8>, ExternalWriteResult, Vec<ExternalBaseWrite>)> {
731 enforce_external_state_limit(&base_state)?;
732 match &op {
733 ExternalWriteOp::Insert { rows } | ExternalWriteOp::ReplaceRows { rows, .. } => {
734 enforce_external_rows_limit(rows, None)?;
735 }
736 }
737 let module = self.module(&entry.module)?;
738 let mut txn = ExternalTxn::new(base_state);
739 let context = ExternalExecutionContext {
740 database: db,
741 control: query.control(),
742 query_id: Some(query.id()),
743 };
744 query.checkpoint()?;
745 let result = controlled_module_result(
746 Some(query),
747 module.write_with_control(&context, entry, op, &mut txn),
748 )?;
749 let (state, base_writes) = txn.into_parts();
750 enforce_external_state_limit(&state)?;
751 Ok((state, result, base_writes))
752 }
753
754 pub(crate) fn destroy_external_table(
755 &self,
756 db: &Arc<Database>,
757 entry: &ExternalTableEntry,
758 query: &RegisteredSqlQuery,
759 ) -> Result<()> {
760 let module = self.module(&entry.module)?;
761 let context = ExternalExecutionContext {
762 database: db,
763 control: query.control(),
764 query_id: Some(query.id()),
765 };
766 query.checkpoint()?;
767 controlled_module_result(Some(query), module.destroy_with_control(&context, entry))?;
768 Ok(())
769 }
770
771 fn module(&self, name: &str) -> Result<Arc<dyn ExternalTableModule>> {
772 let name = normalize_module_name(name)?;
773 self.modules.read().get(&name).cloned().ok_or_else(|| {
774 MongrelQueryError::Schema(format!("external table module {name:?} is not registered"))
775 })
776 }
777}
778
779fn normalize_module_name(name: &str) -> Result<String> {
780 let name = name.trim().to_ascii_lowercase();
781 if name.is_empty() || !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
782 return Err(MongrelQueryError::Schema(format!(
783 "invalid external table module name {name:?}"
784 )));
785 }
786 Ok(name)
787}
788
789fn builtin_modules() -> Vec<Arc<dyn ExternalTableModule>> {
790 vec![
791 Arc::new(JsonModule {
792 name: "json_each",
793 mode: JsonTableMode::Each,
794 }),
795 Arc::new(JsonModule {
796 name: "json_tree",
797 mode: JsonTableMode::Tree,
798 }),
799 Arc::new(JsonModule {
800 name: "jsonb_each",
801 mode: JsonTableMode::Each,
802 }),
803 Arc::new(JsonModule {
804 name: "jsonb_tree",
805 mode: JsonTableMode::Tree,
806 }),
807 Arc::new(SchemaTablesModule),
808 Arc::new(DbStatModule),
809 Arc::new(FtsDocsModule),
810 Arc::new(KvStoreModule),
811 Arc::new(RTreeRectsModule),
812 Arc::new(SeriesModule),
813 ]
814}
815
816struct ExternalTableProvider {
817 db: Arc<Database>,
818 table: Arc<dyn ExternalTable>,
819 cache_plans: bool,
820 plan_cache: parking_lot::Mutex<HashMap<ExternalPlanCacheKey, ExternalPlan>>,
821}
822
823struct ExternalTableExec {
824 props: Arc<PlanProperties>,
825 schema: SchemaRef,
826 db: Arc<Database>,
827 table: Arc<dyn ExternalTable>,
828 projection: Option<Vec<usize>>,
829 filters: Vec<Expr>,
830 limit: Option<usize>,
831}
832
833impl std::fmt::Debug for ExternalTableExec {
834 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
835 f.debug_struct("ExternalTableExec").finish_non_exhaustive()
836 }
837}
838
839impl DisplayAs for ExternalTableExec {
840 fn fmt_as(
841 &self,
842 _format: DisplayFormatType,
843 f: &mut std::fmt::Formatter<'_>,
844 ) -> std::fmt::Result {
845 write!(f, "ExternalTableExec")
846 }
847}
848
849impl ExecutionPlan for ExternalTableExec {
850 fn name(&self) -> &str {
851 "ExternalTableExec"
852 }
853
854 fn properties(&self) -> &Arc<PlanProperties> {
855 &self.props
856 }
857
858 fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
859 Vec::new()
860 }
861
862 fn with_new_children(
863 self: Arc<Self>,
864 children: Vec<Arc<dyn ExecutionPlan>>,
865 ) -> DFResult<Arc<dyn ExecutionPlan>> {
866 if children.is_empty() {
867 Ok(self)
868 } else {
869 Err(DataFusionError::Internal(
870 "ExternalTableExec is a leaf node and has no children".into(),
871 ))
872 }
873 }
874
875 fn execute(
876 &self,
877 partition: usize,
878 task_context: Arc<TaskContext>,
879 ) -> DFResult<SendableRecordBatchStream> {
880 use futures::TryStreamExt;
881
882 self.db
883 .ensure_consistent_read()
884 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
885 if partition != 0 {
886 return Err(DataFusionError::Internal(format!(
887 "ExternalTableExec is single-partition; invalid partition {partition}"
888 )));
889 }
890 let query = task_context
891 .session_config()
892 .get_extension::<SqlTaskContext>()
893 .map(|context| context.query().clone());
894 let control = query
895 .as_ref()
896 .map(|query| query.control().child_with_deadline(None))
897 .unwrap_or_else(|| mongreldb_core::ExecutionControl::new(None));
898 control
899 .checkpoint()
900 .map_err(|error| external_control_error(query.as_ref(), error))?;
901 let table = Arc::clone(&self.table);
902 let projection = self.projection.clone();
903 let filters = self.filters.clone();
904 let limit = self.limit;
905 let worker_control = control.clone();
906 let future = async move {
907 let mut task = tokio::task::spawn_blocking(move || {
908 let schema = table.schema();
909 let request = ExternalPlanRequest {
910 projection,
911 filters: filters
912 .iter()
913 .map(|filter| external_filter_from_expr(filter, &schema))
914 .collect(),
915 raw_filters: filters.iter().collect(),
916 order_by: Vec::new(),
917 limit,
918 offset: None,
919 };
920 table.scan_with_control(&request, &worker_control)
921 });
922 let scan = tokio::select! {
923 result = &mut task => result.map_err(|error| {
924 DataFusionError::Execution(format!("external table worker failed: {error}"))
925 })??,
926 _ = control.cancelled() => {
927 if tokio::time::timeout(std::time::Duration::from_millis(100), &mut task)
928 .await
929 .is_err()
930 {
931 eprintln!("external table worker exceeded cancellation grace");
932 }
933 return Err(external_control_error_from_control(query.as_ref(), &control));
934 }
935 };
936 control
937 .checkpoint()
938 .map_err(|error| external_control_error(query.as_ref(), error))?;
939 Ok(futures::stream::iter(scan.batches.into_iter().map(Ok)))
940 };
941 let stream = futures::stream::once(future).try_flatten();
942 Ok(Box::pin(RecordBatchStreamAdapter::new(
943 Arc::clone(&self.schema),
944 stream,
945 )))
946 }
947}
948
949fn external_control_error(
950 query: Option<&crate::RegisteredSqlQuery>,
951 error: mongreldb_core::MongrelError,
952) -> DataFusionError {
953 if let Some(error) = query.and_then(|query| query.checkpoint().err()) {
954 DataFusionError::External(Box::new(error))
955 } else {
956 DataFusionError::Execution(error.to_string())
957 }
958}
959
960fn external_control_error_from_control(
961 query: Option<&crate::RegisteredSqlQuery>,
962 control: &mongreldb_core::ExecutionControl,
963) -> DataFusionError {
964 external_control_error(
965 query,
966 control
967 .checkpoint()
968 .expect_err("cancelled external control must fail its checkpoint"),
969 )
970}
971
972#[derive(Debug, Clone, PartialEq, Eq, Hash)]
973struct ExternalPlanCacheKey {
974 projection: Option<Vec<usize>>,
975 filters: Vec<String>,
976 order_by: Vec<ExternalOrder>,
977 limit: Option<usize>,
978 offset: Option<usize>,
979}
980
981impl ExternalPlanCacheKey {
982 fn from_request(request: &ExternalPlanRequest<'_>) -> Self {
983 Self {
984 projection: request.projection.clone(),
985 filters: request
986 .filters
987 .iter()
988 .map(|filter| format!("{filter:?}"))
989 .collect(),
990 order_by: request.order_by.clone(),
991 limit: request.limit,
992 offset: request.offset,
993 }
994 }
995}
996
997impl ExternalTableProvider {
998 fn new(
999 db: Arc<Database>,
1000 table: Arc<dyn ExternalTable>,
1001 capabilities: ModuleCapabilities,
1002 ) -> Self {
1003 Self {
1004 db,
1005 table,
1006 cache_plans: capabilities.read_only && capabilities.deterministic,
1007 plan_cache: parking_lot::Mutex::new(HashMap::new()),
1008 }
1009 }
1010
1011 fn plan_with_control(
1012 &self,
1013 request: &ExternalPlanRequest<'_>,
1014 control: &ExecutionControl,
1015 ) -> DFResult<ExternalPlan> {
1016 external_df_checkpoint(control)?;
1017 if !self.cache_plans {
1018 return self.table.plan_with_control(request, control);
1019 }
1020 let key = ExternalPlanCacheKey::from_request(request);
1021 if let Some(plan) = self.plan_cache.lock().get(&key).cloned() {
1022 external_df_checkpoint(control)?;
1023 return Ok(plan);
1024 }
1025 let plan = self.table.plan_with_control(request, control)?;
1026 external_df_checkpoint(control)?;
1027 self.plan_cache.lock().insert(key, plan.clone());
1028 Ok(plan)
1029 }
1030}
1031
1032impl std::fmt::Debug for ExternalTableProvider {
1033 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1034 f.debug_struct("ExternalTableProvider")
1035 .field("table", &self.table)
1036 .finish()
1037 }
1038}
1039
1040#[async_trait::async_trait]
1041impl TableProvider for ExternalTableProvider {
1042 fn schema(&self) -> SchemaRef {
1043 self.table.schema()
1044 }
1045
1046 fn table_type(&self) -> TableType {
1047 TableType::Base
1048 }
1049
1050 fn supports_filters_pushdown(
1051 &self,
1052 filters: &[&Expr],
1053 ) -> DFResult<Vec<TableProviderFilterPushDown>> {
1054 let schema = self.table.schema();
1055 let request = ExternalPlanRequest {
1056 projection: None,
1057 filters: filters
1058 .iter()
1059 .map(|filter| external_filter_from_expr(filter, &schema))
1060 .collect(),
1061 raw_filters: filters.to_vec(),
1062 order_by: Vec::new(),
1063 limit: None,
1064 offset: None,
1065 };
1066 let query = crate::CURRENT_SQL_QUERY.try_with(Clone::clone).ok();
1067 let fallback = ExecutionControl::new(None);
1068 let control = query
1069 .as_ref()
1070 .map_or(&fallback, RegisteredSqlQuery::control);
1071 Ok(self.plan_with_control(&request, control)?.filter_pushdown)
1072 }
1073
1074 async fn scan(
1075 &self,
1076 _state: &dyn Session,
1077 projection: Option<&Vec<usize>>,
1078 filters: &[Expr],
1079 limit: Option<usize>,
1080 ) -> DFResult<Arc<dyn ExecutionPlan>> {
1081 mongreldb_core::trace::QueryTrace::record(|t| {
1082 t.scan_mode = mongreldb_core::trace::ScanMode::ExternalModule;
1083 });
1084 let schema = self.table.schema();
1085 let request = ExternalPlanRequest {
1086 projection: projection.cloned(),
1087 filters: filters
1088 .iter()
1089 .map(|filter| external_filter_from_expr(filter, &schema))
1090 .collect(),
1091 raw_filters: filters.iter().collect(),
1092 order_by: Vec::new(),
1093 limit,
1094 offset: None,
1095 };
1096 let query = crate::CURRENT_SQL_QUERY.try_with(Clone::clone).ok();
1097 let fallback = ExecutionControl::new(None);
1098 let control = query
1099 .as_ref()
1100 .map_or(&fallback, RegisteredSqlQuery::control);
1101 let _plan = self.plan_with_control(&request, control)?;
1102 let output_schema = match projection {
1103 Some(projection) => Arc::new(schema.project(projection)?),
1104 None => schema,
1105 };
1106 let props = Arc::new(PlanProperties::new(
1107 EquivalenceProperties::new(Arc::clone(&output_schema)),
1108 Partitioning::UnknownPartitioning(1),
1109 EmissionType::Incremental,
1110 Boundedness::Bounded,
1111 ));
1112 Ok(Arc::new(ExternalTableExec {
1113 props,
1114 schema: output_schema,
1115 db: Arc::clone(&self.db),
1116 table: Arc::clone(&self.table),
1117 projection: projection.cloned(),
1118 filters: filters.to_vec(),
1119 limit,
1120 }))
1121 }
1122}
1123
1124struct JsonModule {
1125 name: &'static str,
1126 mode: JsonTableMode,
1127}
1128
1129impl ExternalTableModule for JsonModule {
1130 fn name(&self) -> &str {
1131 self.name
1132 }
1133
1134 fn descriptor(&self) -> ExternalModuleDescriptor {
1135 json_descriptor()
1136 }
1137
1138 fn connect_with_control(
1139 &self,
1140 ctx: &ExternalExecutionContext<'_>,
1141 entry: &ExternalTableEntry,
1142 ) -> Result<Arc<dyn ExternalTable>> {
1143 ctx.control.checkpoint()?;
1144 let (json, root) = json_args(entry, self.name)?;
1145 let (schema, batches) =
1146 json_table_batches_from_text(self.name, &json, root.as_deref(), self.mode)
1147 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
1148 Ok(Arc::new(JsonExternalTable {
1149 module: self.name,
1150 schema,
1151 batches,
1152 }))
1153 }
1154}
1155
1156#[derive(Clone)]
1157struct JsonExternalTable {
1158 module: &'static str,
1159 schema: SchemaRef,
1160 batches: Vec<RecordBatch>,
1161}
1162
1163impl std::fmt::Debug for JsonExternalTable {
1164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1165 f.debug_struct("JsonExternalTable")
1166 .field("module", &self.module)
1167 .field("schema", &self.schema)
1168 .finish_non_exhaustive()
1169 }
1170}
1171
1172impl ExternalTable for JsonExternalTable {
1173 fn schema(&self) -> SchemaRef {
1174 self.schema.clone()
1175 }
1176
1177 fn plan_with_control(
1178 &self,
1179 request: &ExternalPlanRequest<'_>,
1180 control: &ExecutionControl,
1181 ) -> DFResult<ExternalPlan> {
1182 external_df_checkpoint(control)?;
1183 Ok(ExternalPlan::new(
1184 request
1185 .filters
1186 .iter()
1187 .map(|_| TableProviderFilterPushDown::Unsupported)
1188 .collect(),
1189 None,
1190 1.0,
1191 false,
1192 ))
1193 }
1194
1195 fn scan_with_control(
1196 &self,
1197 request: &ExternalPlanRequest<'_>,
1198 control: &mongreldb_core::ExecutionControl,
1199 ) -> DFResult<ExternalScan> {
1200 project_scan_with_control(
1201 self.schema.clone(),
1202 self.batches.clone(),
1203 request.projection.as_deref(),
1204 request.limit,
1205 Some(control),
1206 )
1207 }
1208}
1209
1210fn json_descriptor() -> ExternalModuleDescriptor {
1211 ExternalModuleDescriptor {
1212 schema: CoreSchema {
1213 schema_id: 0,
1214 columns: vec![
1215 json_column(1, "key", TypeId::Bytes, true),
1216 json_column(2, "value", TypeId::Bytes, true),
1217 json_column(3, "type", TypeId::Bytes, true),
1218 json_column(4, "atom", TypeId::Bytes, true),
1219 json_column(5, "id", TypeId::Int64, false),
1220 json_column(6, "parent", TypeId::Int64, true),
1221 json_column(7, "fullkey", TypeId::Bytes, true),
1222 json_column(8, "path", TypeId::Bytes, true),
1223 json_column(9, "json", TypeId::Bytes, true),
1224 json_column(10, "root", TypeId::Bytes, true),
1225 ],
1226 indexes: Vec::new(),
1227 colocation: Vec::new(),
1228 constraints: Default::default(),
1229 clustered: false,
1230 },
1231 hidden_columns: vec!["json".to_string(), "root".to_string()],
1232 capabilities: ModuleCapabilities {
1233 read_only: true,
1234 deterministic: true,
1235 trigger_safe: true,
1236 ..ModuleCapabilities::default()
1237 },
1238 }
1239}
1240
1241fn json_column(id: u16, name: &str, ty: TypeId, nullable: bool) -> CoreColumnDef {
1242 CoreColumnDef {
1243 id,
1244 name: name.to_string(),
1245 ty,
1246 flags: if nullable {
1247 ColumnFlags::empty().with(ColumnFlags::NULLABLE)
1248 } else {
1249 ColumnFlags::empty()
1250 },
1251 default_value: None,
1252 embedding_source: None,
1253 }
1254}
1255
1256fn json_args(entry: &ExternalTableEntry, module: &str) -> Result<(String, Option<String>)> {
1257 match entry.args.as_slice() {
1258 [json] => Ok((module_arg_string(json).to_string(), None)),
1259 [json, root] => Ok((
1260 module_arg_string(json).to_string(),
1261 Some(module_arg_string(root).to_string()),
1262 )),
1263 _ => Err(MongrelQueryError::Schema(format!(
1264 "{module} external table requires one JSON argument and an optional root path"
1265 ))),
1266 }
1267}
1268
1269fn module_arg_string(arg: &ModuleArg) -> &str {
1270 match arg {
1271 ModuleArg::Ident(value) | ModuleArg::String(value) | ModuleArg::Number(value) => value,
1272 }
1273}
1274
1275struct SchemaTablesModule;
1276
1277impl ExternalTableModule for SchemaTablesModule {
1278 fn name(&self) -> &str {
1279 "schema_tables"
1280 }
1281
1282 fn descriptor(&self) -> ExternalModuleDescriptor {
1283 ExternalModuleDescriptor {
1284 schema: CoreSchema {
1285 schema_id: 0,
1286 columns: vec![
1287 catalog_column(1, "schema_name", TypeId::Bytes, false),
1288 catalog_column(2, "name", TypeId::Bytes, false),
1289 catalog_column(3, "type", TypeId::Bytes, false),
1290 catalog_column(4, "ncol", TypeId::Int64, false),
1291 catalog_column(5, "module", TypeId::Bytes, true),
1292 catalog_column(6, "created_epoch", TypeId::Int64, false),
1293 ],
1294 indexes: Vec::new(),
1295 colocation: Vec::new(),
1296 constraints: Default::default(),
1297 clustered: false,
1298 },
1299 hidden_columns: Vec::new(),
1300 capabilities: catalog_capabilities(),
1301 }
1302 }
1303
1304 fn connect_with_control(
1305 &self,
1306 ctx: &ExternalExecutionContext<'_>,
1307 entry: &ExternalTableEntry,
1308 ) -> Result<Arc<dyn ExternalTable>> {
1309 ctx.control.checkpoint()?;
1310 ensure_no_args(entry, self.name())?;
1311 Ok(Arc::new(SchemaTablesExternalTable {
1312 db: Arc::clone(ctx.database),
1313 schema: schema_tables_arrow_schema(),
1314 }))
1315 }
1316}
1317
1318#[derive(Clone)]
1319struct SchemaTablesExternalTable {
1320 db: Arc<Database>,
1321 schema: SchemaRef,
1322}
1323
1324impl std::fmt::Debug for SchemaTablesExternalTable {
1325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1326 f.debug_struct("SchemaTablesExternalTable")
1327 .field("schema", &self.schema)
1328 .finish_non_exhaustive()
1329 }
1330}
1331
1332impl ExternalTable for SchemaTablesExternalTable {
1333 fn schema(&self) -> SchemaRef {
1334 self.schema.clone()
1335 }
1336
1337 fn plan_with_control(
1338 &self,
1339 request: &ExternalPlanRequest<'_>,
1340 control: &ExecutionControl,
1341 ) -> DFResult<ExternalPlan> {
1342 external_df_checkpoint(control)?;
1343 unsupported_plan(request)
1344 }
1345
1346 fn scan_with_control(
1347 &self,
1348 request: &ExternalPlanRequest<'_>,
1349 control: &mongreldb_core::ExecutionControl,
1350 ) -> DFResult<ExternalScan> {
1351 external_checkpoint(Some(control), 0)?;
1352 let batches = schema_tables_batches(&self.db, self.schema.clone(), Some(control))?;
1353 project_scan_with_control(
1354 self.schema.clone(),
1355 batches,
1356 request.projection.as_deref(),
1357 request.limit,
1358 Some(control),
1359 )
1360 }
1361}
1362
1363struct DbStatModule;
1364
1365impl ExternalTableModule for DbStatModule {
1366 fn name(&self) -> &str {
1367 "dbstat"
1368 }
1369
1370 fn descriptor(&self) -> ExternalModuleDescriptor {
1371 ExternalModuleDescriptor {
1372 schema: CoreSchema {
1373 schema_id: 0,
1374 columns: vec![
1375 catalog_column(1, "name", TypeId::Bytes, false),
1376 catalog_column(2, "type", TypeId::Bytes, false),
1377 catalog_column(3, "rows", TypeId::Int64, true),
1378 catalog_column(4, "runs", TypeId::Int64, true),
1379 catalog_column(5, "memtable_rows", TypeId::Int64, true),
1380 catalog_column(6, "columns", TypeId::Int64, false),
1381 catalog_column(7, "storage_bytes", TypeId::Int64, false),
1382 ],
1383 indexes: Vec::new(),
1384 colocation: Vec::new(),
1385 constraints: Default::default(),
1386 clustered: false,
1387 },
1388 hidden_columns: Vec::new(),
1389 capabilities: catalog_capabilities(),
1390 }
1391 }
1392
1393 fn connect_with_control(
1394 &self,
1395 ctx: &ExternalExecutionContext<'_>,
1396 entry: &ExternalTableEntry,
1397 ) -> Result<Arc<dyn ExternalTable>> {
1398 ctx.control.checkpoint()?;
1399 ensure_no_args(entry, self.name())?;
1400 Ok(Arc::new(DbStatExternalTable {
1401 db: Arc::clone(ctx.database),
1402 schema: dbstat_arrow_schema(),
1403 }))
1404 }
1405}
1406
1407#[derive(Clone)]
1408struct DbStatExternalTable {
1409 db: Arc<Database>,
1410 schema: SchemaRef,
1411}
1412
1413impl std::fmt::Debug for DbStatExternalTable {
1414 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1415 f.debug_struct("DbStatExternalTable")
1416 .field("schema", &self.schema)
1417 .finish_non_exhaustive()
1418 }
1419}
1420
1421impl ExternalTable for DbStatExternalTable {
1422 fn schema(&self) -> SchemaRef {
1423 self.schema.clone()
1424 }
1425
1426 fn plan_with_control(
1427 &self,
1428 request: &ExternalPlanRequest<'_>,
1429 control: &ExecutionControl,
1430 ) -> DFResult<ExternalPlan> {
1431 external_df_checkpoint(control)?;
1432 unsupported_plan(request)
1433 }
1434
1435 fn scan_with_control(
1436 &self,
1437 request: &ExternalPlanRequest<'_>,
1438 control: &mongreldb_core::ExecutionControl,
1439 ) -> DFResult<ExternalScan> {
1440 external_checkpoint(Some(control), 0)?;
1441 let batches = dbstat_batches(&self.db, self.schema.clone(), Some(control))?;
1442 project_scan_with_control(
1443 self.schema.clone(),
1444 batches,
1445 request.projection.as_deref(),
1446 request.limit,
1447 Some(control),
1448 )
1449 }
1450}
1451
1452fn catalog_column(id: u16, name: &str, ty: TypeId, nullable: bool) -> CoreColumnDef {
1453 CoreColumnDef {
1454 id,
1455 name: name.to_string(),
1456 ty,
1457 flags: if nullable {
1458 ColumnFlags::empty().with(ColumnFlags::NULLABLE)
1459 } else {
1460 ColumnFlags::empty()
1461 },
1462 default_value: None,
1463 embedding_source: None,
1464 }
1465}
1466
1467fn catalog_capabilities() -> ModuleCapabilities {
1468 ModuleCapabilities {
1469 read_only: true,
1470 trigger_safe: true,
1471 ..ModuleCapabilities::default()
1472 }
1473}
1474
1475fn ensure_no_args(entry: &ExternalTableEntry, module: &str) -> Result<()> {
1476 if entry.args.is_empty() {
1477 Ok(())
1478 } else {
1479 Err(MongrelQueryError::Schema(format!(
1480 "{module} external table does not accept arguments"
1481 )))
1482 }
1483}
1484
1485fn schema_tables_arrow_schema() -> SchemaRef {
1486 Arc::new(ArrowSchema::new(vec![
1487 Field::new("schema_name", DataType::Utf8, false),
1488 Field::new("name", DataType::Utf8, false),
1489 Field::new("type", DataType::Utf8, false),
1490 Field::new("ncol", DataType::Int64, false),
1491 Field::new("module", DataType::Utf8, true),
1492 Field::new("created_epoch", DataType::Int64, false),
1493 ]))
1494}
1495
1496fn schema_tables_batches(
1497 db: &Arc<Database>,
1498 schema: SchemaRef,
1499 control: Option<&mongreldb_core::ExecutionControl>,
1500) -> DFResult<Vec<RecordBatch>> {
1501 struct Row {
1502 name: String,
1503 ty: String,
1504 ncol: i64,
1505 module: Option<String>,
1506 created_epoch: i64,
1507 }
1508
1509 let catalog = db.catalog_snapshot();
1510 let mut rows = Vec::new();
1511 for (index, table) in catalog
1512 .tables
1513 .into_iter()
1514 .filter(|table| matches!(table.state, TableState::Live))
1515 .enumerate()
1516 {
1517 external_checkpoint(control, index)?;
1518 rows.push(Row {
1519 name: table.name,
1520 ty: "table".to_string(),
1521 ncol: saturating_i64(table.schema.columns.len() as u64),
1522 module: None,
1523 created_epoch: saturating_i64(table.created_epoch),
1524 });
1525 }
1526 for (index, table) in catalog.external_tables.into_iter().enumerate() {
1527 external_checkpoint(control, index)?;
1528 rows.push(Row {
1529 name: table.name,
1530 ty: "external".to_string(),
1531 ncol: saturating_i64(table.declared_schema.columns.len() as u64),
1532 module: Some(table.module),
1533 created_epoch: saturating_i64(table.created_epoch),
1534 });
1535 }
1536 rows.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.ty.cmp(&b.ty)));
1537
1538 let schema_names = vec!["main".to_string(); rows.len()];
1539 let names = rows.iter().map(|row| row.name.clone()).collect::<Vec<_>>();
1540 let types = rows.iter().map(|row| row.ty.clone()).collect::<Vec<_>>();
1541 let ncols = rows.iter().map(|row| row.ncol).collect::<Vec<_>>();
1542 let modules = rows
1543 .iter()
1544 .map(|row| row.module.clone())
1545 .collect::<Vec<_>>();
1546 let created_epochs = rows.iter().map(|row| row.created_epoch).collect::<Vec<_>>();
1547
1548 Ok(vec![RecordBatch::try_new(
1549 schema,
1550 vec![
1551 Arc::new(StringArray::from(schema_names)) as ArrayRef,
1552 Arc::new(StringArray::from(names)),
1553 Arc::new(StringArray::from(types)),
1554 Arc::new(Int64Array::from(ncols)),
1555 Arc::new(StringArray::from(modules)),
1556 Arc::new(Int64Array::from(created_epochs)),
1557 ],
1558 )?])
1559}
1560
1561fn dbstat_arrow_schema() -> SchemaRef {
1562 Arc::new(ArrowSchema::new(vec![
1563 Field::new("name", DataType::Utf8, false),
1564 Field::new("type", DataType::Utf8, false),
1565 Field::new("rows", DataType::Int64, true),
1566 Field::new("runs", DataType::Int64, true),
1567 Field::new("memtable_rows", DataType::Int64, true),
1568 Field::new("columns", DataType::Int64, false),
1569 Field::new("storage_bytes", DataType::Int64, false),
1570 ]))
1571}
1572
1573fn dbstat_batches(
1574 db: &Arc<Database>,
1575 schema: SchemaRef,
1576 control: Option<&mongreldb_core::ExecutionControl>,
1577) -> DFResult<Vec<RecordBatch>> {
1578 struct Row {
1579 name: String,
1580 ty: String,
1581 rows: Option<i64>,
1582 runs: Option<i64>,
1583 memtable_rows: Option<i64>,
1584 columns: i64,
1585 storage_bytes: i64,
1586 }
1587
1588 let catalog = db.catalog_snapshot();
1589 let mut rows = Vec::new();
1590 for (index, table) in catalog
1591 .tables
1592 .into_iter()
1593 .filter(|table| matches!(table.state, TableState::Live))
1594 .enumerate()
1595 {
1596 external_checkpoint(control, index)?;
1597 let handle = db
1598 .table(&table.name)
1599 .map_err(|e| DataFusionError::Execution(e.to_string()))?;
1600 let table_guard = handle.lock();
1601 let table_dir = db.root().join(TABLES_DIR).join(table.table_id.to_string());
1602 rows.push(Row {
1603 name: table.name,
1604 ty: "table".to_string(),
1605 rows: Some(saturating_i64(table_guard.count())),
1606 runs: Some(saturating_i64(table_guard.run_count() as u64)),
1607 memtable_rows: Some(saturating_i64(table_guard.memtable_len() as u64)),
1608 columns: saturating_i64(table.schema.columns.len() as u64),
1609 storage_bytes: saturating_i64(dir_size(&table_dir, control)?),
1610 });
1611 }
1612 for (index, table) in catalog.external_tables.into_iter().enumerate() {
1613 external_checkpoint(control, index)?;
1614 let state_dir = db.root().join(VTAB_DIR).join(&table.name);
1615 rows.push(Row {
1616 name: table.name,
1617 ty: "external".to_string(),
1618 rows: None,
1619 runs: None,
1620 memtable_rows: None,
1621 columns: saturating_i64(table.declared_schema.columns.len() as u64),
1622 storage_bytes: saturating_i64(dir_size(&state_dir, control)?),
1623 });
1624 }
1625 rows.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.ty.cmp(&b.ty)));
1626
1627 let names = rows.iter().map(|row| row.name.clone()).collect::<Vec<_>>();
1628 let types = rows.iter().map(|row| row.ty.clone()).collect::<Vec<_>>();
1629 let row_counts = rows.iter().map(|row| row.rows).collect::<Vec<_>>();
1630 let runs = rows.iter().map(|row| row.runs).collect::<Vec<_>>();
1631 let memtable_rows = rows.iter().map(|row| row.memtable_rows).collect::<Vec<_>>();
1632 let columns = rows.iter().map(|row| row.columns).collect::<Vec<_>>();
1633 let storage_bytes = rows.iter().map(|row| row.storage_bytes).collect::<Vec<_>>();
1634
1635 Ok(vec![RecordBatch::try_new(
1636 schema,
1637 vec![
1638 Arc::new(StringArray::from(names)) as ArrayRef,
1639 Arc::new(StringArray::from(types)),
1640 Arc::new(Int64Array::from(row_counts)),
1641 Arc::new(Int64Array::from(runs)),
1642 Arc::new(Int64Array::from(memtable_rows)),
1643 Arc::new(Int64Array::from(columns)),
1644 Arc::new(Int64Array::from(storage_bytes)),
1645 ],
1646 )?])
1647}
1648
1649fn dir_size(path: &Path, control: Option<&mongreldb_core::ExecutionControl>) -> DFResult<u64> {
1650 external_checkpoint(control, 0)?;
1651 if !path.exists() {
1652 return Ok(0);
1653 }
1654 let metadata = std::fs::symlink_metadata(path)
1655 .map_err(|e| DataFusionError::Execution(format!("stat {:?}: {e}", path)))?;
1656 if metadata.is_file() {
1657 return Ok(metadata.len());
1658 }
1659 if !metadata.is_dir() {
1660 return Ok(0);
1661 }
1662
1663 let mut total = 0_u64;
1664 for (index, entry) in std::fs::read_dir(path)
1665 .map_err(|e| DataFusionError::Execution(format!("{e}")))?
1666 .enumerate()
1667 {
1668 external_checkpoint(control, index)?;
1669 let entry = entry.map_err(|e| DataFusionError::Execution(format!("{e}")))?;
1670 total = total.saturating_add(dir_size(&entry.path(), control)?);
1671 }
1672 Ok(total)
1673}
1674
1675fn saturating_i64(value: u64) -> i64 {
1676 i64::try_from(value).unwrap_or(i64::MAX)
1677}
1678
1679fn unsupported_plan(request: &ExternalPlanRequest<'_>) -> DFResult<ExternalPlan> {
1680 Ok(ExternalPlan::new(
1681 request
1682 .filters
1683 .iter()
1684 .map(|_| TableProviderFilterPushDown::Unsupported)
1685 .collect(),
1686 None,
1687 1.0,
1688 false,
1689 ))
1690}
1691
1692#[cfg(test)]
1693fn project_scan(
1694 full_schema: SchemaRef,
1695 batches: Vec<RecordBatch>,
1696 projection: Option<&[usize]>,
1697 limit: Option<usize>,
1698) -> DFResult<ExternalScan> {
1699 project_scan_with_control(full_schema, batches, projection, limit, None)
1700}
1701
1702fn project_scan_with_control(
1703 full_schema: SchemaRef,
1704 batches: Vec<RecordBatch>,
1705 projection: Option<&[usize]>,
1706 limit: Option<usize>,
1707 control: Option<&mongreldb_core::ExecutionControl>,
1708) -> DFResult<ExternalScan> {
1709 external_checkpoint(control, 0)?;
1710 let Some(projection) = projection else {
1711 return Ok(ExternalScan {
1712 schema: full_schema,
1713 batches: limit_batches(batches, limit, control)?,
1714 });
1715 };
1716 let schema = Arc::new(ArrowSchema::new(
1717 projection
1718 .iter()
1719 .map(|idx| full_schema.field(*idx).clone())
1720 .collect::<Vec<_>>(),
1721 ));
1722 let projected = batches
1723 .into_iter()
1724 .enumerate()
1725 .map(|(index, batch)| {
1726 external_checkpoint(control, index)?;
1727 let columns = projection
1728 .iter()
1729 .map(|idx| batch.column(*idx).clone())
1730 .collect::<Vec<_>>();
1731 let batch = if columns.is_empty() {
1732 RecordBatch::try_new_with_options(
1733 schema.clone(),
1734 columns,
1735 &RecordBatchOptions::new().with_row_count(Some(batch.num_rows())),
1736 )
1737 } else {
1738 RecordBatch::try_new(schema.clone(), columns)
1739 };
1740 batch.map_err(DataFusionError::from)
1741 })
1742 .collect::<DFResult<Vec<_>>>()?;
1743 Ok(ExternalScan {
1744 schema,
1745 batches: limit_batches(projected, limit, control)?,
1746 })
1747}
1748
1749fn limit_batches(
1750 batches: Vec<RecordBatch>,
1751 limit: Option<usize>,
1752 control: Option<&mongreldb_core::ExecutionControl>,
1753) -> DFResult<Vec<RecordBatch>> {
1754 let Some(mut remaining) = limit else {
1755 return Ok(batches);
1756 };
1757 let mut limited = Vec::new();
1758 for (index, batch) in batches.into_iter().enumerate() {
1759 external_checkpoint(control, index)?;
1760 if remaining == 0 {
1761 break;
1762 }
1763 let take = remaining.min(batch.num_rows());
1764 limited.push(batch.slice(0, take));
1765 remaining -= take;
1766 }
1767 Ok(limited)
1768}
1769
1770#[inline]
1771fn external_checkpoint(
1772 control: Option<&mongreldb_core::ExecutionControl>,
1773 index: usize,
1774) -> DFResult<()> {
1775 if index.is_multiple_of(256) {
1776 control
1777 .map(mongreldb_core::ExecutionControl::checkpoint)
1778 .transpose()
1779 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
1780 }
1781 Ok(())
1782}
1783
1784#[cfg(test)]
1785mod tests {
1786 use super::*;
1787
1788 #[derive(Debug)]
1789 struct BlockingExternalModule {
1790 entered: Arc<std::sync::Barrier>,
1791 }
1792
1793 impl BlockingExternalModule {
1794 fn block(&self, control: &ExecutionControl) -> Result<()> {
1795 self.entered.wait();
1796 loop {
1797 control.checkpoint()?;
1798 std::thread::yield_now();
1799 }
1800 }
1801 }
1802
1803 impl ExternalTableModule for BlockingExternalModule {
1804 fn name(&self) -> &str {
1805 "blocking"
1806 }
1807
1808 fn descriptor(&self) -> ExternalModuleDescriptor {
1809 ExternalModuleDescriptor {
1810 schema: CoreSchema::default(),
1811 hidden_columns: Vec::new(),
1812 capabilities: ModuleCapabilities::default(),
1813 }
1814 }
1815
1816 fn indexes_with_control(
1817 &self,
1818 context: &ExternalExecutionContext<'_>,
1819 _entry: &ExternalTableEntry,
1820 ) -> Result<Vec<ExternalModuleIndex>> {
1821 self.block(context.control)?;
1822 unreachable!()
1823 }
1824
1825 fn connect_with_control(
1826 &self,
1827 context: &ExternalExecutionContext<'_>,
1828 _entry: &ExternalTableEntry,
1829 ) -> Result<Arc<dyn ExternalTable>> {
1830 self.block(context.control)?;
1831 unreachable!()
1832 }
1833
1834 fn read_rows_with_control(
1835 &self,
1836 context: &ExternalExecutionContext<'_>,
1837 _entry: &ExternalTableEntry,
1838 ) -> Result<Vec<HashMap<u16, Value>>> {
1839 self.block(context.control)?;
1840 unreachable!()
1841 }
1842
1843 fn prepare_rows_with_control(
1844 &self,
1845 context: &ExternalExecutionContext<'_>,
1846 _entry: &ExternalTableEntry,
1847 _rows: Vec<HashMap<u16, Value>>,
1848 ) -> Result<Vec<u8>> {
1849 self.block(context.control)?;
1850 unreachable!()
1851 }
1852
1853 fn rows_from_state_with_control(
1854 &self,
1855 context: &ExternalExecutionContext<'_>,
1856 _state: &[u8],
1857 ) -> Result<Vec<HashMap<u16, Value>>> {
1858 self.block(context.control)?;
1859 unreachable!()
1860 }
1861
1862 fn write_with_control(
1863 &self,
1864 context: &ExternalExecutionContext<'_>,
1865 _entry: &ExternalTableEntry,
1866 _op: ExternalWriteOp,
1867 _txn: &mut ExternalTxn,
1868 ) -> Result<ExternalWriteResult> {
1869 self.block(context.control)?;
1870 unreachable!()
1871 }
1872
1873 fn destroy_with_control(
1874 &self,
1875 context: &ExternalExecutionContext<'_>,
1876 _entry: &ExternalTableEntry,
1877 ) -> Result<()> {
1878 self.block(context.control)
1879 }
1880 }
1881
1882 impl ExternalTable for BlockingExternalModule {
1883 fn schema(&self) -> SchemaRef {
1884 Arc::new(ArrowSchema::empty())
1885 }
1886
1887 fn plan_with_control(
1888 &self,
1889 _request: &ExternalPlanRequest<'_>,
1890 control: &ExecutionControl,
1891 ) -> DFResult<ExternalPlan> {
1892 self.block(control)
1893 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
1894 unreachable!()
1895 }
1896
1897 fn scan_with_control(
1898 &self,
1899 _request: &ExternalPlanRequest<'_>,
1900 control: &ExecutionControl,
1901 ) -> DFResult<ExternalScan> {
1902 self.block(control)
1903 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
1904 unreachable!()
1905 }
1906 }
1907
1908 fn cancel_blocking_callback<R>(
1909 database: &Arc<Database>,
1910 entry: &ExternalTableEntry,
1911 callback: impl FnOnce(
1912 &BlockingExternalModule,
1913 &ExternalExecutionContext<'_>,
1914 &ExternalTableEntry,
1915 ) -> R,
1916 ) -> R {
1917 let entered = Arc::new(std::sync::Barrier::new(2));
1918 let module = BlockingExternalModule {
1919 entered: Arc::clone(&entered),
1920 };
1921 let control = ExecutionControl::new(None);
1922 let cancel_control = control.clone();
1923 let canceller = std::thread::spawn(move || {
1924 entered.wait();
1925 cancel_control.cancel(mongreldb_core::CancellationReason::ClientRequest);
1926 });
1927 let context = ExternalExecutionContext {
1928 database,
1929 control: &control,
1930 query_id: None,
1931 };
1932 let result = callback(&module, &context, entry);
1933 canceller.join().unwrap();
1934 result
1935 }
1936
1937 #[test]
1938 fn every_external_callback_can_observe_cancellation() {
1939 let dir = tempfile::tempdir().unwrap();
1940 let database = Arc::new(Database::create(dir.path()).unwrap());
1941 let entry = ExternalTableEntry {
1942 name: "blocked".into(),
1943 module: "blocking".into(),
1944 args: Vec::new(),
1945 declared_schema: CoreSchema::default(),
1946 hidden_columns: Vec::new(),
1947 options: BTreeMap::new(),
1948 capabilities: ModuleCapabilities::default(),
1949 created_epoch: 0,
1950 };
1951
1952 let cancelled = |error: &MongrelQueryError| {
1953 matches!(
1954 error,
1955 MongrelQueryError::Core(mongreldb_core::MongrelError::Cancelled)
1956 )
1957 };
1958 assert!(cancelled(
1959 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1960 module.indexes_with_control(context, entry)
1961 })
1962 .unwrap_err()
1963 ));
1964 assert!(cancelled(
1965 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1966 module.connect_with_control(context, entry)
1967 })
1968 .unwrap_err()
1969 ));
1970 assert!(cancelled(
1971 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1972 module.read_rows_with_control(context, entry)
1973 })
1974 .unwrap_err()
1975 ));
1976 assert!(cancelled(
1977 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1978 module.prepare_rows_with_control(context, entry, Vec::new())
1979 })
1980 .unwrap_err()
1981 ));
1982 assert!(cancelled(
1983 &cancel_blocking_callback(&database, &entry, |module, context, _| {
1984 module.rows_from_state_with_control(context, &[])
1985 })
1986 .unwrap_err()
1987 ));
1988 assert!(cancelled(
1989 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1990 module.write_with_control(
1991 context,
1992 entry,
1993 ExternalWriteOp::Insert { rows: Vec::new() },
1994 &mut ExternalTxn::new(Vec::new()),
1995 )
1996 })
1997 .unwrap_err()
1998 ));
1999 assert!(cancelled(
2000 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
2001 module.destroy_with_control(context, entry)
2002 })
2003 .unwrap_err()
2004 ));
2005
2006 let request = ExternalPlanRequest {
2007 projection: None,
2008 filters: Vec::new(),
2009 raw_filters: Vec::new(),
2010 order_by: Vec::new(),
2011 limit: None,
2012 offset: None,
2013 };
2014 let plan_result = cancel_blocking_callback(&database, &entry, |module, context, _| {
2015 module.plan_with_control(&request, context.control)
2016 });
2017 assert!(matches!(
2018 plan_result,
2019 Err(error) if error.to_string().contains("cancelled")
2020 ));
2021 }
2022
2023 #[test]
2024 fn project_scan_applies_limit_after_projection() {
2025 let schema = Arc::new(ArrowSchema::new(vec![
2026 Field::new("id", DataType::Int64, false),
2027 Field::new("value", DataType::Int64, false),
2028 ]));
2029 let batch = RecordBatch::try_new(
2030 schema.clone(),
2031 vec![
2032 Arc::new(Int64Array::from(vec![1, 2, 3, 4])) as ArrayRef,
2033 Arc::new(Int64Array::from(vec![10, 11, 12, 13])) as ArrayRef,
2034 ],
2035 )
2036 .unwrap();
2037
2038 let scan = project_scan(schema, vec![batch], Some(&[1]), Some(2)).unwrap();
2039
2040 assert_eq!(scan.schema.fields().len(), 1);
2041 assert_eq!(scan.schema.field(0).name(), "value");
2042 assert_eq!(scan.batches.len(), 1);
2043 assert_eq!(scan.batches[0].num_columns(), 1);
2044 assert_eq!(scan.batches[0].num_rows(), 2);
2045 let values = scan.batches[0]
2046 .column(0)
2047 .as_any()
2048 .downcast_ref::<Int64Array>()
2049 .unwrap();
2050 assert_eq!(values.value(0), 10);
2051 assert_eq!(values.value(1), 11);
2052 }
2053
2054 #[test]
2055 fn external_plan_derives_accepted_and_residual_filter_metadata() {
2056 let plan = ExternalPlan::new(
2057 vec![
2058 TableProviderFilterPushDown::Exact,
2059 TableProviderFilterPushDown::Inexact,
2060 TableProviderFilterPushDown::Unsupported,
2061 ],
2062 Some(42),
2063 3.5,
2064 true,
2065 );
2066
2067 assert_eq!(
2068 plan.accepted_filters,
2069 vec![
2070 AcceptedFilter {
2071 filter_index: 0,
2072 pushdown: TableProviderFilterPushDown::Exact,
2073 },
2074 AcceptedFilter {
2075 filter_index: 1,
2076 pushdown: TableProviderFilterPushDown::Inexact,
2077 },
2078 ]
2079 );
2080 assert!(plan.residual_filters_required);
2081 assert_eq!(plan.estimated_rows, Some(42));
2082 assert_eq!(plan.estimated_cost, 3.5);
2083 assert!(plan.order_satisfied);
2084 }
2085
2086 #[test]
2087 fn external_state_writer_stops_before_over_budget_allocation() {
2088 use std::io::Write as _;
2089
2090 let mut writer = LimitedStateWriter::new(&[]);
2091 writer.requested = EXTERNAL_STATE_BYTES_LIMIT;
2092 assert!(writer.write_all(b"x").is_err());
2093 assert!(writer.bytes.is_empty());
2094 }
2095
2096 #[test]
2097 fn external_base_write_budget_rejects_before_push() {
2098 let mut txn = ExternalTxn::new(Vec::new());
2099 txn.base_write_bytes = EXTERNAL_BASE_WRITE_BYTES_LIMIT;
2100 let error = txn
2101 .emit_base_write(ExternalBaseWrite::Delete {
2102 table: "t".into(),
2103 row_id: 1,
2104 })
2105 .unwrap_err();
2106 assert!(matches!(
2107 error,
2108 MongrelQueryError::Core(mongreldb_core::MongrelError::ResourceLimitExceeded { .. })
2109 ));
2110 assert!(txn.base_writes.is_empty());
2111 }
2112}
2113
2114struct KvStoreModule;
2115
2116impl ExternalTableModule for KvStoreModule {
2117 fn name(&self) -> &str {
2118 "kv_store"
2119 }
2120
2121 fn descriptor(&self) -> ExternalModuleDescriptor {
2122 ExternalModuleDescriptor {
2123 schema: kv_store_schema(),
2124 hidden_columns: Vec::new(),
2125 capabilities: ModuleCapabilities {
2126 writable: true,
2127 deterministic: true,
2128 trigger_safe: true,
2129 transaction_safe: true,
2130 ..ModuleCapabilities::default()
2131 },
2132 }
2133 }
2134
2135 fn connect_with_control(
2136 &self,
2137 ctx: &ExternalExecutionContext<'_>,
2138 entry: &ExternalTableEntry,
2139 ) -> Result<Arc<dyn ExternalTable>> {
2140 ctx.control.checkpoint()?;
2141 ensure_no_args(entry, self.name())?;
2142 let rows = read_state_rows(ctx.database, entry)?;
2143 let schema = arrow_conv::arrow_schema(&entry.declared_schema)?;
2144 let batches = core_rows_to_batches(&entry.declared_schema, rows)?;
2145 Ok(Arc::new(KvStoreExternalTable { schema, batches }))
2146 }
2147
2148 fn read_rows_with_control(
2149 &self,
2150 ctx: &ExternalExecutionContext<'_>,
2151 entry: &ExternalTableEntry,
2152 ) -> Result<Vec<HashMap<u16, Value>>> {
2153 ctx.control.checkpoint()?;
2154 ensure_no_args(entry, self.name())?;
2155 read_state_rows(ctx.database, entry)
2156 }
2157
2158 fn prepare_rows_with_control(
2159 &self,
2160 ctx: &ExternalExecutionContext<'_>,
2161 entry: &ExternalTableEntry,
2162 rows: Vec<HashMap<u16, Value>>,
2163 ) -> Result<Vec<u8>> {
2164 ctx.control.checkpoint()?;
2165 ensure_no_args(entry, self.name())?;
2166 validate_external_rows(&entry.declared_schema, &rows)?;
2167 encode_state_rows(&rows)
2168 }
2169}
2170
2171#[derive(Clone)]
2172struct KvStoreExternalTable {
2173 schema: SchemaRef,
2174 batches: Vec<RecordBatch>,
2175}
2176
2177impl std::fmt::Debug for KvStoreExternalTable {
2178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2179 f.debug_struct("KvStoreExternalTable")
2180 .field("schema", &self.schema)
2181 .finish_non_exhaustive()
2182 }
2183}
2184
2185impl ExternalTable for KvStoreExternalTable {
2186 fn schema(&self) -> SchemaRef {
2187 self.schema.clone()
2188 }
2189
2190 fn plan_with_control(
2191 &self,
2192 request: &ExternalPlanRequest<'_>,
2193 control: &ExecutionControl,
2194 ) -> DFResult<ExternalPlan> {
2195 external_df_checkpoint(control)?;
2196 unsupported_plan(request)
2197 }
2198
2199 fn scan_with_control(
2200 &self,
2201 request: &ExternalPlanRequest<'_>,
2202 control: &mongreldb_core::ExecutionControl,
2203 ) -> DFResult<ExternalScan> {
2204 project_scan_with_control(
2205 self.schema.clone(),
2206 self.batches.clone(),
2207 request.projection.as_deref(),
2208 request.limit,
2209 Some(control),
2210 )
2211 }
2212}
2213
2214fn kv_store_schema() -> CoreSchema {
2215 CoreSchema {
2216 schema_id: 0,
2217 columns: vec![
2218 CoreColumnDef {
2219 id: 1,
2220 name: "key".to_string(),
2221 ty: TypeId::Bytes,
2222 flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
2223 default_value: None,
2224 embedding_source: None,
2225 },
2226 CoreColumnDef {
2227 id: 2,
2228 name: "value".to_string(),
2229 ty: TypeId::Bytes,
2230 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2231 default_value: None,
2232 embedding_source: None,
2233 },
2234 ],
2235 indexes: Vec::new(),
2236 colocation: Vec::new(),
2237 constraints: Default::default(),
2238 clustered: false,
2239 }
2240}
2241
2242#[derive(Serialize, Deserialize)]
2243struct ExternalRowState {
2244 cells: Vec<(u16, Value)>,
2245}
2246
2247const KV_STATE_MAGIC: &[u8] = b"mongreldb.external.kv.v1\n";
2248
2249#[derive(Serialize, Deserialize)]
2250struct ExternalKvState {
2251 entries: Vec<(Vec<u8>, Vec<u8>)>,
2252}
2253
2254fn state_file(db: &Arc<Database>, entry: &ExternalTableEntry) -> PathBuf {
2255 db.root()
2256 .join(VTAB_DIR)
2257 .join(&entry.name)
2258 .join("state.json")
2259}
2260
2261pub(crate) fn external_table_state_bytes(
2262 db: &Arc<Database>,
2263 entry: &ExternalTableEntry,
2264) -> Result<Vec<u8>> {
2265 db.ensure_consistent_read()?;
2266 let path = state_file(db, entry);
2267 if !path.exists() {
2268 return Ok(Vec::new());
2269 }
2270 let metadata = fs::metadata(&path)
2271 .map_err(|e| MongrelQueryError::Schema(format!("stat external state {:?}: {e}", path)))?;
2272 if metadata.len() > EXTERNAL_STATE_BYTES_LIMIT as u64 {
2273 return Err(external_resource_limit(
2274 "external table state file bytes",
2275 usize::try_from(metadata.len()).unwrap_or(usize::MAX),
2276 EXTERNAL_STATE_BYTES_LIMIT,
2277 ));
2278 }
2279 let state = fs::read(&path)
2280 .map_err(|e| MongrelQueryError::Schema(format!("read external state {:?}: {e}", path)))?;
2281 enforce_external_state_limit(&state)?;
2282 Ok(state)
2283}
2284
2285fn read_state_rows(
2286 db: &Arc<Database>,
2287 entry: &ExternalTableEntry,
2288) -> Result<Vec<HashMap<u16, Value>>> {
2289 let bytes = external_table_state_bytes(db, entry)?;
2290 if bytes.is_empty() {
2291 return Ok(Vec::new());
2292 }
2293 decode_state_rows(&bytes)
2294}
2295
2296struct LimitedStateWriter {
2297 bytes: Vec<u8>,
2298 requested: usize,
2299}
2300
2301impl LimitedStateWriter {
2302 fn new(prefix: &[u8]) -> Self {
2303 Self {
2304 bytes: prefix.to_vec(),
2305 requested: prefix.len(),
2306 }
2307 }
2308}
2309
2310impl std::io::Write for LimitedStateWriter {
2311 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2312 self.requested = self.requested.saturating_add(buf.len());
2313 if self.requested > EXTERNAL_STATE_BYTES_LIMIT {
2314 return Err(std::io::Error::other("external state byte limit exceeded"));
2315 }
2316 self.bytes.extend_from_slice(buf);
2317 Ok(buf.len())
2318 }
2319
2320 fn flush(&mut self) -> std::io::Result<()> {
2321 Ok(())
2322 }
2323}
2324
2325fn encode_external_json<T: Serialize + ?Sized>(value: &T, prefix: &[u8]) -> Result<Vec<u8>> {
2326 let mut writer = LimitedStateWriter::new(prefix);
2327 if let Err(error) = serde_json::to_writer(&mut writer, value) {
2328 if writer.requested > EXTERNAL_STATE_BYTES_LIMIT {
2329 return Err(external_resource_limit(
2330 "external table encoded state bytes",
2331 writer.requested,
2332 EXTERNAL_STATE_BYTES_LIMIT,
2333 ));
2334 }
2335 return Err(MongrelQueryError::Schema(format!(
2336 "encode external state: {error}"
2337 )));
2338 }
2339 Ok(writer.bytes)
2340}
2341
2342fn encode_state_rows(rows: &[HashMap<u16, Value>]) -> Result<Vec<u8>> {
2343 enforce_external_rows_limit(rows, None)?;
2344 let state = rows
2345 .iter()
2346 .map(|row| {
2347 let mut cells = row
2348 .iter()
2349 .map(|(id, value)| (*id, value.clone()))
2350 .collect::<Vec<_>>();
2351 cells.sort_by_key(|(id, _)| *id);
2352 ExternalRowState { cells }
2353 })
2354 .collect::<Vec<_>>();
2355 encode_external_json(&state, &[])
2356}
2357
2358fn decode_state_rows(state: &[u8]) -> Result<Vec<HashMap<u16, Value>>> {
2359 enforce_external_state_limit(state)?;
2360 let rows: Vec<ExternalRowState> = serde_json::from_slice(state)
2361 .map_err(|e| MongrelQueryError::Schema(format!("decode external state: {e}")))?;
2362 let rows = rows
2363 .into_iter()
2364 .map(|row| row.cells.into_iter().collect())
2365 .collect::<Vec<_>>();
2366 enforce_external_rows_limit(&rows, None)?;
2367 Ok(rows)
2368}
2369
2370fn decode_kv_state(state: &[u8]) -> Result<BTreeMap<Vec<u8>, Vec<u8>>> {
2371 enforce_external_state_limit(state)?;
2372 if state.is_empty() {
2373 return Ok(BTreeMap::new());
2374 }
2375 let json = state.strip_prefix(KV_STATE_MAGIC).ok_or_else(|| {
2376 MongrelQueryError::Schema(
2377 "external transaction state is not in key/value module format".into(),
2378 )
2379 })?;
2380 let decoded: ExternalKvState = serde_json::from_slice(json)
2381 .map_err(|e| MongrelQueryError::Schema(format!("decode external kv state: {e}")))?;
2382 if decoded.entries.len() > EXTERNAL_ROWS_LIMIT {
2383 return Err(external_resource_limit(
2384 "external transaction key/value count",
2385 decoded.entries.len(),
2386 EXTERNAL_ROWS_LIMIT,
2387 ));
2388 }
2389 let bytes = decoded.entries.iter().fold(0_usize, |total, (key, value)| {
2390 total.saturating_add(key.len()).saturating_add(value.len())
2391 });
2392 if bytes > EXTERNAL_STATE_BYTES_LIMIT {
2393 return Err(external_resource_limit(
2394 "external transaction key/value bytes",
2395 bytes,
2396 EXTERNAL_STATE_BYTES_LIMIT,
2397 ));
2398 }
2399 Ok(decoded.entries.into_iter().collect())
2400}
2401
2402fn encode_kv_state(state: &BTreeMap<Vec<u8>, Vec<u8>>) -> Result<Vec<u8>> {
2403 if state.len() > EXTERNAL_ROWS_LIMIT {
2404 return Err(external_resource_limit(
2405 "external transaction key/value count",
2406 state.len(),
2407 EXTERNAL_ROWS_LIMIT,
2408 ));
2409 }
2410 let bytes = state.iter().fold(0_usize, |total, (key, value)| {
2411 total.saturating_add(key.len()).saturating_add(value.len())
2412 });
2413 if bytes > EXTERNAL_STATE_BYTES_LIMIT {
2414 return Err(external_resource_limit(
2415 "external transaction key/value bytes",
2416 bytes,
2417 EXTERNAL_STATE_BYTES_LIMIT,
2418 ));
2419 }
2420 let encoded = ExternalKvState {
2421 entries: state
2422 .iter()
2423 .map(|(key, value)| (key.clone(), value.clone()))
2424 .collect(),
2425 };
2426 encode_external_json(&encoded, KV_STATE_MAGIC)
2427}
2428
2429fn validate_external_rows(schema: &CoreSchema, rows: &[HashMap<u16, Value>]) -> Result<()> {
2430 enforce_external_rows_limit(rows, None)?;
2431 let mut known = HashSet::new();
2432 let mut pk_cols = Vec::new();
2433 for column in &schema.columns {
2434 known.insert(column.id);
2435 if column.flags.contains(ColumnFlags::PRIMARY_KEY) {
2436 pk_cols.push(column.id);
2437 }
2438 }
2439 let mut keys = HashSet::new();
2440 for row in rows {
2441 for column_id in row.keys() {
2442 if !known.contains(column_id) {
2443 return Err(MongrelQueryError::Schema(format!(
2444 "external row contains unknown column id {column_id}"
2445 )));
2446 }
2447 }
2448 if !pk_cols.is_empty() {
2449 let mut key = Vec::new();
2450 for column_id in &pk_cols {
2451 let value = row.get(column_id).ok_or_else(|| {
2452 MongrelQueryError::Schema(format!(
2453 "external row is missing primary key column {column_id}"
2454 ))
2455 })?;
2456 key.extend_from_slice(&column_id.to_be_bytes());
2457 key.extend_from_slice(&value.encode_key());
2458 key.push(0);
2459 }
2460 if !keys.insert(key) {
2461 return Err(MongrelQueryError::Schema(
2462 "external table primary key conflict".into(),
2463 ));
2464 }
2465 }
2466 }
2467 Ok(())
2468}
2469
2470fn core_rows_to_batches(
2471 schema: &CoreSchema,
2472 rows: Vec<HashMap<u16, Value>>,
2473) -> Result<Vec<RecordBatch>> {
2474 let arrow_schema = arrow_conv::arrow_schema(schema)?;
2475 let arrays = schema
2476 .columns
2477 .iter()
2478 .map(|column| {
2479 let values = rows
2480 .iter()
2481 .map(|row| row.get(&column.id).cloned().unwrap_or(Value::Null))
2482 .collect::<Vec<_>>();
2483 arrow_conv::build_array(column.ty.clone(), &values)
2484 })
2485 .collect::<Result<Vec<_>>>()?;
2486 Ok(vec![RecordBatch::try_new(arrow_schema, arrays)
2487 .map_err(|e| MongrelQueryError::Arrow(e.to_string()))?])
2488}
2489
2490struct FtsDocsModule;
2491
2492impl ExternalTableModule for FtsDocsModule {
2493 fn name(&self) -> &str {
2494 "fts_docs"
2495 }
2496
2497 fn descriptor(&self) -> ExternalModuleDescriptor {
2498 ExternalModuleDescriptor {
2499 schema: fts_docs_schema(),
2500 hidden_columns: vec!["query".to_string()],
2501 capabilities: ModuleCapabilities {
2502 writable: true,
2503 deterministic: true,
2504 trigger_safe: true,
2505 ..ModuleCapabilities::default()
2506 },
2507 }
2508 }
2509
2510 fn indexes_with_control(
2511 &self,
2512 context: &ExternalExecutionContext<'_>,
2513 entry: &ExternalTableEntry,
2514 ) -> Result<Vec<ExternalModuleIndex>> {
2515 context.control.checkpoint()?;
2516 Ok(vec![ExternalModuleIndex::new(
2517 format!("{}_fts_inverted", entry.name),
2518 vec![2],
2519 )])
2520 }
2521
2522 fn connect_with_control(
2523 &self,
2524 ctx: &ExternalExecutionContext<'_>,
2525 entry: &ExternalTableEntry,
2526 ) -> Result<Arc<dyn ExternalTable>> {
2527 ctx.control.checkpoint()?;
2528 let options = fts_options(entry)?;
2529 let rows = read_state_rows(ctx.database, entry)?;
2530 let index = FtsInvertedIndex::build(&rows, &options);
2531 Ok(Arc::new(FtsDocsExternalTable {
2532 schema: arrow_conv::arrow_schema(&entry.declared_schema)?,
2533 core_schema: entry.declared_schema.clone(),
2534 options,
2535 index,
2536 rows,
2537 }))
2538 }
2539
2540 fn read_rows_with_control(
2541 &self,
2542 ctx: &ExternalExecutionContext<'_>,
2543 entry: &ExternalTableEntry,
2544 ) -> Result<Vec<HashMap<u16, Value>>> {
2545 ctx.control.checkpoint()?;
2546 let _ = fts_options(entry)?;
2547 read_state_rows(ctx.database, entry)
2548 }
2549
2550 fn prepare_rows_with_control(
2551 &self,
2552 ctx: &ExternalExecutionContext<'_>,
2553 entry: &ExternalTableEntry,
2554 rows: Vec<HashMap<u16, Value>>,
2555 ) -> Result<Vec<u8>> {
2556 ctx.control.checkpoint()?;
2557 let _ = fts_options(entry)?;
2558 let rows = rows
2559 .into_iter()
2560 .map(|mut row| {
2561 row.remove(&3);
2562 row.remove(&4);
2563 row.remove(&5);
2564 row.remove(&6);
2565 row
2566 })
2567 .collect::<Vec<_>>();
2568 validate_external_rows(&entry.declared_schema, &rows)?;
2569 encode_state_rows(&rows)
2570 }
2571}
2572
2573#[derive(Clone)]
2574struct FtsDocsExternalTable {
2575 schema: SchemaRef,
2576 core_schema: CoreSchema,
2577 options: FtsOptions,
2578 index: FtsInvertedIndex,
2579 rows: Vec<HashMap<u16, Value>>,
2580}
2581
2582impl std::fmt::Debug for FtsDocsExternalTable {
2583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2584 f.debug_struct("FtsDocsExternalTable")
2585 .field("schema", &self.schema)
2586 .finish_non_exhaustive()
2587 }
2588}
2589
2590impl ExternalTable for FtsDocsExternalTable {
2591 fn schema(&self) -> SchemaRef {
2592 self.schema.clone()
2593 }
2594
2595 fn plan_with_control(
2596 &self,
2597 request: &ExternalPlanRequest<'_>,
2598 control: &ExecutionControl,
2599 ) -> DFResult<ExternalPlan> {
2600 external_df_checkpoint(control)?;
2601 Ok(ExternalPlan::new(
2602 request
2603 .raw_filters
2604 .iter()
2605 .map(|filter| {
2606 if fts_query_from_expr(filter, &self.options).is_some() {
2607 TableProviderFilterPushDown::Exact
2608 } else {
2609 TableProviderFilterPushDown::Unsupported
2610 }
2611 })
2612 .collect(),
2613 None,
2614 1.0,
2615 false,
2616 ))
2617 }
2618
2619 fn scan_with_control(
2620 &self,
2621 request: &ExternalPlanRequest<'_>,
2622 control: &mongreldb_core::ExecutionControl,
2623 ) -> DFResult<ExternalScan> {
2624 external_checkpoint(Some(control), 0)?;
2625 let query = request
2626 .raw_filters
2627 .iter()
2628 .filter_map(|filter| fts_query_from_expr(filter, &self.options))
2629 .reduce(FtsQuery::and);
2630 let mut rows = Vec::new();
2631 if let Some(query) = query.as_ref() {
2632 for (index, row_index) in self.index.candidates(query).into_iter().enumerate() {
2633 external_checkpoint(Some(control), index)?;
2634 let Some(row) = self.rows.get(row_index) else {
2635 continue;
2636 };
2637 let Some(score) = fts_match_score(row, query, &self.options) else {
2638 continue;
2639 };
2640 rows.push(fts_enrich_row(
2641 row.clone(),
2642 Some(query),
2643 &self.options,
2644 Some(score),
2645 ));
2646 }
2647 } else {
2648 rows.reserve(self.rows.len());
2649 for (index, row) in self.rows.iter().cloned().enumerate() {
2650 external_checkpoint(Some(control), index)?;
2651 rows.push(fts_enrich_row(row, None, &self.options, None));
2652 }
2653 }
2654 external_checkpoint(Some(control), 0)?;
2655 project_scan_with_control(
2656 self.schema.clone(),
2657 core_rows_to_batches(&self.core_schema, rows)
2658 .map_err(|e| DataFusionError::Execution(e.to_string()))?,
2659 request.projection.as_deref(),
2660 request.limit,
2661 Some(control),
2662 )
2663 }
2664}
2665
2666fn fts_docs_schema() -> CoreSchema {
2667 CoreSchema {
2668 schema_id: 0,
2669 columns: vec![
2670 CoreColumnDef {
2671 id: 1,
2672 name: "doc_id".to_string(),
2673 ty: TypeId::Int64,
2674 flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
2675 default_value: None,
2676 embedding_source: None,
2677 },
2678 CoreColumnDef {
2679 id: 2,
2680 name: "text".to_string(),
2681 ty: TypeId::Bytes,
2682 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2683 default_value: None,
2684 embedding_source: None,
2685 },
2686 CoreColumnDef {
2687 id: 3,
2688 name: "query".to_string(),
2689 ty: TypeId::Bytes,
2690 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2691 default_value: None,
2692 embedding_source: None,
2693 },
2694 CoreColumnDef {
2695 id: 4,
2696 name: "rank".to_string(),
2697 ty: TypeId::Float64,
2698 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2699 default_value: None,
2700 embedding_source: None,
2701 },
2702 CoreColumnDef {
2703 id: 5,
2704 name: "snippet".to_string(),
2705 ty: TypeId::Bytes,
2706 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2707 default_value: None,
2708 embedding_source: None,
2709 },
2710 CoreColumnDef {
2711 id: 6,
2712 name: "highlight".to_string(),
2713 ty: TypeId::Bytes,
2714 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2715 default_value: None,
2716 embedding_source: None,
2717 },
2718 ],
2719 indexes: Vec::new(),
2720 colocation: Vec::new(),
2721 constraints: Default::default(),
2722 clustered: false,
2723 }
2724}
2725
2726#[derive(Clone)]
2727struct FtsOptions {
2728 prefix_queries: bool,
2729 case_sensitive: bool,
2730 min_token_len: usize,
2731 stopwords: HashSet<String>,
2732}
2733
2734impl Default for FtsOptions {
2735 fn default() -> Self {
2736 Self {
2737 prefix_queries: false,
2738 case_sensitive: false,
2739 min_token_len: 1,
2740 stopwords: HashSet::new(),
2741 }
2742 }
2743}
2744
2745impl FtsOptions {
2746 fn normalize(&self, value: &str) -> String {
2747 if self.case_sensitive {
2748 value.to_string()
2749 } else {
2750 value.to_ascii_lowercase()
2751 }
2752 }
2753
2754 fn keep_term(&self, term: &str) -> bool {
2755 term.len() >= self.min_token_len && !self.stopwords.contains(term)
2756 }
2757}
2758
2759#[derive(Clone)]
2760struct FtsQuery {
2761 groups: Vec<Vec<FtsClause>>,
2762 prohibited: Vec<FtsClause>,
2763}
2764
2765impl FtsQuery {
2766 fn and(self, other: Self) -> Self {
2767 let left_groups = if self.groups.is_empty() {
2768 vec![Vec::new()]
2769 } else {
2770 self.groups
2771 };
2772 let right_groups = if other.groups.is_empty() {
2773 vec![Vec::new()]
2774 } else {
2775 other.groups
2776 };
2777 let mut groups = Vec::new();
2778 for left in &left_groups {
2779 for right in &right_groups {
2780 let mut combined = left.clone();
2781 combined.extend(right.clone());
2782 groups.push(combined);
2783 }
2784 }
2785 let mut prohibited = self.prohibited;
2786 prohibited.extend(other.prohibited);
2787 Self { groups, prohibited }
2788 }
2789
2790 fn positive_clauses(&self) -> impl Iterator<Item = &FtsClause> {
2791 self.groups.iter().flatten()
2792 }
2793}
2794
2795#[derive(Clone)]
2796struct FtsInvertedIndex {
2797 terms: HashMap<String, Vec<usize>>,
2798 all_rows: Vec<usize>,
2799}
2800
2801impl FtsInvertedIndex {
2802 fn build(rows: &[HashMap<u16, Value>], options: &FtsOptions) -> Self {
2803 let mut term_sets: HashMap<String, BTreeSet<usize>> = HashMap::new();
2804 for (idx, row) in rows.iter().enumerate() {
2805 let Some(Value::Bytes(text)) = row.get(&2) else {
2806 continue;
2807 };
2808 let text = String::from_utf8_lossy(text);
2809 let mut row_terms = HashSet::new();
2810 for span in token_spans_with_options(&text, options) {
2811 if row_terms.insert(span.term.clone()) {
2812 term_sets.entry(span.term).or_default().insert(idx);
2813 }
2814 }
2815 }
2816 let terms = term_sets
2817 .into_iter()
2818 .map(|(term, rows)| (term, rows.into_iter().collect()))
2819 .collect();
2820 Self {
2821 terms,
2822 all_rows: (0..rows.len()).collect(),
2823 }
2824 }
2825
2826 fn candidates(&self, query: &FtsQuery) -> Vec<usize> {
2827 let mut rows = if query.groups.is_empty() {
2828 self.all_rows.iter().copied().collect::<BTreeSet<_>>()
2829 } else {
2830 query
2831 .groups
2832 .iter()
2833 .filter_map(|group| self.group_candidates(group))
2834 .flatten()
2835 .collect::<BTreeSet<_>>()
2836 };
2837 for clause in &query.prohibited {
2838 for idx in self.clause_candidates(clause) {
2839 rows.remove(&idx);
2840 }
2841 }
2842 rows.into_iter().collect()
2843 }
2844
2845 fn group_candidates(&self, group: &[FtsClause]) -> Option<Vec<usize>> {
2846 let mut iter = group.iter().map(|clause| self.clause_candidates(clause));
2847 let first = iter.next()?;
2848 let mut current = first.into_iter().collect::<BTreeSet<_>>();
2849 for rows in iter {
2850 let next = rows.into_iter().collect::<BTreeSet<_>>();
2851 current = current.intersection(&next).copied().collect();
2852 if current.is_empty() {
2853 break;
2854 }
2855 }
2856 Some(current.into_iter().collect())
2857 }
2858
2859 fn clause_candidates(&self, clause: &FtsClause) -> Vec<usize> {
2860 match clause {
2861 FtsClause::Term { term, prefix } => {
2862 if *prefix {
2863 self.terms
2864 .iter()
2865 .filter(|(indexed, _)| indexed.starts_with(term))
2866 .flat_map(|(_, rows)| rows.iter().copied())
2867 .collect::<BTreeSet<_>>()
2868 .into_iter()
2869 .collect()
2870 } else {
2871 self.terms.get(term).cloned().unwrap_or_default()
2872 }
2873 }
2874 FtsClause::Phrase(terms) => {
2875 let clauses = terms
2876 .iter()
2877 .map(|term| FtsClause::Term {
2878 term: term.clone(),
2879 prefix: false,
2880 })
2881 .collect::<Vec<_>>();
2882 self.group_candidates(&clauses).unwrap_or_default()
2883 }
2884 }
2885 }
2886}
2887
2888#[derive(Clone, PartialEq, Eq)]
2889enum FtsClause {
2890 Term { term: String, prefix: bool },
2891 Phrase(Vec<String>),
2892}
2893
2894enum FtsQueryToken {
2895 Clause(FtsClause),
2896 Or,
2897 Not,
2898}
2899
2900fn fts_options(entry: &ExternalTableEntry) -> Result<FtsOptions> {
2901 let mut options = FtsOptions::default();
2902 for arg in &entry.args {
2903 let raw = module_arg_string(arg).trim();
2904 if raw.is_empty() {
2905 continue;
2906 }
2907 let (key, value) = raw
2908 .split_once('=')
2909 .map_or((raw, "true"), |(key, value)| (key.trim(), value.trim()));
2910 match key.to_ascii_lowercase().as_str() {
2911 "tokenizer" => match value.to_ascii_lowercase().as_str() {
2912 "simple" | "ascii" | "unicode61" => {}
2913 other => {
2914 return Err(MongrelQueryError::Schema(format!(
2915 "fts_docs tokenizer {other:?} is not supported"
2916 )))
2917 }
2918 },
2919 "prefix" | "prefix_queries" => options.prefix_queries = parse_bool_option(value)?,
2920 "case_sensitive" => options.case_sensitive = parse_bool_option(value)?,
2921 "min_token_len" => {
2922 options.min_token_len = value.parse::<usize>().map_err(|e| {
2923 MongrelQueryError::Schema(format!(
2924 "fts_docs min_token_len {value:?} must be an integer: {e}"
2925 ))
2926 })?;
2927 if options.min_token_len == 0 {
2928 return Err(MongrelQueryError::Schema(
2929 "fts_docs min_token_len must be at least 1".into(),
2930 ));
2931 }
2932 }
2933 "stopwords" => {
2934 options.stopwords = value
2935 .split('|')
2936 .map(str::trim)
2937 .filter(|word| !word.is_empty())
2938 .map(|word| options.normalize(word))
2939 .collect();
2940 }
2941 other => {
2942 return Err(MongrelQueryError::Schema(format!(
2943 "fts_docs option {other:?} is not supported"
2944 )))
2945 }
2946 }
2947 }
2948 Ok(options)
2949}
2950
2951fn parse_bool_option(value: &str) -> Result<bool> {
2952 match value.to_ascii_lowercase().as_str() {
2953 "1" | "true" | "yes" | "on" => Ok(true),
2954 "0" | "false" | "no" | "off" => Ok(false),
2955 _ => Err(MongrelQueryError::Schema(format!(
2956 "expected boolean option value, got {value:?}"
2957 ))),
2958 }
2959}
2960
2961fn fts_query_from_expr(expr: &Expr, options: &FtsOptions) -> Option<FtsQuery> {
2962 match expr {
2963 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::And => {
2964 Some(fts_query_from_expr(left, options)?.and(fts_query_from_expr(right, options)?))
2965 }
2966 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::Eq => {
2967 let literal = match (left.as_ref(), right.as_ref()) {
2968 (Expr::Column(column), Expr::Literal(literal, _)) if column.name == "query" => {
2969 literal_string(literal)?
2970 }
2971 (Expr::Literal(literal, _), Expr::Column(column)) if column.name == "query" => {
2972 literal_string(literal)?
2973 }
2974 _ => return None,
2975 };
2976 parse_fts_query(&literal, options)
2977 }
2978 _ => None,
2979 }
2980}
2981
2982fn parse_fts_query(input: &str, options: &FtsOptions) -> Option<FtsQuery> {
2983 let tokens = fts_query_tokens(input, options);
2984 if tokens.is_empty() {
2985 return None;
2986 }
2987 let mut groups: Vec<Vec<FtsClause>> = vec![Vec::new()];
2988 let mut prohibited = Vec::new();
2989 let mut negate = false;
2990 for token in tokens {
2991 match token {
2992 FtsQueryToken::Or => {
2993 if groups.last().is_some_and(|group| !group.is_empty()) {
2994 groups.push(Vec::new());
2995 }
2996 negate = false;
2997 }
2998 FtsQueryToken::Not => negate = true,
2999 FtsQueryToken::Clause(clause) => {
3000 if negate {
3001 prohibited.push(clause);
3002 negate = false;
3003 } else if let Some(group) = groups.last_mut() {
3004 group.push(clause);
3005 }
3006 }
3007 }
3008 }
3009 groups.retain(|group| !group.is_empty());
3010 if groups.is_empty() && prohibited.is_empty() {
3011 None
3012 } else {
3013 Some(FtsQuery { groups, prohibited })
3014 }
3015}
3016
3017fn fts_query_tokens(input: &str, options: &FtsOptions) -> Vec<FtsQueryToken> {
3018 let mut tokens = Vec::new();
3019 let mut chars = input.char_indices().peekable();
3020 while let Some((idx, ch)) = chars.next() {
3021 if ch.is_whitespace() {
3022 continue;
3023 }
3024 if ch == '"' {
3025 let start = idx + ch.len_utf8();
3026 let mut end = input.len();
3027 for (next_idx, next_ch) in chars.by_ref() {
3028 if next_ch == '"' {
3029 end = next_idx;
3030 break;
3031 }
3032 }
3033 let terms = token_spans_with_options(&input[start..end], options)
3034 .into_iter()
3035 .map(|span| span.term)
3036 .collect::<Vec<_>>();
3037 if !terms.is_empty() {
3038 tokens.push(FtsQueryToken::Clause(FtsClause::Phrase(terms)));
3039 }
3040 continue;
3041 }
3042 let start = idx;
3043 let mut end = idx + ch.len_utf8();
3044 while let Some((next_idx, next_ch)) = chars.peek().copied() {
3045 if next_ch.is_whitespace() {
3046 break;
3047 }
3048 chars.next();
3049 end = next_idx + next_ch.len_utf8();
3050 }
3051 let raw = &input[start..end];
3052 if raw.eq_ignore_ascii_case("OR") {
3053 tokens.push(FtsQueryToken::Or);
3054 } else if raw.eq_ignore_ascii_case("NOT") {
3055 tokens.push(FtsQueryToken::Not);
3056 } else if let Some(rest) = raw.strip_prefix('-') {
3057 tokens.push(FtsQueryToken::Not);
3058 tokens.extend(
3059 word_clauses(rest, options)
3060 .into_iter()
3061 .map(FtsQueryToken::Clause),
3062 );
3063 } else {
3064 tokens.extend(
3065 word_clauses(raw, options)
3066 .into_iter()
3067 .map(FtsQueryToken::Clause),
3068 );
3069 }
3070 }
3071 tokens
3072}
3073
3074fn word_clauses(raw: &str, options: &FtsOptions) -> Vec<FtsClause> {
3075 let raw = raw.trim_matches(|ch: char| !ch.is_ascii_alphanumeric() && ch != '*');
3076 if raw.is_empty() {
3077 return Vec::new();
3078 }
3079 let prefix = options.prefix_queries && raw.ends_with('*');
3080 let raw = raw.trim_end_matches('*');
3081 token_spans_with_options(raw, options)
3082 .into_iter()
3083 .map(|span| FtsClause::Term {
3084 term: span.term,
3085 prefix,
3086 })
3087 .collect()
3088}
3089
3090fn fts_match_score(
3091 row: &HashMap<u16, Value>,
3092 query: &FtsQuery,
3093 options: &FtsOptions,
3094) -> Option<f64> {
3095 let Some(Value::Bytes(text)) = row.get(&2) else {
3096 return None;
3097 };
3098 let text = String::from_utf8_lossy(text);
3099 let tokens = token_spans_with_options(&text, options);
3100 if query
3101 .prohibited
3102 .iter()
3103 .any(|clause| clause_matches(&tokens, clause))
3104 {
3105 return None;
3106 }
3107 let positive_score = if query.groups.is_empty() {
3108 Some(0.0)
3109 } else {
3110 query
3111 .groups
3112 .iter()
3113 .filter(|group| group.iter().all(|clause| clause_matches(&tokens, clause)))
3114 .map(|group| {
3115 group
3116 .iter()
3117 .map(|clause| clause_score(&tokens, clause))
3118 .sum()
3119 })
3120 .max_by(|left: &f64, right: &f64| left.total_cmp(right))
3121 }?;
3122 Some(positive_score)
3123}
3124
3125fn clause_matches(tokens: &[TokenSpan], clause: &FtsClause) -> bool {
3126 clause_score(tokens, clause) > 0.0
3127}
3128
3129fn clause_score(tokens: &[TokenSpan], clause: &FtsClause) -> f64 {
3130 match clause {
3131 FtsClause::Term { term, prefix } => tokens
3132 .iter()
3133 .filter(|token| token_matches_term(&token.term, term, *prefix))
3134 .count() as f64,
3135 FtsClause::Phrase(terms) => phrase_occurrences(tokens, terms) as f64 * terms.len() as f64,
3136 }
3137}
3138
3139fn token_matches_term(token: &str, term: &str, prefix: bool) -> bool {
3140 if prefix {
3141 token.starts_with(term)
3142 } else {
3143 token == term
3144 }
3145}
3146
3147fn phrase_occurrences(tokens: &[TokenSpan], terms: &[String]) -> usize {
3148 if terms.is_empty() || tokens.len() < terms.len() {
3149 return 0;
3150 }
3151 tokens
3152 .windows(terms.len())
3153 .filter(|window| {
3154 window
3155 .iter()
3156 .zip(terms)
3157 .all(|(token, term)| token.term == *term)
3158 })
3159 .count()
3160}
3161
3162fn token_matches_positive_clause(token: &str, query: &FtsQuery) -> bool {
3163 query.positive_clauses().any(|clause| match clause {
3164 FtsClause::Term { term, prefix } => token_matches_term(token, term, *prefix),
3165 FtsClause::Phrase(terms) => terms.iter().any(|term| term == token),
3166 })
3167}
3168
3169fn fts_enrich_row(
3170 mut row: HashMap<u16, Value>,
3171 query: Option<&FtsQuery>,
3172 options: &FtsOptions,
3173 score: Option<f64>,
3174) -> HashMap<u16, Value> {
3175 row.remove(&3);
3176 row.remove(&4);
3177 row.remove(&5);
3178 row.remove(&6);
3179 let Some(score) = score else {
3180 return row;
3181 };
3182 let text = match row.get(&2) {
3183 Some(Value::Bytes(text)) => String::from_utf8_lossy(text).into_owned(),
3184 _ => String::new(),
3185 };
3186 row.insert(4, Value::Float64(score));
3187 if let Some(query) = query {
3188 row.insert(
3189 5,
3190 Value::Bytes(fts_snippet(&text, query, options).into_bytes()),
3191 );
3192 row.insert(
3193 6,
3194 Value::Bytes(fts_highlight(&text, query, options).into_bytes()),
3195 );
3196 }
3197 row
3198}
3199
3200#[derive(Debug, Clone)]
3201struct TokenSpan {
3202 term: String,
3203 start: usize,
3204 end: usize,
3205}
3206
3207fn token_spans_with_options(input: &str, options: &FtsOptions) -> Vec<TokenSpan> {
3208 let mut spans = Vec::new();
3209 let mut start = None;
3210 for (idx, ch) in input.char_indices() {
3211 if ch.is_ascii_alphanumeric() {
3212 start.get_or_insert(idx);
3213 } else if let Some(lo) = start.take() {
3214 push_token_span(input, &mut spans, lo, idx, options);
3215 }
3216 }
3217 if let Some(lo) = start {
3218 push_token_span(input, &mut spans, lo, input.len(), options);
3219 }
3220 spans
3221}
3222
3223fn push_token_span(
3224 input: &str,
3225 spans: &mut Vec<TokenSpan>,
3226 start: usize,
3227 end: usize,
3228 options: &FtsOptions,
3229) {
3230 if start < end {
3231 let term = options.normalize(&input[start..end]);
3232 if options.keep_term(&term) {
3233 spans.push(TokenSpan { term, start, end });
3234 }
3235 }
3236}
3237
3238fn fts_snippet(text: &str, query: &FtsQuery, options: &FtsOptions) -> String {
3239 let spans = token_spans_with_options(text, options);
3240 if spans.is_empty() {
3241 return String::new();
3242 }
3243 let first_match = spans
3244 .iter()
3245 .position(|span| token_matches_positive_clause(&span.term, query))
3246 .unwrap_or(0);
3247 let lo = first_match.saturating_sub(3);
3248 let hi = (first_match + 5).min(spans.len());
3249 let mut out = Vec::new();
3250 if lo > 0 {
3251 out.push("...".to_string());
3252 }
3253 for span in &spans[lo..hi] {
3254 let token = &text[span.start..span.end];
3255 if token_matches_positive_clause(&span.term, query) {
3256 out.push(format!("[{token}]"));
3257 } else {
3258 out.push(token.to_string());
3259 }
3260 }
3261 if hi < spans.len() {
3262 out.push("...".to_string());
3263 }
3264 out.join(" ")
3265}
3266
3267fn fts_highlight(text: &str, query: &FtsQuery, options: &FtsOptions) -> String {
3268 let spans = token_spans_with_options(text, options);
3269 if spans.is_empty() {
3270 return text.to_string();
3271 }
3272 let mut out = String::new();
3273 let mut cursor = 0;
3274 for span in spans {
3275 out.push_str(&text[cursor..span.start]);
3276 let token = &text[span.start..span.end];
3277 if token_matches_positive_clause(&span.term, query) {
3278 out.push_str("<b>");
3279 out.push_str(token);
3280 out.push_str("</b>");
3281 } else {
3282 out.push_str(token);
3283 }
3284 cursor = span.end;
3285 }
3286 out.push_str(&text[cursor..]);
3287 out
3288}
3289
3290struct RTreeRectsModule;
3291
3292impl ExternalTableModule for RTreeRectsModule {
3293 fn name(&self) -> &str {
3294 "rtree_rects"
3295 }
3296
3297 fn descriptor(&self) -> ExternalModuleDescriptor {
3298 ExternalModuleDescriptor {
3299 schema: rtree_rects_schema(),
3300 hidden_columns: vec![
3301 "query_min_x".to_string(),
3302 "query_max_x".to_string(),
3303 "query_min_y".to_string(),
3304 "query_max_y".to_string(),
3305 ],
3306 capabilities: ModuleCapabilities {
3307 writable: true,
3308 deterministic: true,
3309 trigger_safe: true,
3310 ..ModuleCapabilities::default()
3311 },
3312 }
3313 }
3314
3315 fn indexes_with_control(
3316 &self,
3317 context: &ExternalExecutionContext<'_>,
3318 entry: &ExternalTableEntry,
3319 ) -> Result<Vec<ExternalModuleIndex>> {
3320 context.control.checkpoint()?;
3321 Ok(vec![ExternalModuleIndex::new(
3322 format!("{}_rtree_spatial", entry.name),
3323 vec![2, 3, 4, 5],
3324 )])
3325 }
3326
3327 fn connect_with_control(
3328 &self,
3329 ctx: &ExternalExecutionContext<'_>,
3330 entry: &ExternalTableEntry,
3331 ) -> Result<Arc<dyn ExternalTable>> {
3332 ctx.control.checkpoint()?;
3333 ensure_no_args(entry, self.name())?;
3334 let rows = read_state_rows(ctx.database, entry)?;
3335 let index = RTreeSpatialIndex::build(&rows);
3336 Ok(Arc::new(RTreeRectsExternalTable {
3337 schema: arrow_conv::arrow_schema(&entry.declared_schema)?,
3338 core_schema: entry.declared_schema.clone(),
3339 index,
3340 rows,
3341 }))
3342 }
3343
3344 fn read_rows_with_control(
3345 &self,
3346 ctx: &ExternalExecutionContext<'_>,
3347 entry: &ExternalTableEntry,
3348 ) -> Result<Vec<HashMap<u16, Value>>> {
3349 ctx.control.checkpoint()?;
3350 ensure_no_args(entry, self.name())?;
3351 read_state_rows(ctx.database, entry)
3352 }
3353
3354 fn prepare_rows_with_control(
3355 &self,
3356 ctx: &ExternalExecutionContext<'_>,
3357 entry: &ExternalTableEntry,
3358 rows: Vec<HashMap<u16, Value>>,
3359 ) -> Result<Vec<u8>> {
3360 ctx.control.checkpoint()?;
3361 ensure_no_args(entry, self.name())?;
3362 let rows = rows
3363 .into_iter()
3364 .map(|mut row| {
3365 for column_id in 6..=9 {
3366 row.remove(&column_id);
3367 }
3368 row
3369 })
3370 .collect::<Vec<_>>();
3371 validate_external_rows(&entry.declared_schema, &rows)?;
3372 encode_state_rows(&rows)
3373 }
3374}
3375
3376#[derive(Clone)]
3377struct RTreeRectsExternalTable {
3378 schema: SchemaRef,
3379 core_schema: CoreSchema,
3380 index: RTreeSpatialIndex,
3381 rows: Vec<HashMap<u16, Value>>,
3382}
3383
3384impl std::fmt::Debug for RTreeRectsExternalTable {
3385 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3386 f.debug_struct("RTreeRectsExternalTable")
3387 .field("schema", &self.schema)
3388 .finish_non_exhaustive()
3389 }
3390}
3391
3392impl ExternalTable for RTreeRectsExternalTable {
3393 fn schema(&self) -> SchemaRef {
3394 self.schema.clone()
3395 }
3396
3397 fn plan_with_control(
3398 &self,
3399 request: &ExternalPlanRequest<'_>,
3400 control: &ExecutionControl,
3401 ) -> DFResult<ExternalPlan> {
3402 external_df_checkpoint(control)?;
3403 Ok(ExternalPlan::new(
3404 request
3405 .raw_filters
3406 .iter()
3407 .map(|filter| {
3408 if rtree_query_bound(filter).is_some()
3409 || rtree_query_rect_function(filter).is_some()
3410 {
3411 TableProviderFilterPushDown::Exact
3412 } else {
3413 TableProviderFilterPushDown::Unsupported
3414 }
3415 })
3416 .collect(),
3417 None,
3418 1.0,
3419 false,
3420 ))
3421 }
3422
3423 fn scan_with_control(
3424 &self,
3425 request: &ExternalPlanRequest<'_>,
3426 control: &mongreldb_core::ExecutionControl,
3427 ) -> DFResult<ExternalScan> {
3428 external_checkpoint(Some(control), 0)?;
3429 let mut hidden_bounds = QueryRect::default();
3430 let mut has_hidden_bounds = false;
3431 for (column, value) in request
3432 .raw_filters
3433 .iter()
3434 .filter_map(|filter| rtree_query_bound(filter))
3435 {
3436 hidden_bounds.set(column, value);
3437 has_hidden_bounds = true;
3438 }
3439 let mut query_rects = request
3440 .raw_filters
3441 .iter()
3442 .filter_map(|filter| rtree_query_rect_function(filter))
3443 .collect::<Vec<_>>();
3444 if has_hidden_bounds || query_rects.is_empty() {
3445 query_rects.push(hidden_bounds);
3446 }
3447 let mut candidate_rows: Option<BTreeSet<usize>> = None;
3448 for bounds in &query_rects {
3449 let mut next = BTreeSet::new();
3450 for (index, row) in self.index.candidates(*bounds).into_iter().enumerate() {
3451 external_checkpoint(Some(control), index)?;
3452 next.insert(row);
3453 }
3454 candidate_rows = Some(match candidate_rows.take() {
3455 Some(current) => {
3456 let mut intersection = BTreeSet::new();
3457 for (index, row) in current.into_iter().enumerate() {
3458 external_checkpoint(Some(control), index)?;
3459 if next.contains(&row) {
3460 intersection.insert(row);
3461 }
3462 }
3463 intersection
3464 }
3465 None => next,
3466 });
3467 }
3468 let candidate_rows =
3469 candidate_rows.unwrap_or_else(|| self.index.all_rows.iter().copied().collect());
3470 let mut rows = Vec::new();
3471 for (index, row) in self.rows.iter().enumerate() {
3472 external_checkpoint(Some(control), index)?;
3473 if candidate_rows.contains(&index)
3474 && query_rects
3475 .iter()
3476 .all(|bounds| rtree_row_matches(row, *bounds))
3477 {
3478 rows.push(row.clone());
3479 }
3480 }
3481 external_checkpoint(Some(control), 0)?;
3482 project_scan_with_control(
3483 self.schema.clone(),
3484 core_rows_to_batches(&self.core_schema, rows)
3485 .map_err(|e| DataFusionError::Execution(e.to_string()))?,
3486 request.projection.as_deref(),
3487 request.limit,
3488 Some(control),
3489 )
3490 }
3491}
3492
3493#[derive(Clone, Copy)]
3494struct QueryRect {
3495 min_x: f64,
3496 max_x: f64,
3497 min_y: f64,
3498 max_y: f64,
3499}
3500
3501impl Default for QueryRect {
3502 fn default() -> Self {
3503 Self {
3504 min_x: f64::NEG_INFINITY,
3505 max_x: f64::INFINITY,
3506 min_y: f64::NEG_INFINITY,
3507 max_y: f64::INFINITY,
3508 }
3509 }
3510}
3511
3512impl QueryRect {
3513 fn set(&mut self, column: &str, value: f64) {
3514 match column {
3515 "query_min_x" => self.min_x = value,
3516 "query_max_x" => self.max_x = value,
3517 "query_min_y" => self.min_y = value,
3518 "query_max_y" => self.max_y = value,
3519 _ => {}
3520 }
3521 }
3522}
3523
3524#[derive(Clone)]
3525struct RTreeSpatialIndex {
3526 all_rows: Vec<usize>,
3527 min_x: Vec<(f64, usize)>,
3528 max_x: Vec<(f64, usize)>,
3529 min_y: Vec<(f64, usize)>,
3530 max_y: Vec<(f64, usize)>,
3531}
3532
3533impl RTreeSpatialIndex {
3534 fn build(rows: &[HashMap<u16, Value>]) -> Self {
3535 let mut all_rows = Vec::new();
3536 let mut min_x = Vec::new();
3537 let mut max_x = Vec::new();
3538 let mut min_y = Vec::new();
3539 let mut max_y = Vec::new();
3540 for (idx, row) in rows.iter().enumerate() {
3541 let Some(x0) = row_f64(row, 2) else {
3542 continue;
3543 };
3544 let Some(x1) = row_f64(row, 3) else {
3545 continue;
3546 };
3547 let Some(y0) = row_f64(row, 4) else {
3548 continue;
3549 };
3550 let Some(y1) = row_f64(row, 5) else {
3551 continue;
3552 };
3553 if !(x0.is_finite() && x1.is_finite() && y0.is_finite() && y1.is_finite()) {
3554 continue;
3555 }
3556 all_rows.push(idx);
3557 min_x.push((x0, idx));
3558 max_x.push((x1, idx));
3559 min_y.push((y0, idx));
3560 max_y.push((y1, idx));
3561 }
3562 for values in [&mut min_x, &mut max_x, &mut min_y, &mut max_y] {
3563 values.sort_by(|left, right| left.0.total_cmp(&right.0));
3564 }
3565 Self {
3566 all_rows,
3567 min_x,
3568 max_x,
3569 min_y,
3570 max_y,
3571 }
3572 }
3573
3574 fn candidates(&self, query: QueryRect) -> Vec<usize> {
3575 let mut rows = self.lte(&self.min_x, query.max_x);
3576 for next in [
3577 self.gte(&self.max_x, query.min_x),
3578 self.lte(&self.min_y, query.max_y),
3579 self.gte(&self.max_y, query.min_y),
3580 ] {
3581 rows = rows.intersection(&next).copied().collect();
3582 if rows.is_empty() {
3583 break;
3584 }
3585 }
3586 rows.into_iter().collect()
3587 }
3588
3589 fn lte(&self, values: &[(f64, usize)], bound: f64) -> BTreeSet<usize> {
3590 if bound == f64::INFINITY {
3591 return self.all_rows.iter().copied().collect();
3592 }
3593 if bound.is_nan() {
3594 return BTreeSet::new();
3595 }
3596 let end = values.partition_point(|(value, _)| *value <= bound);
3597 values[..end].iter().map(|(_, idx)| *idx).collect()
3598 }
3599
3600 fn gte(&self, values: &[(f64, usize)], bound: f64) -> BTreeSet<usize> {
3601 if bound == f64::NEG_INFINITY {
3602 return self.all_rows.iter().copied().collect();
3603 }
3604 if bound.is_nan() {
3605 return BTreeSet::new();
3606 }
3607 let start = values.partition_point(|(value, _)| *value < bound);
3608 values[start..].iter().map(|(_, idx)| *idx).collect()
3609 }
3610}
3611
3612fn rtree_query_rect_function(expr: &Expr) -> Option<QueryRect> {
3613 let Expr::ScalarFunction(sf) = expr else {
3614 return None;
3615 };
3616 if !sf.func.name().eq_ignore_ascii_case("rtree_intersects") || sf.args.len() != 8 {
3617 return None;
3618 }
3619 for (arg, expected) in sf
3620 .args
3621 .iter()
3622 .take(4)
3623 .zip(["min_x", "max_x", "min_y", "max_y"])
3624 {
3625 let Expr::Column(column) = arg else {
3626 return None;
3627 };
3628 if column.name != expected {
3629 return None;
3630 }
3631 }
3632 Some(QueryRect {
3633 min_x: literal_f64_from_expr(&sf.args[4])?,
3634 max_x: literal_f64_from_expr(&sf.args[5])?,
3635 min_y: literal_f64_from_expr(&sf.args[6])?,
3636 max_y: literal_f64_from_expr(&sf.args[7])?,
3637 })
3638}
3639
3640fn literal_f64_from_expr(expr: &Expr) -> Option<f64> {
3641 match expr {
3642 Expr::Literal(literal, _) => literal_f64(literal),
3643 _ => None,
3644 }
3645}
3646
3647fn rtree_rects_schema() -> CoreSchema {
3648 CoreSchema {
3649 schema_id: 0,
3650 columns: vec![
3651 CoreColumnDef {
3652 id: 1,
3653 name: "id".to_string(),
3654 ty: TypeId::Int64,
3655 flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
3656 default_value: None,
3657 embedding_source: None,
3658 },
3659 rect_column(2, "min_x"),
3660 rect_column(3, "max_x"),
3661 rect_column(4, "min_y"),
3662 rect_column(5, "max_y"),
3663 rect_column(6, "query_min_x"),
3664 rect_column(7, "query_max_x"),
3665 rect_column(8, "query_min_y"),
3666 rect_column(9, "query_max_y"),
3667 ],
3668 indexes: Vec::new(),
3669 colocation: Vec::new(),
3670 constraints: Default::default(),
3671 clustered: false,
3672 }
3673}
3674
3675fn rect_column(id: u16, name: &str) -> CoreColumnDef {
3676 CoreColumnDef {
3677 id,
3678 name: name.to_string(),
3679 ty: TypeId::Float64,
3680 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3681 default_value: None,
3682 embedding_source: None,
3683 }
3684}
3685
3686fn rtree_query_bound(expr: &Expr) -> Option<(&str, f64)> {
3687 match expr {
3688 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::Eq => {
3689 match (left.as_ref(), right.as_ref()) {
3690 (Expr::Column(column), Expr::Literal(literal, _)) => {
3691 hidden_rect_column(&column.name).zip(literal_f64(literal))
3692 }
3693 (Expr::Literal(literal, _), Expr::Column(column)) => {
3694 hidden_rect_column(&column.name).zip(literal_f64(literal))
3695 }
3696 _ => None,
3697 }
3698 }
3699 _ => None,
3700 }
3701}
3702
3703fn hidden_rect_column(name: &str) -> Option<&str> {
3704 match name {
3705 "query_min_x" | "query_max_x" | "query_min_y" | "query_max_y" => Some(name),
3706 _ => None,
3707 }
3708}
3709
3710fn rtree_row_matches(row: &HashMap<u16, Value>, query: QueryRect) -> bool {
3711 let Some(min_x) = row_f64(row, 2) else {
3712 return false;
3713 };
3714 let Some(max_x) = row_f64(row, 3) else {
3715 return false;
3716 };
3717 let Some(min_y) = row_f64(row, 4) else {
3718 return false;
3719 };
3720 let Some(max_y) = row_f64(row, 5) else {
3721 return false;
3722 };
3723 max_x >= query.min_x && min_x <= query.max_x && max_y >= query.min_y && min_y <= query.max_y
3724}
3725
3726fn row_f64(row: &HashMap<u16, Value>, column_id: u16) -> Option<f64> {
3727 match row.get(&column_id) {
3728 Some(Value::Float64(value)) => Some(*value),
3729 Some(Value::Int64(value)) => Some(*value as f64),
3730 _ => None,
3731 }
3732}
3733
3734struct SeriesModule;
3735
3736impl ExternalTableModule for SeriesModule {
3737 fn name(&self) -> &str {
3738 "series"
3739 }
3740
3741 fn descriptor(&self) -> ExternalModuleDescriptor {
3742 ExternalModuleDescriptor {
3743 schema: CoreSchema {
3744 schema_id: 0,
3745 columns: vec![
3746 CoreColumnDef {
3747 id: 1,
3748 name: "value".to_string(),
3749 ty: TypeId::Int64,
3750 flags: ColumnFlags::empty(),
3751 default_value: None,
3752 embedding_source: None,
3753 },
3754 CoreColumnDef {
3755 id: 2,
3756 name: "start".to_string(),
3757 ty: TypeId::Int64,
3758 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3759 default_value: None,
3760 embedding_source: None,
3761 },
3762 CoreColumnDef {
3763 id: 3,
3764 name: "stop".to_string(),
3765 ty: TypeId::Int64,
3766 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3767 default_value: None,
3768 embedding_source: None,
3769 },
3770 CoreColumnDef {
3771 id: 4,
3772 name: "step".to_string(),
3773 ty: TypeId::Int64,
3774 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3775 default_value: None,
3776 embedding_source: None,
3777 },
3778 ],
3779 indexes: Vec::new(),
3780 colocation: Vec::new(),
3781 constraints: Default::default(),
3782 clustered: false,
3783 },
3784 hidden_columns: vec!["start".to_string(), "stop".to_string(), "step".to_string()],
3785 capabilities: ModuleCapabilities {
3786 read_only: true,
3787 deterministic: true,
3788 trigger_safe: true,
3789 ..ModuleCapabilities::default()
3790 },
3791 }
3792 }
3793
3794 fn connect_with_control(
3795 &self,
3796 ctx: &ExternalExecutionContext<'_>,
3797 entry: &ExternalTableEntry,
3798 ) -> Result<Arc<dyn ExternalTable>> {
3799 ctx.control.checkpoint()?;
3800 let (start, stop, step) = series_args(entry)?;
3801 let table = SeriesExternalTable::new(start, stop, step)?;
3802 Ok(Arc::new(table))
3803 }
3804}
3805
3806struct SeriesExternalTable {
3807 start: i64,
3808 stop: i64,
3809 step: i64,
3810 schema: SchemaRef,
3811}
3812
3813impl SeriesExternalTable {
3814 fn new(start: i64, stop: i64, step: i64) -> Result<Self> {
3815 if step == 0 {
3816 return Err(MongrelQueryError::Schema(
3817 "series step must not be 0".into(),
3818 ));
3819 }
3820 Ok(Self {
3821 start,
3822 stop,
3823 step,
3824 schema: Arc::new(ArrowSchema::new(vec![Field::new(
3825 "value",
3826 DataType::Int64,
3827 false,
3828 )])),
3829 })
3830 }
3831
3832 fn batches(
3833 &self,
3834 request: &ExternalPlanRequest<'_>,
3835 control: &mongreldb_core::ExecutionControl,
3836 ) -> DFResult<Vec<RecordBatch>> {
3837 let mut values = Vec::new();
3838 let mut current = self.start;
3839 let mut scanned = 0;
3840 while if self.step > 0 {
3841 current <= self.stop
3842 } else {
3843 current >= self.stop
3844 } {
3845 external_checkpoint(Some(control), scanned)?;
3846 scanned += 1;
3847 if values.len() >= 1_000_000 {
3848 return Err(DataFusionError::Plan(
3849 "series output is capped at 1,000,000 rows".into(),
3850 ));
3851 }
3852 if request
3853 .filters
3854 .iter()
3855 .all(|filter| series_filter_matches(filter, current).unwrap_or(true))
3856 {
3857 values.push(current);
3858 if request.limit.is_some_and(|limit| values.len() >= limit) {
3859 break;
3860 }
3861 }
3862 current = current.saturating_add(self.step);
3863 if (self.step > 0 && current == i64::MAX) || (self.step < 0 && current == i64::MIN) {
3864 break;
3865 }
3866 }
3867 let batch = RecordBatch::try_new(
3868 self.schema.clone(),
3869 vec![Arc::new(Int64Array::from(values)) as ArrayRef],
3870 )?;
3871 Ok(vec![batch])
3872 }
3873}
3874
3875impl std::fmt::Debug for SeriesExternalTable {
3876 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3877 f.debug_struct("SeriesExternalTable")
3878 .field("start", &self.start)
3879 .field("stop", &self.stop)
3880 .field("step", &self.step)
3881 .finish_non_exhaustive()
3882 }
3883}
3884
3885impl ExternalTable for SeriesExternalTable {
3886 fn schema(&self) -> SchemaRef {
3887 self.schema.clone()
3888 }
3889
3890 fn plan_with_control(
3891 &self,
3892 request: &ExternalPlanRequest<'_>,
3893 control: &ExecutionControl,
3894 ) -> DFResult<ExternalPlan> {
3895 external_df_checkpoint(control)?;
3896 Ok(ExternalPlan::new(
3897 request
3898 .filters
3899 .iter()
3900 .map(|filter| {
3901 if series_filter_supported(filter) {
3902 TableProviderFilterPushDown::Exact
3903 } else {
3904 TableProviderFilterPushDown::Unsupported
3905 }
3906 })
3907 .collect(),
3908 None,
3909 1.0,
3910 self.step > 0,
3911 ))
3912 }
3913
3914 fn scan_with_control(
3915 &self,
3916 request: &ExternalPlanRequest<'_>,
3917 control: &mongreldb_core::ExecutionControl,
3918 ) -> DFResult<ExternalScan> {
3919 external_checkpoint(Some(control), 0)?;
3920 let batches = self.batches(request, control)?;
3921 external_checkpoint(Some(control), 0)?;
3922 project_scan_with_control(
3923 self.schema.clone(),
3924 batches,
3925 request.projection.as_deref(),
3926 request.limit,
3927 Some(control),
3928 )
3929 }
3930}
3931
3932fn series_filter_supported(filter: &ExternalFilter) -> bool {
3933 match filter {
3934 ExternalFilter::And(filters) => filters.iter().all(series_filter_supported),
3935 ExternalFilter::Compare {
3936 column_index,
3937 value,
3938 ..
3939 } => *column_index == 0 && literal_i64(value).is_some(),
3940 ExternalFilter::Unsupported { .. } => false,
3941 }
3942}
3943
3944fn series_filter_matches(filter: &ExternalFilter, value: i64) -> Option<bool> {
3945 match filter {
3946 ExternalFilter::And(filters) => filters.iter().try_fold(true, |matches, filter| {
3947 Some(matches && series_filter_matches(filter, value)?)
3948 }),
3949 ExternalFilter::Compare {
3950 column_index,
3951 op,
3952 value: literal,
3953 } if *column_index == 0 => {
3954 let literal = literal_i64(literal)?;
3955 Some(match op {
3956 ExternalFilterOp::Eq => value == literal,
3957 ExternalFilterOp::NotEq => value != literal,
3958 ExternalFilterOp::Gt => value > literal,
3959 ExternalFilterOp::GtEq => value >= literal,
3960 ExternalFilterOp::Lt => value < literal,
3961 ExternalFilterOp::LtEq => value <= literal,
3962 })
3963 }
3964 _ => None,
3965 }
3966}
3967
3968fn literal_i64(value: &ScalarValue) -> Option<i64> {
3969 match value {
3970 ScalarValue::Int8(Some(v)) => Some(*v as i64),
3971 ScalarValue::Int16(Some(v)) => Some(*v as i64),
3972 ScalarValue::Int32(Some(v)) => Some(*v as i64),
3973 ScalarValue::Int64(Some(v)) => Some(*v),
3974 ScalarValue::UInt8(Some(v)) => Some(*v as i64),
3975 ScalarValue::UInt16(Some(v)) => Some(*v as i64),
3976 ScalarValue::UInt32(Some(v)) => Some(*v as i64),
3977 ScalarValue::UInt64(Some(v)) => i64::try_from(*v).ok(),
3978 _ => None,
3979 }
3980}
3981
3982fn literal_f64(value: &ScalarValue) -> Option<f64> {
3983 match value {
3984 ScalarValue::Float32(Some(v)) => Some(*v as f64),
3985 ScalarValue::Float64(Some(v)) => Some(*v),
3986 _ => literal_i64(value).map(|value| value as f64),
3987 }
3988}
3989
3990fn literal_string(value: &ScalarValue) -> Option<String> {
3991 match value {
3992 ScalarValue::Utf8(Some(v))
3993 | ScalarValue::Utf8View(Some(v))
3994 | ScalarValue::LargeUtf8(Some(v)) => Some(v.clone()),
3995 ScalarValue::Binary(Some(v))
3996 | ScalarValue::BinaryView(Some(v))
3997 | ScalarValue::LargeBinary(Some(v)) => String::from_utf8(v.clone()).ok(),
3998 _ => None,
3999 }
4000}
4001
4002fn series_args(entry: &ExternalTableEntry) -> Result<(i64, i64, i64)> {
4003 let values = entry
4004 .args
4005 .iter()
4006 .map(|arg| match arg {
4007 ModuleArg::Ident(value) | ModuleArg::String(value) | ModuleArg::Number(value) => {
4008 value.parse::<i64>().map_err(|e| {
4009 MongrelQueryError::Schema(format!(
4010 "series module argument {value:?} must be an integer: {e}"
4011 ))
4012 })
4013 }
4014 })
4015 .collect::<Result<Vec<_>>>()?;
4016 match values.as_slice() {
4017 [] => Ok((0, -1, 1)),
4018 [stop] => Ok((0, *stop, 1)),
4019 [start, stop] => Ok((*start, *stop, 1)),
4020 [start, stop, step] => Ok((*start, *stop, *step)),
4021 _ => Err(MongrelQueryError::Schema(
4022 "series external table accepts at most three arguments".into(),
4023 )),
4024 }
4025}