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