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