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 }
1253}
1254
1255fn json_args(entry: &ExternalTableEntry, module: &str) -> Result<(String, Option<String>)> {
1256 match entry.args.as_slice() {
1257 [json] => Ok((module_arg_string(json).to_string(), None)),
1258 [json, root] => Ok((
1259 module_arg_string(json).to_string(),
1260 Some(module_arg_string(root).to_string()),
1261 )),
1262 _ => Err(MongrelQueryError::Schema(format!(
1263 "{module} external table requires one JSON argument and an optional root path"
1264 ))),
1265 }
1266}
1267
1268fn module_arg_string(arg: &ModuleArg) -> &str {
1269 match arg {
1270 ModuleArg::Ident(value) | ModuleArg::String(value) | ModuleArg::Number(value) => value,
1271 }
1272}
1273
1274struct SchemaTablesModule;
1275
1276impl ExternalTableModule for SchemaTablesModule {
1277 fn name(&self) -> &str {
1278 "schema_tables"
1279 }
1280
1281 fn descriptor(&self) -> ExternalModuleDescriptor {
1282 ExternalModuleDescriptor {
1283 schema: CoreSchema {
1284 schema_id: 0,
1285 columns: vec![
1286 catalog_column(1, "schema_name", TypeId::Bytes, false),
1287 catalog_column(2, "name", TypeId::Bytes, false),
1288 catalog_column(3, "type", TypeId::Bytes, false),
1289 catalog_column(4, "ncol", TypeId::Int64, false),
1290 catalog_column(5, "module", TypeId::Bytes, true),
1291 catalog_column(6, "created_epoch", TypeId::Int64, false),
1292 ],
1293 indexes: Vec::new(),
1294 colocation: Vec::new(),
1295 constraints: Default::default(),
1296 clustered: false,
1297 },
1298 hidden_columns: Vec::new(),
1299 capabilities: catalog_capabilities(),
1300 }
1301 }
1302
1303 fn connect_with_control(
1304 &self,
1305 ctx: &ExternalExecutionContext<'_>,
1306 entry: &ExternalTableEntry,
1307 ) -> Result<Arc<dyn ExternalTable>> {
1308 ctx.control.checkpoint()?;
1309 ensure_no_args(entry, self.name())?;
1310 Ok(Arc::new(SchemaTablesExternalTable {
1311 db: Arc::clone(ctx.database),
1312 schema: schema_tables_arrow_schema(),
1313 }))
1314 }
1315}
1316
1317#[derive(Clone)]
1318struct SchemaTablesExternalTable {
1319 db: Arc<Database>,
1320 schema: SchemaRef,
1321}
1322
1323impl std::fmt::Debug for SchemaTablesExternalTable {
1324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1325 f.debug_struct("SchemaTablesExternalTable")
1326 .field("schema", &self.schema)
1327 .finish_non_exhaustive()
1328 }
1329}
1330
1331impl ExternalTable for SchemaTablesExternalTable {
1332 fn schema(&self) -> SchemaRef {
1333 self.schema.clone()
1334 }
1335
1336 fn plan_with_control(
1337 &self,
1338 request: &ExternalPlanRequest<'_>,
1339 control: &ExecutionControl,
1340 ) -> DFResult<ExternalPlan> {
1341 external_df_checkpoint(control)?;
1342 unsupported_plan(request)
1343 }
1344
1345 fn scan_with_control(
1346 &self,
1347 request: &ExternalPlanRequest<'_>,
1348 control: &mongreldb_core::ExecutionControl,
1349 ) -> DFResult<ExternalScan> {
1350 external_checkpoint(Some(control), 0)?;
1351 let batches = schema_tables_batches(&self.db, self.schema.clone(), Some(control))?;
1352 project_scan_with_control(
1353 self.schema.clone(),
1354 batches,
1355 request.projection.as_deref(),
1356 request.limit,
1357 Some(control),
1358 )
1359 }
1360}
1361
1362struct DbStatModule;
1363
1364impl ExternalTableModule for DbStatModule {
1365 fn name(&self) -> &str {
1366 "dbstat"
1367 }
1368
1369 fn descriptor(&self) -> ExternalModuleDescriptor {
1370 ExternalModuleDescriptor {
1371 schema: CoreSchema {
1372 schema_id: 0,
1373 columns: vec![
1374 catalog_column(1, "name", TypeId::Bytes, false),
1375 catalog_column(2, "type", TypeId::Bytes, false),
1376 catalog_column(3, "rows", TypeId::Int64, true),
1377 catalog_column(4, "runs", TypeId::Int64, true),
1378 catalog_column(5, "memtable_rows", TypeId::Int64, true),
1379 catalog_column(6, "columns", TypeId::Int64, false),
1380 catalog_column(7, "storage_bytes", TypeId::Int64, false),
1381 ],
1382 indexes: Vec::new(),
1383 colocation: Vec::new(),
1384 constraints: Default::default(),
1385 clustered: false,
1386 },
1387 hidden_columns: Vec::new(),
1388 capabilities: catalog_capabilities(),
1389 }
1390 }
1391
1392 fn connect_with_control(
1393 &self,
1394 ctx: &ExternalExecutionContext<'_>,
1395 entry: &ExternalTableEntry,
1396 ) -> Result<Arc<dyn ExternalTable>> {
1397 ctx.control.checkpoint()?;
1398 ensure_no_args(entry, self.name())?;
1399 Ok(Arc::new(DbStatExternalTable {
1400 db: Arc::clone(ctx.database),
1401 schema: dbstat_arrow_schema(),
1402 }))
1403 }
1404}
1405
1406#[derive(Clone)]
1407struct DbStatExternalTable {
1408 db: Arc<Database>,
1409 schema: SchemaRef,
1410}
1411
1412impl std::fmt::Debug for DbStatExternalTable {
1413 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1414 f.debug_struct("DbStatExternalTable")
1415 .field("schema", &self.schema)
1416 .finish_non_exhaustive()
1417 }
1418}
1419
1420impl ExternalTable for DbStatExternalTable {
1421 fn schema(&self) -> SchemaRef {
1422 self.schema.clone()
1423 }
1424
1425 fn plan_with_control(
1426 &self,
1427 request: &ExternalPlanRequest<'_>,
1428 control: &ExecutionControl,
1429 ) -> DFResult<ExternalPlan> {
1430 external_df_checkpoint(control)?;
1431 unsupported_plan(request)
1432 }
1433
1434 fn scan_with_control(
1435 &self,
1436 request: &ExternalPlanRequest<'_>,
1437 control: &mongreldb_core::ExecutionControl,
1438 ) -> DFResult<ExternalScan> {
1439 external_checkpoint(Some(control), 0)?;
1440 let batches = dbstat_batches(&self.db, self.schema.clone(), Some(control))?;
1441 project_scan_with_control(
1442 self.schema.clone(),
1443 batches,
1444 request.projection.as_deref(),
1445 request.limit,
1446 Some(control),
1447 )
1448 }
1449}
1450
1451fn catalog_column(id: u16, name: &str, ty: TypeId, nullable: bool) -> CoreColumnDef {
1452 CoreColumnDef {
1453 id,
1454 name: name.to_string(),
1455 ty,
1456 flags: if nullable {
1457 ColumnFlags::empty().with(ColumnFlags::NULLABLE)
1458 } else {
1459 ColumnFlags::empty()
1460 },
1461 default_value: None,
1462 }
1463}
1464
1465fn catalog_capabilities() -> ModuleCapabilities {
1466 ModuleCapabilities {
1467 read_only: true,
1468 trigger_safe: true,
1469 ..ModuleCapabilities::default()
1470 }
1471}
1472
1473fn ensure_no_args(entry: &ExternalTableEntry, module: &str) -> Result<()> {
1474 if entry.args.is_empty() {
1475 Ok(())
1476 } else {
1477 Err(MongrelQueryError::Schema(format!(
1478 "{module} external table does not accept arguments"
1479 )))
1480 }
1481}
1482
1483fn schema_tables_arrow_schema() -> SchemaRef {
1484 Arc::new(ArrowSchema::new(vec![
1485 Field::new("schema_name", DataType::Utf8, false),
1486 Field::new("name", DataType::Utf8, false),
1487 Field::new("type", DataType::Utf8, false),
1488 Field::new("ncol", DataType::Int64, false),
1489 Field::new("module", DataType::Utf8, true),
1490 Field::new("created_epoch", DataType::Int64, false),
1491 ]))
1492}
1493
1494fn schema_tables_batches(
1495 db: &Arc<Database>,
1496 schema: SchemaRef,
1497 control: Option<&mongreldb_core::ExecutionControl>,
1498) -> DFResult<Vec<RecordBatch>> {
1499 struct Row {
1500 name: String,
1501 ty: String,
1502 ncol: i64,
1503 module: Option<String>,
1504 created_epoch: i64,
1505 }
1506
1507 let catalog = db.catalog_snapshot();
1508 let mut rows = Vec::new();
1509 for (index, table) in catalog
1510 .tables
1511 .into_iter()
1512 .filter(|table| matches!(table.state, TableState::Live))
1513 .enumerate()
1514 {
1515 external_checkpoint(control, index)?;
1516 rows.push(Row {
1517 name: table.name,
1518 ty: "table".to_string(),
1519 ncol: saturating_i64(table.schema.columns.len() as u64),
1520 module: None,
1521 created_epoch: saturating_i64(table.created_epoch),
1522 });
1523 }
1524 for (index, table) in catalog.external_tables.into_iter().enumerate() {
1525 external_checkpoint(control, index)?;
1526 rows.push(Row {
1527 name: table.name,
1528 ty: "external".to_string(),
1529 ncol: saturating_i64(table.declared_schema.columns.len() as u64),
1530 module: Some(table.module),
1531 created_epoch: saturating_i64(table.created_epoch),
1532 });
1533 }
1534 rows.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.ty.cmp(&b.ty)));
1535
1536 let schema_names = vec!["main".to_string(); rows.len()];
1537 let names = rows.iter().map(|row| row.name.clone()).collect::<Vec<_>>();
1538 let types = rows.iter().map(|row| row.ty.clone()).collect::<Vec<_>>();
1539 let ncols = rows.iter().map(|row| row.ncol).collect::<Vec<_>>();
1540 let modules = rows
1541 .iter()
1542 .map(|row| row.module.clone())
1543 .collect::<Vec<_>>();
1544 let created_epochs = rows.iter().map(|row| row.created_epoch).collect::<Vec<_>>();
1545
1546 Ok(vec![RecordBatch::try_new(
1547 schema,
1548 vec![
1549 Arc::new(StringArray::from(schema_names)) as ArrayRef,
1550 Arc::new(StringArray::from(names)),
1551 Arc::new(StringArray::from(types)),
1552 Arc::new(Int64Array::from(ncols)),
1553 Arc::new(StringArray::from(modules)),
1554 Arc::new(Int64Array::from(created_epochs)),
1555 ],
1556 )?])
1557}
1558
1559fn dbstat_arrow_schema() -> SchemaRef {
1560 Arc::new(ArrowSchema::new(vec![
1561 Field::new("name", DataType::Utf8, false),
1562 Field::new("type", DataType::Utf8, false),
1563 Field::new("rows", DataType::Int64, true),
1564 Field::new("runs", DataType::Int64, true),
1565 Field::new("memtable_rows", DataType::Int64, true),
1566 Field::new("columns", DataType::Int64, false),
1567 Field::new("storage_bytes", DataType::Int64, false),
1568 ]))
1569}
1570
1571fn dbstat_batches(
1572 db: &Arc<Database>,
1573 schema: SchemaRef,
1574 control: Option<&mongreldb_core::ExecutionControl>,
1575) -> DFResult<Vec<RecordBatch>> {
1576 struct Row {
1577 name: String,
1578 ty: String,
1579 rows: Option<i64>,
1580 runs: Option<i64>,
1581 memtable_rows: Option<i64>,
1582 columns: i64,
1583 storage_bytes: i64,
1584 }
1585
1586 let catalog = db.catalog_snapshot();
1587 let mut rows = Vec::new();
1588 for (index, table) in catalog
1589 .tables
1590 .into_iter()
1591 .filter(|table| matches!(table.state, TableState::Live))
1592 .enumerate()
1593 {
1594 external_checkpoint(control, index)?;
1595 let handle = db
1596 .table(&table.name)
1597 .map_err(|e| DataFusionError::Execution(e.to_string()))?;
1598 let table_guard = handle.lock();
1599 let table_dir = db.root().join(TABLES_DIR).join(table.table_id.to_string());
1600 rows.push(Row {
1601 name: table.name,
1602 ty: "table".to_string(),
1603 rows: Some(saturating_i64(table_guard.count())),
1604 runs: Some(saturating_i64(table_guard.run_count() as u64)),
1605 memtable_rows: Some(saturating_i64(table_guard.memtable_len() as u64)),
1606 columns: saturating_i64(table.schema.columns.len() as u64),
1607 storage_bytes: saturating_i64(dir_size(&table_dir, control)?),
1608 });
1609 }
1610 for (index, table) in catalog.external_tables.into_iter().enumerate() {
1611 external_checkpoint(control, index)?;
1612 let state_dir = db.root().join(VTAB_DIR).join(&table.name);
1613 rows.push(Row {
1614 name: table.name,
1615 ty: "external".to_string(),
1616 rows: None,
1617 runs: None,
1618 memtable_rows: None,
1619 columns: saturating_i64(table.declared_schema.columns.len() as u64),
1620 storage_bytes: saturating_i64(dir_size(&state_dir, control)?),
1621 });
1622 }
1623 rows.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.ty.cmp(&b.ty)));
1624
1625 let names = rows.iter().map(|row| row.name.clone()).collect::<Vec<_>>();
1626 let types = rows.iter().map(|row| row.ty.clone()).collect::<Vec<_>>();
1627 let row_counts = rows.iter().map(|row| row.rows).collect::<Vec<_>>();
1628 let runs = rows.iter().map(|row| row.runs).collect::<Vec<_>>();
1629 let memtable_rows = rows.iter().map(|row| row.memtable_rows).collect::<Vec<_>>();
1630 let columns = rows.iter().map(|row| row.columns).collect::<Vec<_>>();
1631 let storage_bytes = rows.iter().map(|row| row.storage_bytes).collect::<Vec<_>>();
1632
1633 Ok(vec![RecordBatch::try_new(
1634 schema,
1635 vec![
1636 Arc::new(StringArray::from(names)) as ArrayRef,
1637 Arc::new(StringArray::from(types)),
1638 Arc::new(Int64Array::from(row_counts)),
1639 Arc::new(Int64Array::from(runs)),
1640 Arc::new(Int64Array::from(memtable_rows)),
1641 Arc::new(Int64Array::from(columns)),
1642 Arc::new(Int64Array::from(storage_bytes)),
1643 ],
1644 )?])
1645}
1646
1647fn dir_size(path: &Path, control: Option<&mongreldb_core::ExecutionControl>) -> DFResult<u64> {
1648 external_checkpoint(control, 0)?;
1649 if !path.exists() {
1650 return Ok(0);
1651 }
1652 let metadata = std::fs::symlink_metadata(path)
1653 .map_err(|e| DataFusionError::Execution(format!("stat {:?}: {e}", path)))?;
1654 if metadata.is_file() {
1655 return Ok(metadata.len());
1656 }
1657 if !metadata.is_dir() {
1658 return Ok(0);
1659 }
1660
1661 let mut total = 0_u64;
1662 for (index, entry) in std::fs::read_dir(path)
1663 .map_err(|e| DataFusionError::Execution(format!("{e}")))?
1664 .enumerate()
1665 {
1666 external_checkpoint(control, index)?;
1667 let entry = entry.map_err(|e| DataFusionError::Execution(format!("{e}")))?;
1668 total = total.saturating_add(dir_size(&entry.path(), control)?);
1669 }
1670 Ok(total)
1671}
1672
1673fn saturating_i64(value: u64) -> i64 {
1674 i64::try_from(value).unwrap_or(i64::MAX)
1675}
1676
1677fn unsupported_plan(request: &ExternalPlanRequest<'_>) -> DFResult<ExternalPlan> {
1678 Ok(ExternalPlan::new(
1679 request
1680 .filters
1681 .iter()
1682 .map(|_| TableProviderFilterPushDown::Unsupported)
1683 .collect(),
1684 None,
1685 1.0,
1686 false,
1687 ))
1688}
1689
1690#[cfg(test)]
1691fn project_scan(
1692 full_schema: SchemaRef,
1693 batches: Vec<RecordBatch>,
1694 projection: Option<&[usize]>,
1695 limit: Option<usize>,
1696) -> DFResult<ExternalScan> {
1697 project_scan_with_control(full_schema, batches, projection, limit, None)
1698}
1699
1700fn project_scan_with_control(
1701 full_schema: SchemaRef,
1702 batches: Vec<RecordBatch>,
1703 projection: Option<&[usize]>,
1704 limit: Option<usize>,
1705 control: Option<&mongreldb_core::ExecutionControl>,
1706) -> DFResult<ExternalScan> {
1707 external_checkpoint(control, 0)?;
1708 let Some(projection) = projection else {
1709 return Ok(ExternalScan {
1710 schema: full_schema,
1711 batches: limit_batches(batches, limit, control)?,
1712 });
1713 };
1714 let schema = Arc::new(ArrowSchema::new(
1715 projection
1716 .iter()
1717 .map(|idx| full_schema.field(*idx).clone())
1718 .collect::<Vec<_>>(),
1719 ));
1720 let projected = batches
1721 .into_iter()
1722 .enumerate()
1723 .map(|(index, batch)| {
1724 external_checkpoint(control, index)?;
1725 let columns = projection
1726 .iter()
1727 .map(|idx| batch.column(*idx).clone())
1728 .collect::<Vec<_>>();
1729 let batch = if columns.is_empty() {
1730 RecordBatch::try_new_with_options(
1731 schema.clone(),
1732 columns,
1733 &RecordBatchOptions::new().with_row_count(Some(batch.num_rows())),
1734 )
1735 } else {
1736 RecordBatch::try_new(schema.clone(), columns)
1737 };
1738 batch.map_err(DataFusionError::from)
1739 })
1740 .collect::<DFResult<Vec<_>>>()?;
1741 Ok(ExternalScan {
1742 schema,
1743 batches: limit_batches(projected, limit, control)?,
1744 })
1745}
1746
1747fn limit_batches(
1748 batches: Vec<RecordBatch>,
1749 limit: Option<usize>,
1750 control: Option<&mongreldb_core::ExecutionControl>,
1751) -> DFResult<Vec<RecordBatch>> {
1752 let Some(mut remaining) = limit else {
1753 return Ok(batches);
1754 };
1755 let mut limited = Vec::new();
1756 for (index, batch) in batches.into_iter().enumerate() {
1757 external_checkpoint(control, index)?;
1758 if remaining == 0 {
1759 break;
1760 }
1761 let take = remaining.min(batch.num_rows());
1762 limited.push(batch.slice(0, take));
1763 remaining -= take;
1764 }
1765 Ok(limited)
1766}
1767
1768#[inline]
1769fn external_checkpoint(
1770 control: Option<&mongreldb_core::ExecutionControl>,
1771 index: usize,
1772) -> DFResult<()> {
1773 if index.is_multiple_of(256) {
1774 control
1775 .map(mongreldb_core::ExecutionControl::checkpoint)
1776 .transpose()
1777 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
1778 }
1779 Ok(())
1780}
1781
1782#[cfg(test)]
1783mod tests {
1784 use super::*;
1785
1786 #[derive(Debug)]
1787 struct BlockingExternalModule {
1788 entered: Arc<std::sync::Barrier>,
1789 }
1790
1791 impl BlockingExternalModule {
1792 fn block(&self, control: &ExecutionControl) -> Result<()> {
1793 self.entered.wait();
1794 loop {
1795 control.checkpoint()?;
1796 std::thread::yield_now();
1797 }
1798 }
1799 }
1800
1801 impl ExternalTableModule for BlockingExternalModule {
1802 fn name(&self) -> &str {
1803 "blocking"
1804 }
1805
1806 fn descriptor(&self) -> ExternalModuleDescriptor {
1807 ExternalModuleDescriptor {
1808 schema: CoreSchema::default(),
1809 hidden_columns: Vec::new(),
1810 capabilities: ModuleCapabilities::default(),
1811 }
1812 }
1813
1814 fn indexes_with_control(
1815 &self,
1816 context: &ExternalExecutionContext<'_>,
1817 _entry: &ExternalTableEntry,
1818 ) -> Result<Vec<ExternalModuleIndex>> {
1819 self.block(context.control)?;
1820 unreachable!()
1821 }
1822
1823 fn connect_with_control(
1824 &self,
1825 context: &ExternalExecutionContext<'_>,
1826 _entry: &ExternalTableEntry,
1827 ) -> Result<Arc<dyn ExternalTable>> {
1828 self.block(context.control)?;
1829 unreachable!()
1830 }
1831
1832 fn read_rows_with_control(
1833 &self,
1834 context: &ExternalExecutionContext<'_>,
1835 _entry: &ExternalTableEntry,
1836 ) -> Result<Vec<HashMap<u16, Value>>> {
1837 self.block(context.control)?;
1838 unreachable!()
1839 }
1840
1841 fn prepare_rows_with_control(
1842 &self,
1843 context: &ExternalExecutionContext<'_>,
1844 _entry: &ExternalTableEntry,
1845 _rows: Vec<HashMap<u16, Value>>,
1846 ) -> Result<Vec<u8>> {
1847 self.block(context.control)?;
1848 unreachable!()
1849 }
1850
1851 fn rows_from_state_with_control(
1852 &self,
1853 context: &ExternalExecutionContext<'_>,
1854 _state: &[u8],
1855 ) -> Result<Vec<HashMap<u16, Value>>> {
1856 self.block(context.control)?;
1857 unreachable!()
1858 }
1859
1860 fn write_with_control(
1861 &self,
1862 context: &ExternalExecutionContext<'_>,
1863 _entry: &ExternalTableEntry,
1864 _op: ExternalWriteOp,
1865 _txn: &mut ExternalTxn,
1866 ) -> Result<ExternalWriteResult> {
1867 self.block(context.control)?;
1868 unreachable!()
1869 }
1870
1871 fn destroy_with_control(
1872 &self,
1873 context: &ExternalExecutionContext<'_>,
1874 _entry: &ExternalTableEntry,
1875 ) -> Result<()> {
1876 self.block(context.control)
1877 }
1878 }
1879
1880 impl ExternalTable for BlockingExternalModule {
1881 fn schema(&self) -> SchemaRef {
1882 Arc::new(ArrowSchema::empty())
1883 }
1884
1885 fn plan_with_control(
1886 &self,
1887 _request: &ExternalPlanRequest<'_>,
1888 control: &ExecutionControl,
1889 ) -> DFResult<ExternalPlan> {
1890 self.block(control)
1891 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
1892 unreachable!()
1893 }
1894
1895 fn scan_with_control(
1896 &self,
1897 _request: &ExternalPlanRequest<'_>,
1898 control: &ExecutionControl,
1899 ) -> DFResult<ExternalScan> {
1900 self.block(control)
1901 .map_err(|error| DataFusionError::Execution(error.to_string()))?;
1902 unreachable!()
1903 }
1904 }
1905
1906 fn cancel_blocking_callback<R>(
1907 database: &Arc<Database>,
1908 entry: &ExternalTableEntry,
1909 callback: impl FnOnce(
1910 &BlockingExternalModule,
1911 &ExternalExecutionContext<'_>,
1912 &ExternalTableEntry,
1913 ) -> R,
1914 ) -> R {
1915 let entered = Arc::new(std::sync::Barrier::new(2));
1916 let module = BlockingExternalModule {
1917 entered: Arc::clone(&entered),
1918 };
1919 let control = ExecutionControl::new(None);
1920 let cancel_control = control.clone();
1921 let canceller = std::thread::spawn(move || {
1922 entered.wait();
1923 cancel_control.cancel(mongreldb_core::CancellationReason::ClientRequest);
1924 });
1925 let context = ExternalExecutionContext {
1926 database,
1927 control: &control,
1928 query_id: None,
1929 };
1930 let result = callback(&module, &context, entry);
1931 canceller.join().unwrap();
1932 result
1933 }
1934
1935 #[test]
1936 fn every_external_callback_can_observe_cancellation() {
1937 let dir = tempfile::tempdir().unwrap();
1938 let database = Arc::new(Database::create(dir.path()).unwrap());
1939 let entry = ExternalTableEntry {
1940 name: "blocked".into(),
1941 module: "blocking".into(),
1942 args: Vec::new(),
1943 declared_schema: CoreSchema::default(),
1944 hidden_columns: Vec::new(),
1945 options: BTreeMap::new(),
1946 capabilities: ModuleCapabilities::default(),
1947 created_epoch: 0,
1948 };
1949
1950 let cancelled = |error: &MongrelQueryError| {
1951 matches!(
1952 error,
1953 MongrelQueryError::Core(mongreldb_core::MongrelError::Cancelled)
1954 )
1955 };
1956 assert!(cancelled(
1957 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1958 module.indexes_with_control(context, entry)
1959 })
1960 .unwrap_err()
1961 ));
1962 assert!(cancelled(
1963 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1964 module.connect_with_control(context, entry)
1965 })
1966 .unwrap_err()
1967 ));
1968 assert!(cancelled(
1969 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1970 module.read_rows_with_control(context, entry)
1971 })
1972 .unwrap_err()
1973 ));
1974 assert!(cancelled(
1975 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1976 module.prepare_rows_with_control(context, entry, Vec::new())
1977 })
1978 .unwrap_err()
1979 ));
1980 assert!(cancelled(
1981 &cancel_blocking_callback(&database, &entry, |module, context, _| {
1982 module.rows_from_state_with_control(context, &[])
1983 })
1984 .unwrap_err()
1985 ));
1986 assert!(cancelled(
1987 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1988 module.write_with_control(
1989 context,
1990 entry,
1991 ExternalWriteOp::Insert { rows: Vec::new() },
1992 &mut ExternalTxn::new(Vec::new()),
1993 )
1994 })
1995 .unwrap_err()
1996 ));
1997 assert!(cancelled(
1998 &cancel_blocking_callback(&database, &entry, |module, context, entry| {
1999 module.destroy_with_control(context, entry)
2000 })
2001 .unwrap_err()
2002 ));
2003
2004 let request = ExternalPlanRequest {
2005 projection: None,
2006 filters: Vec::new(),
2007 raw_filters: Vec::new(),
2008 order_by: Vec::new(),
2009 limit: None,
2010 offset: None,
2011 };
2012 let plan_result = cancel_blocking_callback(&database, &entry, |module, context, _| {
2013 module.plan_with_control(&request, context.control)
2014 });
2015 assert!(matches!(
2016 plan_result,
2017 Err(error) if error.to_string().contains("cancelled")
2018 ));
2019 }
2020
2021 #[test]
2022 fn project_scan_applies_limit_after_projection() {
2023 let schema = Arc::new(ArrowSchema::new(vec![
2024 Field::new("id", DataType::Int64, false),
2025 Field::new("value", DataType::Int64, false),
2026 ]));
2027 let batch = RecordBatch::try_new(
2028 schema.clone(),
2029 vec![
2030 Arc::new(Int64Array::from(vec![1, 2, 3, 4])) as ArrayRef,
2031 Arc::new(Int64Array::from(vec![10, 11, 12, 13])) as ArrayRef,
2032 ],
2033 )
2034 .unwrap();
2035
2036 let scan = project_scan(schema, vec![batch], Some(&[1]), Some(2)).unwrap();
2037
2038 assert_eq!(scan.schema.fields().len(), 1);
2039 assert_eq!(scan.schema.field(0).name(), "value");
2040 assert_eq!(scan.batches.len(), 1);
2041 assert_eq!(scan.batches[0].num_columns(), 1);
2042 assert_eq!(scan.batches[0].num_rows(), 2);
2043 let values = scan.batches[0]
2044 .column(0)
2045 .as_any()
2046 .downcast_ref::<Int64Array>()
2047 .unwrap();
2048 assert_eq!(values.value(0), 10);
2049 assert_eq!(values.value(1), 11);
2050 }
2051
2052 #[test]
2053 fn external_plan_derives_accepted_and_residual_filter_metadata() {
2054 let plan = ExternalPlan::new(
2055 vec![
2056 TableProviderFilterPushDown::Exact,
2057 TableProviderFilterPushDown::Inexact,
2058 TableProviderFilterPushDown::Unsupported,
2059 ],
2060 Some(42),
2061 3.5,
2062 true,
2063 );
2064
2065 assert_eq!(
2066 plan.accepted_filters,
2067 vec![
2068 AcceptedFilter {
2069 filter_index: 0,
2070 pushdown: TableProviderFilterPushDown::Exact,
2071 },
2072 AcceptedFilter {
2073 filter_index: 1,
2074 pushdown: TableProviderFilterPushDown::Inexact,
2075 },
2076 ]
2077 );
2078 assert!(plan.residual_filters_required);
2079 assert_eq!(plan.estimated_rows, Some(42));
2080 assert_eq!(plan.estimated_cost, 3.5);
2081 assert!(plan.order_satisfied);
2082 }
2083
2084 #[test]
2085 fn external_state_writer_stops_before_over_budget_allocation() {
2086 use std::io::Write as _;
2087
2088 let mut writer = LimitedStateWriter::new(&[]);
2089 writer.requested = EXTERNAL_STATE_BYTES_LIMIT;
2090 assert!(writer.write_all(b"x").is_err());
2091 assert!(writer.bytes.is_empty());
2092 }
2093
2094 #[test]
2095 fn external_base_write_budget_rejects_before_push() {
2096 let mut txn = ExternalTxn::new(Vec::new());
2097 txn.base_write_bytes = EXTERNAL_BASE_WRITE_BYTES_LIMIT;
2098 let error = txn
2099 .emit_base_write(ExternalBaseWrite::Delete {
2100 table: "t".into(),
2101 row_id: 1,
2102 })
2103 .unwrap_err();
2104 assert!(matches!(
2105 error,
2106 MongrelQueryError::Core(mongreldb_core::MongrelError::ResourceLimitExceeded { .. })
2107 ));
2108 assert!(txn.base_writes.is_empty());
2109 }
2110}
2111
2112struct KvStoreModule;
2113
2114impl ExternalTableModule for KvStoreModule {
2115 fn name(&self) -> &str {
2116 "kv_store"
2117 }
2118
2119 fn descriptor(&self) -> ExternalModuleDescriptor {
2120 ExternalModuleDescriptor {
2121 schema: kv_store_schema(),
2122 hidden_columns: Vec::new(),
2123 capabilities: ModuleCapabilities {
2124 writable: true,
2125 deterministic: true,
2126 trigger_safe: true,
2127 transaction_safe: true,
2128 ..ModuleCapabilities::default()
2129 },
2130 }
2131 }
2132
2133 fn connect_with_control(
2134 &self,
2135 ctx: &ExternalExecutionContext<'_>,
2136 entry: &ExternalTableEntry,
2137 ) -> Result<Arc<dyn ExternalTable>> {
2138 ctx.control.checkpoint()?;
2139 ensure_no_args(entry, self.name())?;
2140 let rows = read_state_rows(ctx.database, entry)?;
2141 let schema = arrow_conv::arrow_schema(&entry.declared_schema)?;
2142 let batches = core_rows_to_batches(&entry.declared_schema, rows)?;
2143 Ok(Arc::new(KvStoreExternalTable { schema, batches }))
2144 }
2145
2146 fn read_rows_with_control(
2147 &self,
2148 ctx: &ExternalExecutionContext<'_>,
2149 entry: &ExternalTableEntry,
2150 ) -> Result<Vec<HashMap<u16, Value>>> {
2151 ctx.control.checkpoint()?;
2152 ensure_no_args(entry, self.name())?;
2153 read_state_rows(ctx.database, entry)
2154 }
2155
2156 fn prepare_rows_with_control(
2157 &self,
2158 ctx: &ExternalExecutionContext<'_>,
2159 entry: &ExternalTableEntry,
2160 rows: Vec<HashMap<u16, Value>>,
2161 ) -> Result<Vec<u8>> {
2162 ctx.control.checkpoint()?;
2163 ensure_no_args(entry, self.name())?;
2164 validate_external_rows(&entry.declared_schema, &rows)?;
2165 encode_state_rows(&rows)
2166 }
2167}
2168
2169#[derive(Clone)]
2170struct KvStoreExternalTable {
2171 schema: SchemaRef,
2172 batches: Vec<RecordBatch>,
2173}
2174
2175impl std::fmt::Debug for KvStoreExternalTable {
2176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2177 f.debug_struct("KvStoreExternalTable")
2178 .field("schema", &self.schema)
2179 .finish_non_exhaustive()
2180 }
2181}
2182
2183impl ExternalTable for KvStoreExternalTable {
2184 fn schema(&self) -> SchemaRef {
2185 self.schema.clone()
2186 }
2187
2188 fn plan_with_control(
2189 &self,
2190 request: &ExternalPlanRequest<'_>,
2191 control: &ExecutionControl,
2192 ) -> DFResult<ExternalPlan> {
2193 external_df_checkpoint(control)?;
2194 unsupported_plan(request)
2195 }
2196
2197 fn scan_with_control(
2198 &self,
2199 request: &ExternalPlanRequest<'_>,
2200 control: &mongreldb_core::ExecutionControl,
2201 ) -> DFResult<ExternalScan> {
2202 project_scan_with_control(
2203 self.schema.clone(),
2204 self.batches.clone(),
2205 request.projection.as_deref(),
2206 request.limit,
2207 Some(control),
2208 )
2209 }
2210}
2211
2212fn kv_store_schema() -> CoreSchema {
2213 CoreSchema {
2214 schema_id: 0,
2215 columns: vec![
2216 CoreColumnDef {
2217 id: 1,
2218 name: "key".to_string(),
2219 ty: TypeId::Bytes,
2220 flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
2221 default_value: None,
2222 },
2223 CoreColumnDef {
2224 id: 2,
2225 name: "value".to_string(),
2226 ty: TypeId::Bytes,
2227 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2228 default_value: None,
2229 },
2230 ],
2231 indexes: Vec::new(),
2232 colocation: Vec::new(),
2233 constraints: Default::default(),
2234 clustered: false,
2235 }
2236}
2237
2238#[derive(Serialize, Deserialize)]
2239struct ExternalRowState {
2240 cells: Vec<(u16, Value)>,
2241}
2242
2243const KV_STATE_MAGIC: &[u8] = b"mongreldb.external.kv.v1\n";
2244
2245#[derive(Serialize, Deserialize)]
2246struct ExternalKvState {
2247 entries: Vec<(Vec<u8>, Vec<u8>)>,
2248}
2249
2250fn state_file(db: &Arc<Database>, entry: &ExternalTableEntry) -> PathBuf {
2251 db.root()
2252 .join(VTAB_DIR)
2253 .join(&entry.name)
2254 .join("state.json")
2255}
2256
2257pub(crate) fn external_table_state_bytes(
2258 db: &Arc<Database>,
2259 entry: &ExternalTableEntry,
2260) -> Result<Vec<u8>> {
2261 db.ensure_consistent_read()?;
2262 let path = state_file(db, entry);
2263 if !path.exists() {
2264 return Ok(Vec::new());
2265 }
2266 let metadata = fs::metadata(&path)
2267 .map_err(|e| MongrelQueryError::Schema(format!("stat external state {:?}: {e}", path)))?;
2268 if metadata.len() > EXTERNAL_STATE_BYTES_LIMIT as u64 {
2269 return Err(external_resource_limit(
2270 "external table state file bytes",
2271 usize::try_from(metadata.len()).unwrap_or(usize::MAX),
2272 EXTERNAL_STATE_BYTES_LIMIT,
2273 ));
2274 }
2275 let state = fs::read(&path)
2276 .map_err(|e| MongrelQueryError::Schema(format!("read external state {:?}: {e}", path)))?;
2277 enforce_external_state_limit(&state)?;
2278 Ok(state)
2279}
2280
2281fn read_state_rows(
2282 db: &Arc<Database>,
2283 entry: &ExternalTableEntry,
2284) -> Result<Vec<HashMap<u16, Value>>> {
2285 let bytes = external_table_state_bytes(db, entry)?;
2286 if bytes.is_empty() {
2287 return Ok(Vec::new());
2288 }
2289 decode_state_rows(&bytes)
2290}
2291
2292struct LimitedStateWriter {
2293 bytes: Vec<u8>,
2294 requested: usize,
2295}
2296
2297impl LimitedStateWriter {
2298 fn new(prefix: &[u8]) -> Self {
2299 Self {
2300 bytes: prefix.to_vec(),
2301 requested: prefix.len(),
2302 }
2303 }
2304}
2305
2306impl std::io::Write for LimitedStateWriter {
2307 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2308 self.requested = self.requested.saturating_add(buf.len());
2309 if self.requested > EXTERNAL_STATE_BYTES_LIMIT {
2310 return Err(std::io::Error::other("external state byte limit exceeded"));
2311 }
2312 self.bytes.extend_from_slice(buf);
2313 Ok(buf.len())
2314 }
2315
2316 fn flush(&mut self) -> std::io::Result<()> {
2317 Ok(())
2318 }
2319}
2320
2321fn encode_external_json<T: Serialize + ?Sized>(value: &T, prefix: &[u8]) -> Result<Vec<u8>> {
2322 let mut writer = LimitedStateWriter::new(prefix);
2323 if let Err(error) = serde_json::to_writer(&mut writer, value) {
2324 if writer.requested > EXTERNAL_STATE_BYTES_LIMIT {
2325 return Err(external_resource_limit(
2326 "external table encoded state bytes",
2327 writer.requested,
2328 EXTERNAL_STATE_BYTES_LIMIT,
2329 ));
2330 }
2331 return Err(MongrelQueryError::Schema(format!(
2332 "encode external state: {error}"
2333 )));
2334 }
2335 Ok(writer.bytes)
2336}
2337
2338fn encode_state_rows(rows: &[HashMap<u16, Value>]) -> Result<Vec<u8>> {
2339 enforce_external_rows_limit(rows, None)?;
2340 let state = rows
2341 .iter()
2342 .map(|row| {
2343 let mut cells = row
2344 .iter()
2345 .map(|(id, value)| (*id, value.clone()))
2346 .collect::<Vec<_>>();
2347 cells.sort_by_key(|(id, _)| *id);
2348 ExternalRowState { cells }
2349 })
2350 .collect::<Vec<_>>();
2351 encode_external_json(&state, &[])
2352}
2353
2354fn decode_state_rows(state: &[u8]) -> Result<Vec<HashMap<u16, Value>>> {
2355 enforce_external_state_limit(state)?;
2356 let rows: Vec<ExternalRowState> = serde_json::from_slice(state)
2357 .map_err(|e| MongrelQueryError::Schema(format!("decode external state: {e}")))?;
2358 let rows = rows
2359 .into_iter()
2360 .map(|row| row.cells.into_iter().collect())
2361 .collect::<Vec<_>>();
2362 enforce_external_rows_limit(&rows, None)?;
2363 Ok(rows)
2364}
2365
2366fn decode_kv_state(state: &[u8]) -> Result<BTreeMap<Vec<u8>, Vec<u8>>> {
2367 enforce_external_state_limit(state)?;
2368 if state.is_empty() {
2369 return Ok(BTreeMap::new());
2370 }
2371 let json = state.strip_prefix(KV_STATE_MAGIC).ok_or_else(|| {
2372 MongrelQueryError::Schema(
2373 "external transaction state is not in key/value module format".into(),
2374 )
2375 })?;
2376 let decoded: ExternalKvState = serde_json::from_slice(json)
2377 .map_err(|e| MongrelQueryError::Schema(format!("decode external kv state: {e}")))?;
2378 if decoded.entries.len() > EXTERNAL_ROWS_LIMIT {
2379 return Err(external_resource_limit(
2380 "external transaction key/value count",
2381 decoded.entries.len(),
2382 EXTERNAL_ROWS_LIMIT,
2383 ));
2384 }
2385 let bytes = decoded.entries.iter().fold(0_usize, |total, (key, value)| {
2386 total.saturating_add(key.len()).saturating_add(value.len())
2387 });
2388 if bytes > EXTERNAL_STATE_BYTES_LIMIT {
2389 return Err(external_resource_limit(
2390 "external transaction key/value bytes",
2391 bytes,
2392 EXTERNAL_STATE_BYTES_LIMIT,
2393 ));
2394 }
2395 Ok(decoded.entries.into_iter().collect())
2396}
2397
2398fn encode_kv_state(state: &BTreeMap<Vec<u8>, Vec<u8>>) -> Result<Vec<u8>> {
2399 if state.len() > EXTERNAL_ROWS_LIMIT {
2400 return Err(external_resource_limit(
2401 "external transaction key/value count",
2402 state.len(),
2403 EXTERNAL_ROWS_LIMIT,
2404 ));
2405 }
2406 let bytes = state.iter().fold(0_usize, |total, (key, value)| {
2407 total.saturating_add(key.len()).saturating_add(value.len())
2408 });
2409 if bytes > EXTERNAL_STATE_BYTES_LIMIT {
2410 return Err(external_resource_limit(
2411 "external transaction key/value bytes",
2412 bytes,
2413 EXTERNAL_STATE_BYTES_LIMIT,
2414 ));
2415 }
2416 let encoded = ExternalKvState {
2417 entries: state
2418 .iter()
2419 .map(|(key, value)| (key.clone(), value.clone()))
2420 .collect(),
2421 };
2422 encode_external_json(&encoded, KV_STATE_MAGIC)
2423}
2424
2425fn validate_external_rows(schema: &CoreSchema, rows: &[HashMap<u16, Value>]) -> Result<()> {
2426 enforce_external_rows_limit(rows, None)?;
2427 let mut known = HashSet::new();
2428 let mut pk_cols = Vec::new();
2429 for column in &schema.columns {
2430 known.insert(column.id);
2431 if column.flags.contains(ColumnFlags::PRIMARY_KEY) {
2432 pk_cols.push(column.id);
2433 }
2434 }
2435 let mut keys = HashSet::new();
2436 for row in rows {
2437 for column_id in row.keys() {
2438 if !known.contains(column_id) {
2439 return Err(MongrelQueryError::Schema(format!(
2440 "external row contains unknown column id {column_id}"
2441 )));
2442 }
2443 }
2444 if !pk_cols.is_empty() {
2445 let mut key = Vec::new();
2446 for column_id in &pk_cols {
2447 let value = row.get(column_id).ok_or_else(|| {
2448 MongrelQueryError::Schema(format!(
2449 "external row is missing primary key column {column_id}"
2450 ))
2451 })?;
2452 key.extend_from_slice(&column_id.to_be_bytes());
2453 key.extend_from_slice(&value.encode_key());
2454 key.push(0);
2455 }
2456 if !keys.insert(key) {
2457 return Err(MongrelQueryError::Schema(
2458 "external table primary key conflict".into(),
2459 ));
2460 }
2461 }
2462 }
2463 Ok(())
2464}
2465
2466fn core_rows_to_batches(
2467 schema: &CoreSchema,
2468 rows: Vec<HashMap<u16, Value>>,
2469) -> Result<Vec<RecordBatch>> {
2470 let arrow_schema = arrow_conv::arrow_schema(schema)?;
2471 let arrays = schema
2472 .columns
2473 .iter()
2474 .map(|column| {
2475 let values = rows
2476 .iter()
2477 .map(|row| row.get(&column.id).cloned().unwrap_or(Value::Null))
2478 .collect::<Vec<_>>();
2479 arrow_conv::build_array(column.ty.clone(), &values)
2480 })
2481 .collect::<Result<Vec<_>>>()?;
2482 Ok(vec![RecordBatch::try_new(arrow_schema, arrays)
2483 .map_err(|e| MongrelQueryError::Arrow(e.to_string()))?])
2484}
2485
2486struct FtsDocsModule;
2487
2488impl ExternalTableModule for FtsDocsModule {
2489 fn name(&self) -> &str {
2490 "fts_docs"
2491 }
2492
2493 fn descriptor(&self) -> ExternalModuleDescriptor {
2494 ExternalModuleDescriptor {
2495 schema: fts_docs_schema(),
2496 hidden_columns: vec!["query".to_string()],
2497 capabilities: ModuleCapabilities {
2498 writable: true,
2499 deterministic: true,
2500 trigger_safe: true,
2501 ..ModuleCapabilities::default()
2502 },
2503 }
2504 }
2505
2506 fn indexes_with_control(
2507 &self,
2508 context: &ExternalExecutionContext<'_>,
2509 entry: &ExternalTableEntry,
2510 ) -> Result<Vec<ExternalModuleIndex>> {
2511 context.control.checkpoint()?;
2512 Ok(vec![ExternalModuleIndex::new(
2513 format!("{}_fts_inverted", entry.name),
2514 vec![2],
2515 )])
2516 }
2517
2518 fn connect_with_control(
2519 &self,
2520 ctx: &ExternalExecutionContext<'_>,
2521 entry: &ExternalTableEntry,
2522 ) -> Result<Arc<dyn ExternalTable>> {
2523 ctx.control.checkpoint()?;
2524 let options = fts_options(entry)?;
2525 let rows = read_state_rows(ctx.database, entry)?;
2526 let index = FtsInvertedIndex::build(&rows, &options);
2527 Ok(Arc::new(FtsDocsExternalTable {
2528 schema: arrow_conv::arrow_schema(&entry.declared_schema)?,
2529 core_schema: entry.declared_schema.clone(),
2530 options,
2531 index,
2532 rows,
2533 }))
2534 }
2535
2536 fn read_rows_with_control(
2537 &self,
2538 ctx: &ExternalExecutionContext<'_>,
2539 entry: &ExternalTableEntry,
2540 ) -> Result<Vec<HashMap<u16, Value>>> {
2541 ctx.control.checkpoint()?;
2542 let _ = fts_options(entry)?;
2543 read_state_rows(ctx.database, entry)
2544 }
2545
2546 fn prepare_rows_with_control(
2547 &self,
2548 ctx: &ExternalExecutionContext<'_>,
2549 entry: &ExternalTableEntry,
2550 rows: Vec<HashMap<u16, Value>>,
2551 ) -> Result<Vec<u8>> {
2552 ctx.control.checkpoint()?;
2553 let _ = fts_options(entry)?;
2554 let rows = rows
2555 .into_iter()
2556 .map(|mut row| {
2557 row.remove(&3);
2558 row.remove(&4);
2559 row.remove(&5);
2560 row.remove(&6);
2561 row
2562 })
2563 .collect::<Vec<_>>();
2564 validate_external_rows(&entry.declared_schema, &rows)?;
2565 encode_state_rows(&rows)
2566 }
2567}
2568
2569#[derive(Clone)]
2570struct FtsDocsExternalTable {
2571 schema: SchemaRef,
2572 core_schema: CoreSchema,
2573 options: FtsOptions,
2574 index: FtsInvertedIndex,
2575 rows: Vec<HashMap<u16, Value>>,
2576}
2577
2578impl std::fmt::Debug for FtsDocsExternalTable {
2579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2580 f.debug_struct("FtsDocsExternalTable")
2581 .field("schema", &self.schema)
2582 .finish_non_exhaustive()
2583 }
2584}
2585
2586impl ExternalTable for FtsDocsExternalTable {
2587 fn schema(&self) -> SchemaRef {
2588 self.schema.clone()
2589 }
2590
2591 fn plan_with_control(
2592 &self,
2593 request: &ExternalPlanRequest<'_>,
2594 control: &ExecutionControl,
2595 ) -> DFResult<ExternalPlan> {
2596 external_df_checkpoint(control)?;
2597 Ok(ExternalPlan::new(
2598 request
2599 .raw_filters
2600 .iter()
2601 .map(|filter| {
2602 if fts_query_from_expr(filter, &self.options).is_some() {
2603 TableProviderFilterPushDown::Exact
2604 } else {
2605 TableProviderFilterPushDown::Unsupported
2606 }
2607 })
2608 .collect(),
2609 None,
2610 1.0,
2611 false,
2612 ))
2613 }
2614
2615 fn scan_with_control(
2616 &self,
2617 request: &ExternalPlanRequest<'_>,
2618 control: &mongreldb_core::ExecutionControl,
2619 ) -> DFResult<ExternalScan> {
2620 external_checkpoint(Some(control), 0)?;
2621 let query = request
2622 .raw_filters
2623 .iter()
2624 .filter_map(|filter| fts_query_from_expr(filter, &self.options))
2625 .reduce(FtsQuery::and);
2626 let mut rows = Vec::new();
2627 if let Some(query) = query.as_ref() {
2628 for (index, row_index) in self.index.candidates(query).into_iter().enumerate() {
2629 external_checkpoint(Some(control), index)?;
2630 let Some(row) = self.rows.get(row_index) else {
2631 continue;
2632 };
2633 let Some(score) = fts_match_score(row, query, &self.options) else {
2634 continue;
2635 };
2636 rows.push(fts_enrich_row(
2637 row.clone(),
2638 Some(query),
2639 &self.options,
2640 Some(score),
2641 ));
2642 }
2643 } else {
2644 rows.reserve(self.rows.len());
2645 for (index, row) in self.rows.iter().cloned().enumerate() {
2646 external_checkpoint(Some(control), index)?;
2647 rows.push(fts_enrich_row(row, None, &self.options, None));
2648 }
2649 }
2650 external_checkpoint(Some(control), 0)?;
2651 project_scan_with_control(
2652 self.schema.clone(),
2653 core_rows_to_batches(&self.core_schema, rows)
2654 .map_err(|e| DataFusionError::Execution(e.to_string()))?,
2655 request.projection.as_deref(),
2656 request.limit,
2657 Some(control),
2658 )
2659 }
2660}
2661
2662fn fts_docs_schema() -> CoreSchema {
2663 CoreSchema {
2664 schema_id: 0,
2665 columns: vec![
2666 CoreColumnDef {
2667 id: 1,
2668 name: "doc_id".to_string(),
2669 ty: TypeId::Int64,
2670 flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
2671 default_value: None,
2672 },
2673 CoreColumnDef {
2674 id: 2,
2675 name: "text".to_string(),
2676 ty: TypeId::Bytes,
2677 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2678 default_value: None,
2679 },
2680 CoreColumnDef {
2681 id: 3,
2682 name: "query".to_string(),
2683 ty: TypeId::Bytes,
2684 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2685 default_value: None,
2686 },
2687 CoreColumnDef {
2688 id: 4,
2689 name: "rank".to_string(),
2690 ty: TypeId::Float64,
2691 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2692 default_value: None,
2693 },
2694 CoreColumnDef {
2695 id: 5,
2696 name: "snippet".to_string(),
2697 ty: TypeId::Bytes,
2698 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2699 default_value: None,
2700 },
2701 CoreColumnDef {
2702 id: 6,
2703 name: "highlight".to_string(),
2704 ty: TypeId::Bytes,
2705 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
2706 default_value: None,
2707 },
2708 ],
2709 indexes: Vec::new(),
2710 colocation: Vec::new(),
2711 constraints: Default::default(),
2712 clustered: false,
2713 }
2714}
2715
2716#[derive(Clone)]
2717struct FtsOptions {
2718 prefix_queries: bool,
2719 case_sensitive: bool,
2720 min_token_len: usize,
2721 stopwords: HashSet<String>,
2722}
2723
2724impl Default for FtsOptions {
2725 fn default() -> Self {
2726 Self {
2727 prefix_queries: false,
2728 case_sensitive: false,
2729 min_token_len: 1,
2730 stopwords: HashSet::new(),
2731 }
2732 }
2733}
2734
2735impl FtsOptions {
2736 fn normalize(&self, value: &str) -> String {
2737 if self.case_sensitive {
2738 value.to_string()
2739 } else {
2740 value.to_ascii_lowercase()
2741 }
2742 }
2743
2744 fn keep_term(&self, term: &str) -> bool {
2745 term.len() >= self.min_token_len && !self.stopwords.contains(term)
2746 }
2747}
2748
2749#[derive(Clone)]
2750struct FtsQuery {
2751 groups: Vec<Vec<FtsClause>>,
2752 prohibited: Vec<FtsClause>,
2753}
2754
2755impl FtsQuery {
2756 fn and(self, other: Self) -> Self {
2757 let left_groups = if self.groups.is_empty() {
2758 vec![Vec::new()]
2759 } else {
2760 self.groups
2761 };
2762 let right_groups = if other.groups.is_empty() {
2763 vec![Vec::new()]
2764 } else {
2765 other.groups
2766 };
2767 let mut groups = Vec::new();
2768 for left in &left_groups {
2769 for right in &right_groups {
2770 let mut combined = left.clone();
2771 combined.extend(right.clone());
2772 groups.push(combined);
2773 }
2774 }
2775 let mut prohibited = self.prohibited;
2776 prohibited.extend(other.prohibited);
2777 Self { groups, prohibited }
2778 }
2779
2780 fn positive_clauses(&self) -> impl Iterator<Item = &FtsClause> {
2781 self.groups.iter().flatten()
2782 }
2783}
2784
2785#[derive(Clone)]
2786struct FtsInvertedIndex {
2787 terms: HashMap<String, Vec<usize>>,
2788 all_rows: Vec<usize>,
2789}
2790
2791impl FtsInvertedIndex {
2792 fn build(rows: &[HashMap<u16, Value>], options: &FtsOptions) -> Self {
2793 let mut term_sets: HashMap<String, BTreeSet<usize>> = HashMap::new();
2794 for (idx, row) in rows.iter().enumerate() {
2795 let Some(Value::Bytes(text)) = row.get(&2) else {
2796 continue;
2797 };
2798 let text = String::from_utf8_lossy(text);
2799 let mut row_terms = HashSet::new();
2800 for span in token_spans_with_options(&text, options) {
2801 if row_terms.insert(span.term.clone()) {
2802 term_sets.entry(span.term).or_default().insert(idx);
2803 }
2804 }
2805 }
2806 let terms = term_sets
2807 .into_iter()
2808 .map(|(term, rows)| (term, rows.into_iter().collect()))
2809 .collect();
2810 Self {
2811 terms,
2812 all_rows: (0..rows.len()).collect(),
2813 }
2814 }
2815
2816 fn candidates(&self, query: &FtsQuery) -> Vec<usize> {
2817 let mut rows = if query.groups.is_empty() {
2818 self.all_rows.iter().copied().collect::<BTreeSet<_>>()
2819 } else {
2820 query
2821 .groups
2822 .iter()
2823 .filter_map(|group| self.group_candidates(group))
2824 .flatten()
2825 .collect::<BTreeSet<_>>()
2826 };
2827 for clause in &query.prohibited {
2828 for idx in self.clause_candidates(clause) {
2829 rows.remove(&idx);
2830 }
2831 }
2832 rows.into_iter().collect()
2833 }
2834
2835 fn group_candidates(&self, group: &[FtsClause]) -> Option<Vec<usize>> {
2836 let mut iter = group.iter().map(|clause| self.clause_candidates(clause));
2837 let first = iter.next()?;
2838 let mut current = first.into_iter().collect::<BTreeSet<_>>();
2839 for rows in iter {
2840 let next = rows.into_iter().collect::<BTreeSet<_>>();
2841 current = current.intersection(&next).copied().collect();
2842 if current.is_empty() {
2843 break;
2844 }
2845 }
2846 Some(current.into_iter().collect())
2847 }
2848
2849 fn clause_candidates(&self, clause: &FtsClause) -> Vec<usize> {
2850 match clause {
2851 FtsClause::Term { term, prefix } => {
2852 if *prefix {
2853 self.terms
2854 .iter()
2855 .filter(|(indexed, _)| indexed.starts_with(term))
2856 .flat_map(|(_, rows)| rows.iter().copied())
2857 .collect::<BTreeSet<_>>()
2858 .into_iter()
2859 .collect()
2860 } else {
2861 self.terms.get(term).cloned().unwrap_or_default()
2862 }
2863 }
2864 FtsClause::Phrase(terms) => {
2865 let clauses = terms
2866 .iter()
2867 .map(|term| FtsClause::Term {
2868 term: term.clone(),
2869 prefix: false,
2870 })
2871 .collect::<Vec<_>>();
2872 self.group_candidates(&clauses).unwrap_or_default()
2873 }
2874 }
2875 }
2876}
2877
2878#[derive(Clone, PartialEq, Eq)]
2879enum FtsClause {
2880 Term { term: String, prefix: bool },
2881 Phrase(Vec<String>),
2882}
2883
2884enum FtsQueryToken {
2885 Clause(FtsClause),
2886 Or,
2887 Not,
2888}
2889
2890fn fts_options(entry: &ExternalTableEntry) -> Result<FtsOptions> {
2891 let mut options = FtsOptions::default();
2892 for arg in &entry.args {
2893 let raw = module_arg_string(arg).trim();
2894 if raw.is_empty() {
2895 continue;
2896 }
2897 let (key, value) = raw
2898 .split_once('=')
2899 .map_or((raw, "true"), |(key, value)| (key.trim(), value.trim()));
2900 match key.to_ascii_lowercase().as_str() {
2901 "tokenizer" => match value.to_ascii_lowercase().as_str() {
2902 "simple" | "ascii" | "unicode61" => {}
2903 other => {
2904 return Err(MongrelQueryError::Schema(format!(
2905 "fts_docs tokenizer {other:?} is not supported"
2906 )))
2907 }
2908 },
2909 "prefix" | "prefix_queries" => options.prefix_queries = parse_bool_option(value)?,
2910 "case_sensitive" => options.case_sensitive = parse_bool_option(value)?,
2911 "min_token_len" => {
2912 options.min_token_len = value.parse::<usize>().map_err(|e| {
2913 MongrelQueryError::Schema(format!(
2914 "fts_docs min_token_len {value:?} must be an integer: {e}"
2915 ))
2916 })?;
2917 if options.min_token_len == 0 {
2918 return Err(MongrelQueryError::Schema(
2919 "fts_docs min_token_len must be at least 1".into(),
2920 ));
2921 }
2922 }
2923 "stopwords" => {
2924 options.stopwords = value
2925 .split('|')
2926 .map(str::trim)
2927 .filter(|word| !word.is_empty())
2928 .map(|word| options.normalize(word))
2929 .collect();
2930 }
2931 other => {
2932 return Err(MongrelQueryError::Schema(format!(
2933 "fts_docs option {other:?} is not supported"
2934 )))
2935 }
2936 }
2937 }
2938 Ok(options)
2939}
2940
2941fn parse_bool_option(value: &str) -> Result<bool> {
2942 match value.to_ascii_lowercase().as_str() {
2943 "1" | "true" | "yes" | "on" => Ok(true),
2944 "0" | "false" | "no" | "off" => Ok(false),
2945 _ => Err(MongrelQueryError::Schema(format!(
2946 "expected boolean option value, got {value:?}"
2947 ))),
2948 }
2949}
2950
2951fn fts_query_from_expr(expr: &Expr, options: &FtsOptions) -> Option<FtsQuery> {
2952 match expr {
2953 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::And => {
2954 Some(fts_query_from_expr(left, options)?.and(fts_query_from_expr(right, options)?))
2955 }
2956 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::Eq => {
2957 let literal = match (left.as_ref(), right.as_ref()) {
2958 (Expr::Column(column), Expr::Literal(literal, _)) if column.name == "query" => {
2959 literal_string(literal)?
2960 }
2961 (Expr::Literal(literal, _), Expr::Column(column)) if column.name == "query" => {
2962 literal_string(literal)?
2963 }
2964 _ => return None,
2965 };
2966 parse_fts_query(&literal, options)
2967 }
2968 _ => None,
2969 }
2970}
2971
2972fn parse_fts_query(input: &str, options: &FtsOptions) -> Option<FtsQuery> {
2973 let tokens = fts_query_tokens(input, options);
2974 if tokens.is_empty() {
2975 return None;
2976 }
2977 let mut groups: Vec<Vec<FtsClause>> = vec![Vec::new()];
2978 let mut prohibited = Vec::new();
2979 let mut negate = false;
2980 for token in tokens {
2981 match token {
2982 FtsQueryToken::Or => {
2983 if groups.last().is_some_and(|group| !group.is_empty()) {
2984 groups.push(Vec::new());
2985 }
2986 negate = false;
2987 }
2988 FtsQueryToken::Not => negate = true,
2989 FtsQueryToken::Clause(clause) => {
2990 if negate {
2991 prohibited.push(clause);
2992 negate = false;
2993 } else if let Some(group) = groups.last_mut() {
2994 group.push(clause);
2995 }
2996 }
2997 }
2998 }
2999 groups.retain(|group| !group.is_empty());
3000 if groups.is_empty() && prohibited.is_empty() {
3001 None
3002 } else {
3003 Some(FtsQuery { groups, prohibited })
3004 }
3005}
3006
3007fn fts_query_tokens(input: &str, options: &FtsOptions) -> Vec<FtsQueryToken> {
3008 let mut tokens = Vec::new();
3009 let mut chars = input.char_indices().peekable();
3010 while let Some((idx, ch)) = chars.next() {
3011 if ch.is_whitespace() {
3012 continue;
3013 }
3014 if ch == '"' {
3015 let start = idx + ch.len_utf8();
3016 let mut end = input.len();
3017 for (next_idx, next_ch) in chars.by_ref() {
3018 if next_ch == '"' {
3019 end = next_idx;
3020 break;
3021 }
3022 }
3023 let terms = token_spans_with_options(&input[start..end], options)
3024 .into_iter()
3025 .map(|span| span.term)
3026 .collect::<Vec<_>>();
3027 if !terms.is_empty() {
3028 tokens.push(FtsQueryToken::Clause(FtsClause::Phrase(terms)));
3029 }
3030 continue;
3031 }
3032 let start = idx;
3033 let mut end = idx + ch.len_utf8();
3034 while let Some((next_idx, next_ch)) = chars.peek().copied() {
3035 if next_ch.is_whitespace() {
3036 break;
3037 }
3038 chars.next();
3039 end = next_idx + next_ch.len_utf8();
3040 }
3041 let raw = &input[start..end];
3042 if raw.eq_ignore_ascii_case("OR") {
3043 tokens.push(FtsQueryToken::Or);
3044 } else if raw.eq_ignore_ascii_case("NOT") {
3045 tokens.push(FtsQueryToken::Not);
3046 } else if let Some(rest) = raw.strip_prefix('-') {
3047 tokens.push(FtsQueryToken::Not);
3048 tokens.extend(
3049 word_clauses(rest, options)
3050 .into_iter()
3051 .map(FtsQueryToken::Clause),
3052 );
3053 } else {
3054 tokens.extend(
3055 word_clauses(raw, options)
3056 .into_iter()
3057 .map(FtsQueryToken::Clause),
3058 );
3059 }
3060 }
3061 tokens
3062}
3063
3064fn word_clauses(raw: &str, options: &FtsOptions) -> Vec<FtsClause> {
3065 let raw = raw.trim_matches(|ch: char| !ch.is_ascii_alphanumeric() && ch != '*');
3066 if raw.is_empty() {
3067 return Vec::new();
3068 }
3069 let prefix = options.prefix_queries && raw.ends_with('*');
3070 let raw = raw.trim_end_matches('*');
3071 token_spans_with_options(raw, options)
3072 .into_iter()
3073 .map(|span| FtsClause::Term {
3074 term: span.term,
3075 prefix,
3076 })
3077 .collect()
3078}
3079
3080fn fts_match_score(
3081 row: &HashMap<u16, Value>,
3082 query: &FtsQuery,
3083 options: &FtsOptions,
3084) -> Option<f64> {
3085 let Some(Value::Bytes(text)) = row.get(&2) else {
3086 return None;
3087 };
3088 let text = String::from_utf8_lossy(text);
3089 let tokens = token_spans_with_options(&text, options);
3090 if query
3091 .prohibited
3092 .iter()
3093 .any(|clause| clause_matches(&tokens, clause))
3094 {
3095 return None;
3096 }
3097 let positive_score = if query.groups.is_empty() {
3098 Some(0.0)
3099 } else {
3100 query
3101 .groups
3102 .iter()
3103 .filter(|group| group.iter().all(|clause| clause_matches(&tokens, clause)))
3104 .map(|group| {
3105 group
3106 .iter()
3107 .map(|clause| clause_score(&tokens, clause))
3108 .sum()
3109 })
3110 .max_by(|left: &f64, right: &f64| left.total_cmp(right))
3111 }?;
3112 Some(positive_score)
3113}
3114
3115fn clause_matches(tokens: &[TokenSpan], clause: &FtsClause) -> bool {
3116 clause_score(tokens, clause) > 0.0
3117}
3118
3119fn clause_score(tokens: &[TokenSpan], clause: &FtsClause) -> f64 {
3120 match clause {
3121 FtsClause::Term { term, prefix } => tokens
3122 .iter()
3123 .filter(|token| token_matches_term(&token.term, term, *prefix))
3124 .count() as f64,
3125 FtsClause::Phrase(terms) => phrase_occurrences(tokens, terms) as f64 * terms.len() as f64,
3126 }
3127}
3128
3129fn token_matches_term(token: &str, term: &str, prefix: bool) -> bool {
3130 if prefix {
3131 token.starts_with(term)
3132 } else {
3133 token == term
3134 }
3135}
3136
3137fn phrase_occurrences(tokens: &[TokenSpan], terms: &[String]) -> usize {
3138 if terms.is_empty() || tokens.len() < terms.len() {
3139 return 0;
3140 }
3141 tokens
3142 .windows(terms.len())
3143 .filter(|window| {
3144 window
3145 .iter()
3146 .zip(terms)
3147 .all(|(token, term)| token.term == *term)
3148 })
3149 .count()
3150}
3151
3152fn token_matches_positive_clause(token: &str, query: &FtsQuery) -> bool {
3153 query.positive_clauses().any(|clause| match clause {
3154 FtsClause::Term { term, prefix } => token_matches_term(token, term, *prefix),
3155 FtsClause::Phrase(terms) => terms.iter().any(|term| term == token),
3156 })
3157}
3158
3159fn fts_enrich_row(
3160 mut row: HashMap<u16, Value>,
3161 query: Option<&FtsQuery>,
3162 options: &FtsOptions,
3163 score: Option<f64>,
3164) -> HashMap<u16, Value> {
3165 row.remove(&3);
3166 row.remove(&4);
3167 row.remove(&5);
3168 row.remove(&6);
3169 let Some(score) = score else {
3170 return row;
3171 };
3172 let text = match row.get(&2) {
3173 Some(Value::Bytes(text)) => String::from_utf8_lossy(text).into_owned(),
3174 _ => String::new(),
3175 };
3176 row.insert(4, Value::Float64(score));
3177 if let Some(query) = query {
3178 row.insert(
3179 5,
3180 Value::Bytes(fts_snippet(&text, query, options).into_bytes()),
3181 );
3182 row.insert(
3183 6,
3184 Value::Bytes(fts_highlight(&text, query, options).into_bytes()),
3185 );
3186 }
3187 row
3188}
3189
3190#[derive(Debug, Clone)]
3191struct TokenSpan {
3192 term: String,
3193 start: usize,
3194 end: usize,
3195}
3196
3197fn token_spans_with_options(input: &str, options: &FtsOptions) -> Vec<TokenSpan> {
3198 let mut spans = Vec::new();
3199 let mut start = None;
3200 for (idx, ch) in input.char_indices() {
3201 if ch.is_ascii_alphanumeric() {
3202 start.get_or_insert(idx);
3203 } else if let Some(lo) = start.take() {
3204 push_token_span(input, &mut spans, lo, idx, options);
3205 }
3206 }
3207 if let Some(lo) = start {
3208 push_token_span(input, &mut spans, lo, input.len(), options);
3209 }
3210 spans
3211}
3212
3213fn push_token_span(
3214 input: &str,
3215 spans: &mut Vec<TokenSpan>,
3216 start: usize,
3217 end: usize,
3218 options: &FtsOptions,
3219) {
3220 if start < end {
3221 let term = options.normalize(&input[start..end]);
3222 if options.keep_term(&term) {
3223 spans.push(TokenSpan { term, start, end });
3224 }
3225 }
3226}
3227
3228fn fts_snippet(text: &str, query: &FtsQuery, options: &FtsOptions) -> String {
3229 let spans = token_spans_with_options(text, options);
3230 if spans.is_empty() {
3231 return String::new();
3232 }
3233 let first_match = spans
3234 .iter()
3235 .position(|span| token_matches_positive_clause(&span.term, query))
3236 .unwrap_or(0);
3237 let lo = first_match.saturating_sub(3);
3238 let hi = (first_match + 5).min(spans.len());
3239 let mut out = Vec::new();
3240 if lo > 0 {
3241 out.push("...".to_string());
3242 }
3243 for span in &spans[lo..hi] {
3244 let token = &text[span.start..span.end];
3245 if token_matches_positive_clause(&span.term, query) {
3246 out.push(format!("[{token}]"));
3247 } else {
3248 out.push(token.to_string());
3249 }
3250 }
3251 if hi < spans.len() {
3252 out.push("...".to_string());
3253 }
3254 out.join(" ")
3255}
3256
3257fn fts_highlight(text: &str, query: &FtsQuery, options: &FtsOptions) -> String {
3258 let spans = token_spans_with_options(text, options);
3259 if spans.is_empty() {
3260 return text.to_string();
3261 }
3262 let mut out = String::new();
3263 let mut cursor = 0;
3264 for span in spans {
3265 out.push_str(&text[cursor..span.start]);
3266 let token = &text[span.start..span.end];
3267 if token_matches_positive_clause(&span.term, query) {
3268 out.push_str("<b>");
3269 out.push_str(token);
3270 out.push_str("</b>");
3271 } else {
3272 out.push_str(token);
3273 }
3274 cursor = span.end;
3275 }
3276 out.push_str(&text[cursor..]);
3277 out
3278}
3279
3280struct RTreeRectsModule;
3281
3282impl ExternalTableModule for RTreeRectsModule {
3283 fn name(&self) -> &str {
3284 "rtree_rects"
3285 }
3286
3287 fn descriptor(&self) -> ExternalModuleDescriptor {
3288 ExternalModuleDescriptor {
3289 schema: rtree_rects_schema(),
3290 hidden_columns: vec![
3291 "query_min_x".to_string(),
3292 "query_max_x".to_string(),
3293 "query_min_y".to_string(),
3294 "query_max_y".to_string(),
3295 ],
3296 capabilities: ModuleCapabilities {
3297 writable: true,
3298 deterministic: true,
3299 trigger_safe: true,
3300 ..ModuleCapabilities::default()
3301 },
3302 }
3303 }
3304
3305 fn indexes_with_control(
3306 &self,
3307 context: &ExternalExecutionContext<'_>,
3308 entry: &ExternalTableEntry,
3309 ) -> Result<Vec<ExternalModuleIndex>> {
3310 context.control.checkpoint()?;
3311 Ok(vec![ExternalModuleIndex::new(
3312 format!("{}_rtree_spatial", entry.name),
3313 vec![2, 3, 4, 5],
3314 )])
3315 }
3316
3317 fn connect_with_control(
3318 &self,
3319 ctx: &ExternalExecutionContext<'_>,
3320 entry: &ExternalTableEntry,
3321 ) -> Result<Arc<dyn ExternalTable>> {
3322 ctx.control.checkpoint()?;
3323 ensure_no_args(entry, self.name())?;
3324 let rows = read_state_rows(ctx.database, entry)?;
3325 let index = RTreeSpatialIndex::build(&rows);
3326 Ok(Arc::new(RTreeRectsExternalTable {
3327 schema: arrow_conv::arrow_schema(&entry.declared_schema)?,
3328 core_schema: entry.declared_schema.clone(),
3329 index,
3330 rows,
3331 }))
3332 }
3333
3334 fn read_rows_with_control(
3335 &self,
3336 ctx: &ExternalExecutionContext<'_>,
3337 entry: &ExternalTableEntry,
3338 ) -> Result<Vec<HashMap<u16, Value>>> {
3339 ctx.control.checkpoint()?;
3340 ensure_no_args(entry, self.name())?;
3341 read_state_rows(ctx.database, entry)
3342 }
3343
3344 fn prepare_rows_with_control(
3345 &self,
3346 ctx: &ExternalExecutionContext<'_>,
3347 entry: &ExternalTableEntry,
3348 rows: Vec<HashMap<u16, Value>>,
3349 ) -> Result<Vec<u8>> {
3350 ctx.control.checkpoint()?;
3351 ensure_no_args(entry, self.name())?;
3352 let rows = rows
3353 .into_iter()
3354 .map(|mut row| {
3355 for column_id in 6..=9 {
3356 row.remove(&column_id);
3357 }
3358 row
3359 })
3360 .collect::<Vec<_>>();
3361 validate_external_rows(&entry.declared_schema, &rows)?;
3362 encode_state_rows(&rows)
3363 }
3364}
3365
3366#[derive(Clone)]
3367struct RTreeRectsExternalTable {
3368 schema: SchemaRef,
3369 core_schema: CoreSchema,
3370 index: RTreeSpatialIndex,
3371 rows: Vec<HashMap<u16, Value>>,
3372}
3373
3374impl std::fmt::Debug for RTreeRectsExternalTable {
3375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3376 f.debug_struct("RTreeRectsExternalTable")
3377 .field("schema", &self.schema)
3378 .finish_non_exhaustive()
3379 }
3380}
3381
3382impl ExternalTable for RTreeRectsExternalTable {
3383 fn schema(&self) -> SchemaRef {
3384 self.schema.clone()
3385 }
3386
3387 fn plan_with_control(
3388 &self,
3389 request: &ExternalPlanRequest<'_>,
3390 control: &ExecutionControl,
3391 ) -> DFResult<ExternalPlan> {
3392 external_df_checkpoint(control)?;
3393 Ok(ExternalPlan::new(
3394 request
3395 .raw_filters
3396 .iter()
3397 .map(|filter| {
3398 if rtree_query_bound(filter).is_some()
3399 || rtree_query_rect_function(filter).is_some()
3400 {
3401 TableProviderFilterPushDown::Exact
3402 } else {
3403 TableProviderFilterPushDown::Unsupported
3404 }
3405 })
3406 .collect(),
3407 None,
3408 1.0,
3409 false,
3410 ))
3411 }
3412
3413 fn scan_with_control(
3414 &self,
3415 request: &ExternalPlanRequest<'_>,
3416 control: &mongreldb_core::ExecutionControl,
3417 ) -> DFResult<ExternalScan> {
3418 external_checkpoint(Some(control), 0)?;
3419 let mut hidden_bounds = QueryRect::default();
3420 let mut has_hidden_bounds = false;
3421 for (column, value) in request
3422 .raw_filters
3423 .iter()
3424 .filter_map(|filter| rtree_query_bound(filter))
3425 {
3426 hidden_bounds.set(column, value);
3427 has_hidden_bounds = true;
3428 }
3429 let mut query_rects = request
3430 .raw_filters
3431 .iter()
3432 .filter_map(|filter| rtree_query_rect_function(filter))
3433 .collect::<Vec<_>>();
3434 if has_hidden_bounds || query_rects.is_empty() {
3435 query_rects.push(hidden_bounds);
3436 }
3437 let mut candidate_rows: Option<BTreeSet<usize>> = None;
3438 for bounds in &query_rects {
3439 let mut next = BTreeSet::new();
3440 for (index, row) in self.index.candidates(*bounds).into_iter().enumerate() {
3441 external_checkpoint(Some(control), index)?;
3442 next.insert(row);
3443 }
3444 candidate_rows = Some(match candidate_rows.take() {
3445 Some(current) => {
3446 let mut intersection = BTreeSet::new();
3447 for (index, row) in current.into_iter().enumerate() {
3448 external_checkpoint(Some(control), index)?;
3449 if next.contains(&row) {
3450 intersection.insert(row);
3451 }
3452 }
3453 intersection
3454 }
3455 None => next,
3456 });
3457 }
3458 let candidate_rows =
3459 candidate_rows.unwrap_or_else(|| self.index.all_rows.iter().copied().collect());
3460 let mut rows = Vec::new();
3461 for (index, row) in self.rows.iter().enumerate() {
3462 external_checkpoint(Some(control), index)?;
3463 if candidate_rows.contains(&index)
3464 && query_rects
3465 .iter()
3466 .all(|bounds| rtree_row_matches(row, *bounds))
3467 {
3468 rows.push(row.clone());
3469 }
3470 }
3471 external_checkpoint(Some(control), 0)?;
3472 project_scan_with_control(
3473 self.schema.clone(),
3474 core_rows_to_batches(&self.core_schema, rows)
3475 .map_err(|e| DataFusionError::Execution(e.to_string()))?,
3476 request.projection.as_deref(),
3477 request.limit,
3478 Some(control),
3479 )
3480 }
3481}
3482
3483#[derive(Clone, Copy)]
3484struct QueryRect {
3485 min_x: f64,
3486 max_x: f64,
3487 min_y: f64,
3488 max_y: f64,
3489}
3490
3491impl Default for QueryRect {
3492 fn default() -> Self {
3493 Self {
3494 min_x: f64::NEG_INFINITY,
3495 max_x: f64::INFINITY,
3496 min_y: f64::NEG_INFINITY,
3497 max_y: f64::INFINITY,
3498 }
3499 }
3500}
3501
3502impl QueryRect {
3503 fn set(&mut self, column: &str, value: f64) {
3504 match column {
3505 "query_min_x" => self.min_x = value,
3506 "query_max_x" => self.max_x = value,
3507 "query_min_y" => self.min_y = value,
3508 "query_max_y" => self.max_y = value,
3509 _ => {}
3510 }
3511 }
3512}
3513
3514#[derive(Clone)]
3515struct RTreeSpatialIndex {
3516 all_rows: Vec<usize>,
3517 min_x: Vec<(f64, usize)>,
3518 max_x: Vec<(f64, usize)>,
3519 min_y: Vec<(f64, usize)>,
3520 max_y: Vec<(f64, usize)>,
3521}
3522
3523impl RTreeSpatialIndex {
3524 fn build(rows: &[HashMap<u16, Value>]) -> Self {
3525 let mut all_rows = Vec::new();
3526 let mut min_x = Vec::new();
3527 let mut max_x = Vec::new();
3528 let mut min_y = Vec::new();
3529 let mut max_y = Vec::new();
3530 for (idx, row) in rows.iter().enumerate() {
3531 let Some(x0) = row_f64(row, 2) else {
3532 continue;
3533 };
3534 let Some(x1) = row_f64(row, 3) else {
3535 continue;
3536 };
3537 let Some(y0) = row_f64(row, 4) else {
3538 continue;
3539 };
3540 let Some(y1) = row_f64(row, 5) else {
3541 continue;
3542 };
3543 if !(x0.is_finite() && x1.is_finite() && y0.is_finite() && y1.is_finite()) {
3544 continue;
3545 }
3546 all_rows.push(idx);
3547 min_x.push((x0, idx));
3548 max_x.push((x1, idx));
3549 min_y.push((y0, idx));
3550 max_y.push((y1, idx));
3551 }
3552 for values in [&mut min_x, &mut max_x, &mut min_y, &mut max_y] {
3553 values.sort_by(|left, right| left.0.total_cmp(&right.0));
3554 }
3555 Self {
3556 all_rows,
3557 min_x,
3558 max_x,
3559 min_y,
3560 max_y,
3561 }
3562 }
3563
3564 fn candidates(&self, query: QueryRect) -> Vec<usize> {
3565 let mut rows = self.lte(&self.min_x, query.max_x);
3566 for next in [
3567 self.gte(&self.max_x, query.min_x),
3568 self.lte(&self.min_y, query.max_y),
3569 self.gte(&self.max_y, query.min_y),
3570 ] {
3571 rows = rows.intersection(&next).copied().collect();
3572 if rows.is_empty() {
3573 break;
3574 }
3575 }
3576 rows.into_iter().collect()
3577 }
3578
3579 fn lte(&self, values: &[(f64, usize)], bound: f64) -> BTreeSet<usize> {
3580 if bound == f64::INFINITY {
3581 return self.all_rows.iter().copied().collect();
3582 }
3583 if bound.is_nan() {
3584 return BTreeSet::new();
3585 }
3586 let end = values.partition_point(|(value, _)| *value <= bound);
3587 values[..end].iter().map(|(_, idx)| *idx).collect()
3588 }
3589
3590 fn gte(&self, values: &[(f64, usize)], bound: f64) -> BTreeSet<usize> {
3591 if bound == f64::NEG_INFINITY {
3592 return self.all_rows.iter().copied().collect();
3593 }
3594 if bound.is_nan() {
3595 return BTreeSet::new();
3596 }
3597 let start = values.partition_point(|(value, _)| *value < bound);
3598 values[start..].iter().map(|(_, idx)| *idx).collect()
3599 }
3600}
3601
3602fn rtree_query_rect_function(expr: &Expr) -> Option<QueryRect> {
3603 let Expr::ScalarFunction(sf) = expr else {
3604 return None;
3605 };
3606 if !sf.func.name().eq_ignore_ascii_case("rtree_intersects") || sf.args.len() != 8 {
3607 return None;
3608 }
3609 for (arg, expected) in sf
3610 .args
3611 .iter()
3612 .take(4)
3613 .zip(["min_x", "max_x", "min_y", "max_y"])
3614 {
3615 let Expr::Column(column) = arg else {
3616 return None;
3617 };
3618 if column.name != expected {
3619 return None;
3620 }
3621 }
3622 Some(QueryRect {
3623 min_x: literal_f64_from_expr(&sf.args[4])?,
3624 max_x: literal_f64_from_expr(&sf.args[5])?,
3625 min_y: literal_f64_from_expr(&sf.args[6])?,
3626 max_y: literal_f64_from_expr(&sf.args[7])?,
3627 })
3628}
3629
3630fn literal_f64_from_expr(expr: &Expr) -> Option<f64> {
3631 match expr {
3632 Expr::Literal(literal, _) => literal_f64(literal),
3633 _ => None,
3634 }
3635}
3636
3637fn rtree_rects_schema() -> CoreSchema {
3638 CoreSchema {
3639 schema_id: 0,
3640 columns: vec![
3641 CoreColumnDef {
3642 id: 1,
3643 name: "id".to_string(),
3644 ty: TypeId::Int64,
3645 flags: ColumnFlags::empty().with(ColumnFlags::PRIMARY_KEY),
3646 default_value: None,
3647 },
3648 rect_column(2, "min_x"),
3649 rect_column(3, "max_x"),
3650 rect_column(4, "min_y"),
3651 rect_column(5, "max_y"),
3652 rect_column(6, "query_min_x"),
3653 rect_column(7, "query_max_x"),
3654 rect_column(8, "query_min_y"),
3655 rect_column(9, "query_max_y"),
3656 ],
3657 indexes: Vec::new(),
3658 colocation: Vec::new(),
3659 constraints: Default::default(),
3660 clustered: false,
3661 }
3662}
3663
3664fn rect_column(id: u16, name: &str) -> CoreColumnDef {
3665 CoreColumnDef {
3666 id,
3667 name: name.to_string(),
3668 ty: TypeId::Float64,
3669 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3670 default_value: None,
3671 }
3672}
3673
3674fn rtree_query_bound(expr: &Expr) -> Option<(&str, f64)> {
3675 match expr {
3676 Expr::BinaryExpr(BinaryExpr { left, op, right }) if *op == Operator::Eq => {
3677 match (left.as_ref(), right.as_ref()) {
3678 (Expr::Column(column), Expr::Literal(literal, _)) => {
3679 hidden_rect_column(&column.name).zip(literal_f64(literal))
3680 }
3681 (Expr::Literal(literal, _), Expr::Column(column)) => {
3682 hidden_rect_column(&column.name).zip(literal_f64(literal))
3683 }
3684 _ => None,
3685 }
3686 }
3687 _ => None,
3688 }
3689}
3690
3691fn hidden_rect_column(name: &str) -> Option<&str> {
3692 match name {
3693 "query_min_x" | "query_max_x" | "query_min_y" | "query_max_y" => Some(name),
3694 _ => None,
3695 }
3696}
3697
3698fn rtree_row_matches(row: &HashMap<u16, Value>, query: QueryRect) -> bool {
3699 let Some(min_x) = row_f64(row, 2) else {
3700 return false;
3701 };
3702 let Some(max_x) = row_f64(row, 3) else {
3703 return false;
3704 };
3705 let Some(min_y) = row_f64(row, 4) else {
3706 return false;
3707 };
3708 let Some(max_y) = row_f64(row, 5) else {
3709 return false;
3710 };
3711 max_x >= query.min_x && min_x <= query.max_x && max_y >= query.min_y && min_y <= query.max_y
3712}
3713
3714fn row_f64(row: &HashMap<u16, Value>, column_id: u16) -> Option<f64> {
3715 match row.get(&column_id) {
3716 Some(Value::Float64(value)) => Some(*value),
3717 Some(Value::Int64(value)) => Some(*value as f64),
3718 _ => None,
3719 }
3720}
3721
3722struct SeriesModule;
3723
3724impl ExternalTableModule for SeriesModule {
3725 fn name(&self) -> &str {
3726 "series"
3727 }
3728
3729 fn descriptor(&self) -> ExternalModuleDescriptor {
3730 ExternalModuleDescriptor {
3731 schema: CoreSchema {
3732 schema_id: 0,
3733 columns: vec![
3734 CoreColumnDef {
3735 id: 1,
3736 name: "value".to_string(),
3737 ty: TypeId::Int64,
3738 flags: ColumnFlags::empty(),
3739 default_value: None,
3740 },
3741 CoreColumnDef {
3742 id: 2,
3743 name: "start".to_string(),
3744 ty: TypeId::Int64,
3745 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3746 default_value: None,
3747 },
3748 CoreColumnDef {
3749 id: 3,
3750 name: "stop".to_string(),
3751 ty: TypeId::Int64,
3752 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3753 default_value: None,
3754 },
3755 CoreColumnDef {
3756 id: 4,
3757 name: "step".to_string(),
3758 ty: TypeId::Int64,
3759 flags: ColumnFlags::empty().with(ColumnFlags::NULLABLE),
3760 default_value: None,
3761 },
3762 ],
3763 indexes: Vec::new(),
3764 colocation: Vec::new(),
3765 constraints: Default::default(),
3766 clustered: false,
3767 },
3768 hidden_columns: vec!["start".to_string(), "stop".to_string(), "step".to_string()],
3769 capabilities: ModuleCapabilities {
3770 read_only: true,
3771 deterministic: true,
3772 trigger_safe: true,
3773 ..ModuleCapabilities::default()
3774 },
3775 }
3776 }
3777
3778 fn connect_with_control(
3779 &self,
3780 ctx: &ExternalExecutionContext<'_>,
3781 entry: &ExternalTableEntry,
3782 ) -> Result<Arc<dyn ExternalTable>> {
3783 ctx.control.checkpoint()?;
3784 let (start, stop, step) = series_args(entry)?;
3785 let table = SeriesExternalTable::new(start, stop, step)?;
3786 Ok(Arc::new(table))
3787 }
3788}
3789
3790struct SeriesExternalTable {
3791 start: i64,
3792 stop: i64,
3793 step: i64,
3794 schema: SchemaRef,
3795}
3796
3797impl SeriesExternalTable {
3798 fn new(start: i64, stop: i64, step: i64) -> Result<Self> {
3799 if step == 0 {
3800 return Err(MongrelQueryError::Schema(
3801 "series step must not be 0".into(),
3802 ));
3803 }
3804 Ok(Self {
3805 start,
3806 stop,
3807 step,
3808 schema: Arc::new(ArrowSchema::new(vec![Field::new(
3809 "value",
3810 DataType::Int64,
3811 false,
3812 )])),
3813 })
3814 }
3815
3816 fn batches(
3817 &self,
3818 request: &ExternalPlanRequest<'_>,
3819 control: &mongreldb_core::ExecutionControl,
3820 ) -> DFResult<Vec<RecordBatch>> {
3821 let mut values = Vec::new();
3822 let mut current = self.start;
3823 let mut scanned = 0;
3824 while if self.step > 0 {
3825 current <= self.stop
3826 } else {
3827 current >= self.stop
3828 } {
3829 external_checkpoint(Some(control), scanned)?;
3830 scanned += 1;
3831 if values.len() >= 1_000_000 {
3832 return Err(DataFusionError::Plan(
3833 "series output is capped at 1,000,000 rows".into(),
3834 ));
3835 }
3836 if request
3837 .filters
3838 .iter()
3839 .all(|filter| series_filter_matches(filter, current).unwrap_or(true))
3840 {
3841 values.push(current);
3842 if request.limit.is_some_and(|limit| values.len() >= limit) {
3843 break;
3844 }
3845 }
3846 current = current.saturating_add(self.step);
3847 if (self.step > 0 && current == i64::MAX) || (self.step < 0 && current == i64::MIN) {
3848 break;
3849 }
3850 }
3851 let batch = RecordBatch::try_new(
3852 self.schema.clone(),
3853 vec![Arc::new(Int64Array::from(values)) as ArrayRef],
3854 )?;
3855 Ok(vec![batch])
3856 }
3857}
3858
3859impl std::fmt::Debug for SeriesExternalTable {
3860 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3861 f.debug_struct("SeriesExternalTable")
3862 .field("start", &self.start)
3863 .field("stop", &self.stop)
3864 .field("step", &self.step)
3865 .finish_non_exhaustive()
3866 }
3867}
3868
3869impl ExternalTable for SeriesExternalTable {
3870 fn schema(&self) -> SchemaRef {
3871 self.schema.clone()
3872 }
3873
3874 fn plan_with_control(
3875 &self,
3876 request: &ExternalPlanRequest<'_>,
3877 control: &ExecutionControl,
3878 ) -> DFResult<ExternalPlan> {
3879 external_df_checkpoint(control)?;
3880 Ok(ExternalPlan::new(
3881 request
3882 .filters
3883 .iter()
3884 .map(|filter| {
3885 if series_filter_supported(filter) {
3886 TableProviderFilterPushDown::Exact
3887 } else {
3888 TableProviderFilterPushDown::Unsupported
3889 }
3890 })
3891 .collect(),
3892 None,
3893 1.0,
3894 self.step > 0,
3895 ))
3896 }
3897
3898 fn scan_with_control(
3899 &self,
3900 request: &ExternalPlanRequest<'_>,
3901 control: &mongreldb_core::ExecutionControl,
3902 ) -> DFResult<ExternalScan> {
3903 external_checkpoint(Some(control), 0)?;
3904 let batches = self.batches(request, control)?;
3905 external_checkpoint(Some(control), 0)?;
3906 project_scan_with_control(
3907 self.schema.clone(),
3908 batches,
3909 request.projection.as_deref(),
3910 request.limit,
3911 Some(control),
3912 )
3913 }
3914}
3915
3916fn series_filter_supported(filter: &ExternalFilter) -> bool {
3917 match filter {
3918 ExternalFilter::And(filters) => filters.iter().all(series_filter_supported),
3919 ExternalFilter::Compare {
3920 column_index,
3921 value,
3922 ..
3923 } => *column_index == 0 && literal_i64(value).is_some(),
3924 ExternalFilter::Unsupported { .. } => false,
3925 }
3926}
3927
3928fn series_filter_matches(filter: &ExternalFilter, value: i64) -> Option<bool> {
3929 match filter {
3930 ExternalFilter::And(filters) => filters.iter().try_fold(true, |matches, filter| {
3931 Some(matches && series_filter_matches(filter, value)?)
3932 }),
3933 ExternalFilter::Compare {
3934 column_index,
3935 op,
3936 value: literal,
3937 } if *column_index == 0 => {
3938 let literal = literal_i64(literal)?;
3939 Some(match op {
3940 ExternalFilterOp::Eq => value == literal,
3941 ExternalFilterOp::NotEq => value != literal,
3942 ExternalFilterOp::Gt => value > literal,
3943 ExternalFilterOp::GtEq => value >= literal,
3944 ExternalFilterOp::Lt => value < literal,
3945 ExternalFilterOp::LtEq => value <= literal,
3946 })
3947 }
3948 _ => None,
3949 }
3950}
3951
3952fn literal_i64(value: &ScalarValue) -> Option<i64> {
3953 match value {
3954 ScalarValue::Int8(Some(v)) => Some(*v as i64),
3955 ScalarValue::Int16(Some(v)) => Some(*v as i64),
3956 ScalarValue::Int32(Some(v)) => Some(*v as i64),
3957 ScalarValue::Int64(Some(v)) => Some(*v),
3958 ScalarValue::UInt8(Some(v)) => Some(*v as i64),
3959 ScalarValue::UInt16(Some(v)) => Some(*v as i64),
3960 ScalarValue::UInt32(Some(v)) => Some(*v as i64),
3961 ScalarValue::UInt64(Some(v)) => i64::try_from(*v).ok(),
3962 _ => None,
3963 }
3964}
3965
3966fn literal_f64(value: &ScalarValue) -> Option<f64> {
3967 match value {
3968 ScalarValue::Float32(Some(v)) => Some(*v as f64),
3969 ScalarValue::Float64(Some(v)) => Some(*v),
3970 _ => literal_i64(value).map(|value| value as f64),
3971 }
3972}
3973
3974fn literal_string(value: &ScalarValue) -> Option<String> {
3975 match value {
3976 ScalarValue::Utf8(Some(v))
3977 | ScalarValue::Utf8View(Some(v))
3978 | ScalarValue::LargeUtf8(Some(v)) => Some(v.clone()),
3979 ScalarValue::Binary(Some(v))
3980 | ScalarValue::BinaryView(Some(v))
3981 | ScalarValue::LargeBinary(Some(v)) => String::from_utf8(v.clone()).ok(),
3982 _ => None,
3983 }
3984}
3985
3986fn series_args(entry: &ExternalTableEntry) -> Result<(i64, i64, i64)> {
3987 let values = entry
3988 .args
3989 .iter()
3990 .map(|arg| match arg {
3991 ModuleArg::Ident(value) | ModuleArg::String(value) | ModuleArg::Number(value) => {
3992 value.parse::<i64>().map_err(|e| {
3993 MongrelQueryError::Schema(format!(
3994 "series module argument {value:?} must be an integer: {e}"
3995 ))
3996 })
3997 }
3998 })
3999 .collect::<Result<Vec<_>>>()?;
4000 match values.as_slice() {
4001 [] => Ok((0, -1, 1)),
4002 [stop] => Ok((0, *stop, 1)),
4003 [start, stop] => Ok((*start, *stop, 1)),
4004 [start, stop, step] => Ok((*start, *stop, *step)),
4005 _ => Err(MongrelQueryError::Schema(
4006 "series external table accepts at most three arguments".into(),
4007 )),
4008 }
4009}