Skip to main content

mongreldb_query/
external_modules.rs

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