1pub mod arrow_conv;
25mod commands;
26mod error;
27pub mod extended_sql_functions;
28mod external_modules;
29mod fk_join;
30mod native_agg;
31mod percentile;
32mod query_registry;
33mod scan;
34mod scored_sql;
35mod shadow;
36mod udf;
37
38pub use error::{MongrelQueryError, Result};
39pub use external_modules::{
40 ExternalBaseWrite, ExternalModuleDescriptor, ExternalModuleIndex, ExternalModuleRegistry,
41 ExternalPlan, ExternalPlanRequest, ExternalScan, ExternalTable, ExternalTableModule,
42 ExternalTxn, ExternalWriteOp, ExternalWriteResult, ModuleConnectCtx,
43};
44pub use query_registry::{
45 CancelOutcome, CommitFenceOutcome, QueryId, QueryStatus, RegisteredQueryGuard,
46 RegisteredSqlQuery, SqlQueryOptions, SqlQueryPhase, SqlQueryRegistry,
47};
48
49pub fn contains_boolean_ai_predicate(sql: &str) -> bool {
53 use sqlparser::dialect::GenericDialect;
54 use sqlparser::tokenizer::{Token, Tokenizer};
55
56 let Ok(tokens) = Tokenizer::new(&GenericDialect {}, sql).tokenize() else {
57 return false;
58 };
59 let tokens: Vec<_> = tokens
60 .iter()
61 .filter(|token| !matches!(token, Token::Whitespace(_)))
62 .collect();
63 tokens.windows(2).any(|tokens| {
64 matches!(
65 (tokens[0], tokens[1]),
66 (Token::Word(word), Token::LParen)
67 if word.value.eq_ignore_ascii_case("ann_search")
68 || word.value.eq_ignore_ascii_case("sparse_match")
69 )
70 })
71}
72
73pub type MongrelRecordBatchStream = datafusion::physical_plan::SendableRecordBatchStream;
74
75#[doc(hidden)]
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum SqlTestHookPoint {
78 AfterRegistration,
79 WaitingForSqlPermit,
80 WaitingForSessionPermit,
81 Planning,
82 BeforeScanBatch,
83 AfterScanBatch,
84 BeforeCommitFence,
85 InsideCommitCritical,
86 AfterStatementStaging,
87 BeforeSerializationBatch,
88}
89
90#[doc(hidden)]
91pub type SqlTestHook = Arc<dyn Fn(SqlTestHookPoint) + Send + Sync>;
92
93tokio::task_local! {
94 static CURRENT_SQL_QUERY: RegisteredSqlQuery;
97}
98
99use arrow::array::{Array, ArrayRef, Int64Array, StringArray};
100use arrow::datatypes::SchemaRef;
101use arrow::record_batch::RecordBatch;
102use datafusion::catalog::{Session, TableProvider};
103use datafusion::common::{DataFusionError, Result as DFResult};
104use datafusion::logical_expr::{AggregateUDF, Expr, ScalarUDF, TableType, WindowUDF};
105use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
106use datafusion::physical_plan::{execute_stream, ExecutionPlan};
107use datafusion::prelude::SessionContext;
108use mongreldb_core::{
109 AlterColumn, ColumnFlags, Cursor, Database, OwnedSnapshotGuard, Schema as CoreSchema, Snapshot,
110 Table, TableHandle, TypeId,
111};
112use parking_lot::Mutex;
113use std::borrow::Borrow;
114use std::collections::{HashMap, HashSet};
115use std::hash::Hash;
116use std::sync::Arc;
117
118use query_registry::SqlTaskContext;
119
120pub struct MongrelProvider {
123 db: TableHandle,
124 schema: SchemaRef,
125 core_schema: CoreSchema,
126 snapshot: Option<Snapshot>,
127 _retention: Option<Arc<OwnedSnapshotGuard>>,
128 security: Option<ProviderSecurity>,
129}
130
131#[derive(Clone)]
132struct ProviderSecurity {
133 database: Arc<Database>,
134 table: String,
135 principal: Option<mongreldb_core::Principal>,
136 principal_catalog_bound: bool,
137}
138
139impl ProviderSecurity {
140 fn principal(&self) -> mongreldb_core::Result<Option<mongreldb_core::Principal>> {
141 let Some(principal) = &self.principal else {
142 return Ok(None);
143 };
144 if self.principal_catalog_bound {
145 self.database
146 .resolve_principal(&principal.username)
147 .map(Some)
148 .ok_or(mongreldb_core::MongrelError::AuthRequired)
149 } else {
150 Ok(Some(principal.clone()))
151 }
152 }
153}
154
155#[derive(Debug, Clone)]
156pub(crate) struct ViewDef {
157 pub sql: String,
158 pub schema: CoreSchema,
159 pub input_types: HashMap<u16, Option<TypeId>>,
160}
161
162impl MongrelProvider {
163 pub fn new(db: Arc<Mutex<Table>>) -> Result<Self> {
164 Self::new_handle(db.into())
165 }
166
167 pub(crate) fn new_handle(db: TableHandle) -> Result<Self> {
168 let (schema, core_schema) = {
169 let db = db.lock();
170 (arrow_conv::arrow_schema(db.schema())?, db.schema().clone())
171 };
172 Ok(Self {
173 db,
174 schema,
175 core_schema,
176 snapshot: None,
177 _retention: None,
178 security: None,
179 })
180 }
181
182 pub(crate) fn new_secured(
183 db: TableHandle,
184 database: Arc<Database>,
185 table: String,
186 principal: Option<mongreldb_core::Principal>,
187 ) -> Result<Self> {
188 let core_schema = db.lock().schema().clone();
189 let schema = arrow_conv::arrow_schema(&core_schema)?;
190 Ok(Self {
191 db,
192 schema,
193 core_schema,
194 snapshot: None,
195 _retention: None,
196 security: Some(ProviderSecurity {
197 principal_catalog_bound: principal.as_ref().is_some_and(|principal| {
198 database.resolve_principal(&principal.username).is_some()
199 }),
200 database,
201 table,
202 principal,
203 }),
204 })
205 }
206
207 fn new_historical(
208 db: TableHandle,
209 snapshot: Snapshot,
210 retention: OwnedSnapshotGuard,
211 security: Option<ProviderSecurity>,
212 ) -> Result<Self> {
213 let full_schema = {
214 let db = db.lock();
215 db.schema().clone()
216 };
217 let core_schema = full_schema;
218 let schema = arrow_conv::arrow_schema(&core_schema)?;
219 Ok(Self {
220 db,
221 schema,
222 core_schema,
223 snapshot: Some(snapshot),
224 _retention: Some(Arc::new(retention)),
225 security,
226 })
227 }
228
229 pub fn arrow_schema(&self) -> SchemaRef {
230 self.schema.clone()
231 }
232}
233
234impl std::fmt::Debug for MongrelProvider {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 f.debug_struct("MongrelProvider").finish_non_exhaustive()
237 }
238}
239
240struct AsOfRegistration {
241 ctx: SessionContext,
242 table_name: String,
243}
244
245impl Drop for AsOfRegistration {
246 fn drop(&mut self) {
247 let _ = self.ctx.deregister_table(&self.table_name);
248 }
249}
250
251struct AsOfQuery {
252 sql: String,
253 registration: AsOfRegistration,
254}
255
256#[async_trait::async_trait]
257impl TableProvider for MongrelProvider {
258 fn schema(&self) -> SchemaRef {
259 self.schema.clone()
260 }
261
262 fn table_type(&self) -> TableType {
263 TableType::Base
264 }
265
266 fn supports_filters_pushdown(
271 &self,
272 filters: &[&Expr],
273 ) -> DFResult<Vec<datafusion::logical_expr::TableProviderFilterPushDown>> {
274 use datafusion::logical_expr::TableProviderFilterPushDown;
275 if self.snapshot.is_some() {
276 return Ok(vec![
277 TableProviderFilterPushDown::Unsupported;
278 filters.len()
279 ]);
280 }
281 let schema_ref = self.db.lock().schema().clone();
282 if self.security.is_some() {
283 return Ok(filters
284 .iter()
285 .map(|filter| {
286 if translate_ann_search(filter, &schema_ref).is_some()
287 || translate_sparse_match(filter, &schema_ref).is_some()
288 {
289 TableProviderFilterPushDown::Exact
290 } else {
291 TableProviderFilterPushDown::Unsupported
292 }
293 })
294 .collect());
295 }
296 Ok(filters
297 .iter()
298 .map(|f| match translate_filter(f, &schema_ref) {
299 Some(
300 mongreldb_core::Condition::FmContains { .. }
301 | mongreldb_core::Condition::FmContainsAll { .. },
302 ) => TableProviderFilterPushDown::Inexact,
303 Some(_) => TableProviderFilterPushDown::Exact,
304 None => match translate_ann_search(f, &schema_ref)
305 .or_else(|| translate_sparse_match(f, &schema_ref))
306 {
307 Some(_) => TableProviderFilterPushDown::Exact,
308 None => TableProviderFilterPushDown::Unsupported,
309 },
310 })
311 .collect())
312 }
313
314 async fn scan(
315 &self,
316 _state: &dyn Session,
317 projection: Option<&Vec<usize>>,
318 filters: &[Expr],
319 _limit: Option<usize>,
320 ) -> DFResult<Arc<dyn ExecutionPlan>> {
321 let core_err = |e: mongreldb_core::MongrelError| {
322 DataFusionError::External(Box::new(MongrelQueryError::Core(e)))
323 };
324 if let Some(security) = &self.security {
325 let principal = security.principal().map_err(core_err)?;
326 let allowed = security
327 .database
328 .select_column_ids_for(&security.table, principal.as_ref())
329 .map_err(core_err)?;
330 let projected = projection
331 .map(|projection| {
332 projection
333 .iter()
334 .filter_map(|index| self.core_schema.columns.get(*index))
335 .map(|column| column.id)
336 .collect::<Vec<_>>()
337 })
338 .unwrap_or_else(|| {
339 self.core_schema
340 .columns
341 .iter()
342 .map(|column| column.id)
343 .collect()
344 });
345 if projected.iter().any(|column| !allowed.contains(column)) {
346 security
347 .database
348 .require_columns_for(
349 &security.table,
350 mongreldb_core::ColumnOperation::Select,
351 &projected,
352 principal.as_ref(),
353 )
354 .map_err(core_err)?;
355 }
356 let ai_conditions = filters
357 .iter()
358 .filter_map(|filter| {
359 translate_ann_search(filter, &self.core_schema)
360 .or_else(|| translate_sparse_match(filter, &self.core_schema))
361 })
362 .collect::<Vec<_>>();
363 let mut required_columns = projected.clone();
364 required_columns.extend(mongreldb_core::query::condition_columns(&ai_conditions));
365 required_columns.sort_unstable();
366 required_columns.dedup();
367 let rows = if let Some(snapshot) = self.snapshot {
368 let rows = self.db.lock().visible_rows(snapshot).map_err(core_err)?;
369 security
370 .database
371 .secure_rows_for(&security.table, rows, principal.as_ref())
372 .map_err(core_err)?
373 } else {
374 security
375 .database
376 .with_authorized_read(
377 &security.table,
378 principal.as_ref(),
379 security.principal_catalog_bound,
380 |table, snapshot, allowed, effective_principal| {
381 security.database.require_columns_for(
382 &security.table,
383 mongreldb_core::ColumnOperation::Select,
384 &required_columns,
385 effective_principal,
386 )?;
387 let rows = if ai_conditions.is_empty() {
388 let mut rows = table.visible_rows(snapshot)?;
389 if let Some(allowed) = allowed {
390 rows.retain(|row| allowed.contains(&row.row_id));
391 }
392 rows
393 } else {
394 table.query_at_with_allowed(
395 &mongreldb_core::Query {
396 conditions: ai_conditions.clone(),
397 limit: Some(mongreldb_core::query::MAX_FINAL_LIMIT),
398 offset: 0,
399 },
400 snapshot,
401 allowed,
402 )?
403 };
404 security.database.secure_rows_for(
405 &security.table,
406 rows,
407 effective_principal,
408 )
409 },
410 )
411 .map_err(core_err)?
412 };
413 if projection.is_some_and(Vec::is_empty) {
414 return Ok(Arc::new(scan::MongrelScanExec::new_row_count(rows.len())));
415 }
416 let batch = arrow_conv::rows_to_batch(&rows, &self.core_schema)
417 .map_err(|error| DataFusionError::External(Box::new(error)))?;
418 let batch = match projection {
419 Some(projection) => batch.project(projection).map_err(|error| {
420 DataFusionError::External(Box::new(MongrelQueryError::Arrow(error.to_string())))
421 })?,
422 None => batch,
423 };
424 let schema = batch.schema();
425 let statistics = (0..batch.num_columns())
426 .map(|_| scan::to_col_statistics(None))
427 .collect();
428 return Ok(Arc::new(scan::MongrelScanExec::new_batch(
429 schema, batch, statistics,
430 )));
431 }
432 let mut db = self.db.lock();
433 db.require_select().map_err(core_err)?;
437 let historical = self.snapshot.is_some();
438 let snap = self.snapshot.unwrap_or_else(|| db.snapshot());
439 let schema_ref = db.schema().clone();
440
441 let translated: Vec<mongreldb_core::Condition> = if historical {
443 Vec::new()
444 } else {
445 filters
446 .iter()
447 .filter_map(|f| {
448 translate_filter(f, &schema_ref)
449 .or_else(|| translate_ann_search(f, &schema_ref))
450 .or_else(|| translate_sparse_match(f, &schema_ref))
451 })
452 .collect()
453 };
454
455 if !translated.is_empty() {
458 db.ensure_indexes_complete().map_err(core_err)?;
459 }
460
461 let empty_proj = projection.map(|p| p.is_empty()).unwrap_or(false);
465 if empty_proj {
466 let total: usize = if historical {
467 db.visible_rows(snap).map_err(core_err)?.len()
468 } else if translated.is_empty() {
469 mongreldb_core::trace::QueryTrace::record(|t| {
470 t.scan_mode = mongreldb_core::trace::ScanMode::CountMetadata;
471 });
472 db.count() as usize
473 } else if let Some(count) = db.count_conditions(&translated, snap).map_err(core_err)? {
474 count as usize
475 } else {
476 match schema_ref.columns.first() {
477 Some(cdef) => {
478 let one = [cdef.id];
479 let cols = match db
480 .query_columns_native_cached(&translated, Some(&one), snap)
481 .map_err(core_err)?
482 {
483 Some(c) => c,
484 None => db
485 .visible_columns_native(snap, Some(&one))
486 .map_err(core_err)?,
487 };
488 mongreldb_core::trace::QueryTrace::record(|t| {
489 t.scan_mode = mongreldb_core::trace::ScanMode::Materialized;
490 });
491 cols.first().map(|(_, c)| c.len()).unwrap_or(0)
492 }
493 None => 0,
494 }
495 };
496 return Ok(Arc::new(scan::MongrelScanExec::new_row_count(total)));
497 }
498
499 let (col_ids, scan_schema): (Vec<u16>, SchemaRef) = match projection {
504 Some(p) if !p.is_empty() => {
505 let ids = p.iter().map(|&idx| schema_ref.columns[idx].id).collect();
506 let fields: Vec<arrow::datatypes::Field> = p
507 .iter()
508 .map(|&idx| self.schema.field(idx).clone())
509 .collect();
510 (ids, Arc::new(arrow::datatypes::Schema::new(fields)))
511 }
512 _ => (
513 schema_ref.columns.iter().map(|c| c.id).collect(),
514 self.schema.clone(),
515 ),
516 };
517
518 let mut proj_pairs: Vec<(u16, mongreldb_core::schema::TypeId)> =
520 Vec::with_capacity(col_ids.len());
521 let mut types: Vec<mongreldb_core::schema::TypeId> = Vec::with_capacity(col_ids.len());
522 for cid in &col_ids {
523 let ty = schema_ref
524 .columns
525 .iter()
526 .find(|c| c.id == *cid)
527 .map(|c| c.ty.clone())
528 .ok_or_else(|| {
529 DataFusionError::External(Box::new(MongrelQueryError::Arrow(format!(
530 "unknown column {cid}"
531 ))))
532 })?;
533 proj_pairs.push((*cid, ty.clone()));
534 types.push(ty);
535 }
536
537 let col_stats_map = if !historical && translated.is_empty() {
541 db.exact_column_stats(snap, &col_ids).map_err(core_err)?
542 } else {
543 None
544 };
545 let column_stats: Vec<datafusion::physical_plan::ColumnStatistics> = col_ids
546 .iter()
547 .map(|cid| scan::to_col_statistics(col_stats_map.as_ref().and_then(|m| m.get(cid))))
548 .collect();
549
550 if !historical
554 && translated.is_empty()
555 && db.run_count() == 1
556 && db.memtable_is_empty()
557 && db.mutable_run_len() == 0
558 && db.single_run_is_clean()
559 {
560 let shadow = shadow::ArrowShadow::new(db.dir());
561 let run_ids: HashSet<u128> = db.run_ids().into_iter().collect();
562 shadow.sweep(&run_ids);
563 if let Some(&run_id) = run_ids.iter().next() {
564 if let Some(batch) = shadow.try_read(run_id) {
565 if let Some(projected) =
566 project_batch(&batch, &col_ids, &schema_ref, &scan_schema)
567 {
568 mongreldb_core::trace::QueryTrace::record(|t| {
569 t.scan_mode = mongreldb_core::trace::ScanMode::ArrowShadow;
570 });
571 return Ok(Arc::new(scan::MongrelScanExec::new_batch(
572 scan_schema,
573 projected,
574 column_stats,
575 )));
576 }
577 }
578 }
579 }
580
581 let cursor: Option<Box<dyn Cursor>> = db
588 .scan_cursor(snap, proj_pairs, &translated)
589 .map_err(core_err)?;
590 if let Some(cursor) = cursor {
591 let num_rows = cursor.remaining_rows();
592 let residual = extract_residual_filter(filters, &col_ids, &schema_ref);
594 return Ok(Arc::new(scan::MongrelScanExec::new_cursor(
595 scan_schema,
596 types,
597 cursor,
598 num_rows,
599 column_stats,
600 residual,
601 )));
602 }
603
604 let cols = if !translated.is_empty() {
607 match db
608 .query_columns_native_cached(&translated, Some(&col_ids), snap)
609 .map_err(core_err)?
610 {
611 Some(c) => c,
612 None => db
613 .visible_columns_native(snap, Some(&col_ids))
614 .map_err(core_err)?,
615 }
616 } else {
617 db.visible_columns_native(snap, Some(&col_ids))
618 .map_err(core_err)?
619 };
620
621 let mut ordered: Vec<mongreldb_core::columnar::NativeColumn> =
623 Vec::with_capacity(col_ids.len());
624 for cid in &col_ids {
625 let col = cols
626 .iter()
627 .find(|(id, _)| id == cid)
628 .map(|(_, c)| c.clone())
629 .ok_or_else(|| {
630 DataFusionError::External(Box::new(MongrelQueryError::Arrow(format!(
631 "missing column {cid}"
632 ))))
633 })?;
634 ordered.push(col);
635 }
636 let num_rows = ordered.first().map(|c| c.len()).unwrap_or(0);
637
638 let shadow_write: Option<(
640 std::path::PathBuf,
641 u128,
642 Vec<arrow::array::ArrayRef>,
643 SchemaRef,
644 )> = if !historical
645 && translated.is_empty()
646 && db.run_count() == 1
647 && db.memtable_is_empty()
648 && db.mutable_run_len() == 0
649 && db.single_run_is_clean()
650 {
651 let all_schema_ids: Vec<u16> = schema_ref.columns.iter().map(|c| c.id).collect();
652 if col_ids == all_schema_ids {
653 let dir = db.dir().to_path_buf();
654 let run_id = db.run_ids().first().copied();
655 run_id.map(|rid| {
656 let arrays = ordered
657 .iter()
658 .zip(types.iter())
659 .map(|(col, ty)| arrow_conv::native_to_array(ty.clone(), col))
660 .collect::<Result<_>>()
661 .unwrap_or_default();
662 (dir, rid, arrays, scan_schema.clone())
663 })
664 } else {
665 None
666 }
667 } else {
668 None
669 };
670
671 drop(db);
672
673 if let Some((dir, run_id, arrays, schema)) = shadow_write {
676 if let Ok(batch) = RecordBatch::try_new(schema, arrays) {
677 shadow::ArrowShadow::new(&dir).write(run_id, &batch);
678 }
679 }
680
681 mongreldb_core::trace::QueryTrace::record(|t| {
682 t.scan_mode = mongreldb_core::trace::ScanMode::Materialized;
683 t.row_materialized = true;
684 });
685 Ok(Arc::new(scan::MongrelScanExec::new(
686 scan_schema,
687 ordered,
688 types,
689 num_rows,
690 column_stats,
691 )))
692 }
693}
694
695fn project_batch(
699 batch: &RecordBatch,
700 col_ids: &[u16],
701 schema_ref: &mongreldb_core::schema::Schema,
702 scan_schema: &arrow::datatypes::SchemaRef,
703) -> Option<RecordBatch> {
704 let arrays: Vec<arrow::array::ArrayRef> = col_ids
706 .iter()
707 .map(|cid| {
708 let name = schema_ref
710 .columns
711 .iter()
712 .find(|c| c.id == *cid)
713 .map(|c| c.name.as_str())?;
714 let idx = batch.schema().index_of(name).ok()?;
716 Some(batch.column(idx).clone())
717 })
718 .collect::<Option<Vec<_>>>()?;
719 RecordBatch::try_new(scan_schema.clone(), arrays).ok()
720}
721
722pub(crate) fn translate_filter(
740 expr: &Expr,
741 schema: &mongreldb_core::Schema,
742) -> Option<mongreldb_core::Condition> {
743 use datafusion::common::ScalarValue;
744 use datafusion::logical_expr::{Between, BinaryExpr, Like, Operator};
745 use mongreldb_core::{ColumnFlags, Condition, IndexKind, TypeId, Value};
746
747 let int_val = |s: &ScalarValue| match s {
751 ScalarValue::Int8(Some(v)) => Some(*v as i64),
752 ScalarValue::Int16(Some(v)) => Some(*v as i64),
753 ScalarValue::Int32(Some(v)) => Some(*v as i64),
754 ScalarValue::Int64(Some(v)) => Some(*v),
755 ScalarValue::UInt8(Some(v)) => Some(*v as i64),
756 ScalarValue::UInt16(Some(v)) => Some(*v as i64),
757 ScalarValue::UInt32(Some(v)) => Some(*v as i64),
758 ScalarValue::UInt64(Some(v)) => Some(*v as i64),
759 ScalarValue::Date32(Some(v)) => Some(*v as i64),
760 ScalarValue::TimestampSecond(Some(v), _) => Some(*v),
761 ScalarValue::TimestampMillisecond(Some(v), _) => Some(*v),
762 ScalarValue::TimestampMicrosecond(Some(v), _) => Some(*v),
763 ScalarValue::TimestampNanosecond(Some(v), _) => Some(*v),
764 _ => None,
765 };
766 let float_val = |s: &ScalarValue| match s {
767 ScalarValue::Float32(Some(f)) => Some(*f as f64),
768 ScalarValue::Float64(Some(f)) => Some(*f),
769 _ => None,
770 };
771 let bytes_val = |s: &ScalarValue| match s {
772 ScalarValue::Utf8(Some(s)) => Some(s.as_bytes().to_vec()),
773 _ => None,
774 };
775 let _ = bytes_val; let val = |s: &ScalarValue| -> Option<Value> {
778 if let Some(i) = int_val(s) {
782 return Some(Value::Int64(i));
783 }
784 match s {
785 ScalarValue::Utf8(Some(s)) => Some(Value::Bytes(s.as_bytes().to_vec())),
786 ScalarValue::Float32(Some(f)) => Some(Value::Float64(*f as f64)),
787 ScalarValue::Float64(Some(f)) => Some(Value::Float64(*f)),
788 ScalarValue::Boolean(Some(b)) => Some(Value::Bool(*b)),
789 _ => None,
790 }
791 };
792
793 let col_def = |name: &str| schema.columns.iter().find(|c| c.name == name);
794 let has_fm = |cid: u16| {
795 schema
796 .indexes
797 .iter()
798 .any(|i| i.column_id == cid && i.kind == IndexKind::FmIndex)
799 };
800 let has_bitmap = |cid: u16| {
801 schema
802 .indexes
803 .iter()
804 .any(|i| i.column_id == cid && i.kind == IndexKind::Bitmap)
805 };
806
807 match expr {
808 Expr::BinaryExpr(BinaryExpr { left, op, right }) => {
811 if *op == Operator::Or {
813 return try_or_as_bitmap_in(expr, schema);
814 }
815 let left = peel_cast(left);
817 let right = peel_cast(right);
818 let (col_name, scalar, flipped) = match (left.as_ref(), right.as_ref()) {
819 (Expr::Column(c), Expr::Literal(s, _)) => (&c.name, s, false),
820 (Expr::Literal(s, _), Expr::Column(c)) => (&c.name, s, true),
821 _ => return None,
822 };
823 let op = if flipped { flip_op(*op)? } else { *op };
824 let cdef = col_def(col_name)?;
825
826 if op == Operator::Eq {
828 let v = val(scalar)?;
829 if has_bitmap(cdef.id) {
830 return Some(Condition::BitmapEq {
831 column_id: cdef.id,
832 value: v.encode_key(),
833 });
834 }
835 if cdef.flags.contains(ColumnFlags::PRIMARY_KEY) {
836 return Some(Condition::Pk(v.encode_key()));
837 }
838 return None;
839 }
840
841 match cdef.ty {
844 TypeId::Int8
845 | TypeId::Int16
846 | TypeId::Int32
847 | TypeId::Int64
848 | TypeId::UInt8
849 | TypeId::UInt16
850 | TypeId::UInt32
851 | TypeId::UInt64
852 | TypeId::TimestampNanos
853 | TypeId::Date32 => {
854 let v = int_val(scalar)?;
855 let (lo, hi) = int_bounds(op, v)?;
856 Some(Condition::Range {
857 column_id: cdef.id,
858 lo,
859 hi,
860 })
861 }
862 TypeId::Float32 | TypeId::Float64 => {
863 let v = float_val(scalar)?;
864 let (lo, lo_inc, hi, hi_inc) = float_bounds(op, v)?;
865 Some(Condition::RangeF64 {
866 column_id: cdef.id,
867 lo,
868 lo_inclusive: lo_inc,
869 hi,
870 hi_inclusive: hi_inc,
871 })
872 }
873 _ => None,
874 }
875 }
876
877 Expr::Between(Between {
879 expr,
880 negated,
881 low,
882 high,
883 }) => {
884 if *negated {
885 return None;
886 }
887 let Expr::Column(c) = expr.as_ref() else {
888 return None;
889 };
890 let cdef = col_def(&c.name)?;
891 let (lo_s, hi_s) = match (low.as_ref(), high.as_ref()) {
892 (Expr::Literal(lo, _), Expr::Literal(hi, _)) => (lo, hi),
893 _ => return None,
894 };
895 match cdef.ty {
896 TypeId::Int8
897 | TypeId::Int16
898 | TypeId::Int32
899 | TypeId::Int64
900 | TypeId::UInt8
901 | TypeId::UInt16
902 | TypeId::UInt32
903 | TypeId::UInt64
904 | TypeId::TimestampNanos
905 | TypeId::Date32 => {
906 let (Some(lo), Some(hi)) = (int_val(lo_s), int_val(hi_s)) else {
907 return None;
908 };
909 Some(Condition::Range {
910 column_id: cdef.id,
911 lo,
912 hi,
913 })
914 }
915 TypeId::Float32 | TypeId::Float64 => {
916 let (Some(lo), Some(hi)) = (float_val(lo_s), float_val(hi_s)) else {
917 return None;
918 };
919 Some(Condition::RangeF64 {
920 column_id: cdef.id,
921 lo,
922 lo_inclusive: true,
923 hi,
924 hi_inclusive: true,
925 })
926 }
927 _ => None,
928 }
929 }
930
931 Expr::Like(Like {
933 negated,
934 expr,
935 pattern,
936 ..
937 }) => {
938 if *negated {
939 return None;
940 }
941 let Expr::Column(c) = expr.as_ref() else {
942 return None;
943 };
944 let Expr::Literal(ScalarValue::Utf8(Some(pat)), _) = pattern.as_ref() else {
945 return None;
946 };
947 let cdef = col_def(&c.name)?;
948 if has_bitmap(cdef.id) {
952 if let Some(prefix) = anchored_like_prefix(pat) {
953 return Some(Condition::BytesPrefix {
954 column_id: cdef.id,
955 prefix: mongreldb_core::Value::Bytes(prefix.as_bytes().to_vec())
956 .encode_key(),
957 });
958 }
959 }
960 if !has_fm(cdef.id) {
961 return None;
962 }
963 let segments: Vec<Vec<u8>> = pat
967 .split(['%', '_'])
968 .filter(|s| s.len() >= 3)
969 .map(|s| s.as_bytes().to_vec())
970 .collect();
971 match segments.len() {
972 0 => longest_like_segment(pat).map(|seg| Condition::FmContains {
973 column_id: cdef.id,
974 pattern: seg,
975 }),
976 1 => Some(Condition::FmContains {
977 column_id: cdef.id,
978 pattern: segments.into_iter().next().unwrap(),
979 }),
980 _ => Some(Condition::FmContainsAll {
981 column_id: cdef.id,
982 patterns: segments,
983 }),
984 }
985 }
986
987 Expr::InList(il) if !il.negated => {
991 let Expr::Column(c) = il.expr.as_ref() else {
992 return None;
993 };
994 let cdef = col_def(&c.name)?;
995 if !has_bitmap(cdef.id) {
996 return None;
997 }
998 let values: Vec<Vec<u8>> = il
999 .list
1000 .iter()
1001 .filter_map(|e| match e {
1002 Expr::Literal(s, _) => val(s).map(|v| v.encode_key()),
1003 _ => None,
1004 })
1005 .collect();
1006 if values.is_empty() || values.len() != il.list.len() {
1007 return None;
1008 }
1009 Some(Condition::BitmapIn {
1010 column_id: cdef.id,
1011 values,
1012 })
1013 }
1014
1015 Expr::IsNull(inner) => {
1017 let col_name = match inner.as_ref() {
1018 Expr::Column(c) => &c.name,
1019 _ => return None,
1020 };
1021 let cdef = col_def(col_name)?;
1022 Some(Condition::IsNull { column_id: cdef.id })
1023 }
1024
1025 Expr::IsNotNull(inner) => {
1027 let col_name = match inner.as_ref() {
1028 Expr::Column(c) => &c.name,
1029 _ => return None,
1030 };
1031 let cdef = col_def(col_name)?;
1032 Some(Condition::IsNotNull { column_id: cdef.id })
1033 }
1034
1035 _ => None,
1036 }
1037}
1038
1039pub(crate) fn extract_residual_filter(
1043 filters: &[Expr],
1044 col_ids: &[u16],
1045 schema: &mongreldb_core::Schema,
1046) -> Option<std::sync::Arc<scan::ResidualFilter>> {
1047 use datafusion::common::ScalarValue;
1048 use datafusion::logical_expr::Like;
1049 for f in filters {
1050 if let Expr::Like(Like {
1051 negated: false,
1052 expr,
1053 pattern,
1054 ..
1055 }) = f
1056 {
1057 let Expr::Column(c) = expr.as_ref() else {
1058 continue;
1059 };
1060 let Expr::Literal(ScalarValue::Utf8(Some(pat)), _) = pattern.as_ref() else {
1061 continue;
1062 };
1063 let cdef = schema.columns.iter().find(|col| col.name == c.name)?;
1064 let col_idx = col_ids.iter().position(|&id| id == cdef.id)?;
1065 return Some(std::sync::Arc::new(scan::ResidualFilter::new(
1066 col_idx,
1067 pat.as_bytes().to_vec(),
1068 )));
1069 }
1070 }
1071 None
1072}
1073
1074pub(crate) fn translate_ann_search(
1080 expr: &Expr,
1081 schema: &mongreldb_core::Schema,
1082) -> Option<mongreldb_core::Condition> {
1083 use datafusion::common::ScalarValue;
1084 use mongreldb_core::Condition;
1085
1086 let Expr::ScalarFunction(sf) = expr else {
1087 return None;
1088 };
1089 if !sf.func.name().eq_ignore_ascii_case("ann_search") || sf.args.len() != 3 {
1090 return None;
1091 }
1092 let (Expr::Column(c), query_expr, k_expr) = (&sf.args[0], &sf.args[1], &sf.args[2]) else {
1093 return None;
1094 };
1095 let cdef = schema.columns.iter().find(|col| col.name == c.name)?;
1096 let json = match query_expr {
1097 Expr::Literal(ScalarValue::Utf8(Some(s)), _) => s.as_str(),
1098 _ => return None,
1099 };
1100 let k: usize = match k_expr {
1101 Expr::Literal(scalar, _) => match scalar {
1102 ScalarValue::Int64(Some(k)) => usize::try_from(*k).ok()?,
1103 ScalarValue::UInt64(Some(k)) => usize::try_from(*k).ok()?,
1104 ScalarValue::Int32(Some(k)) => usize::try_from(*k).ok()?,
1105 _ => return None,
1106 },
1107 _ => return None,
1108 };
1109 let query: Vec<f32> = serde_json::from_str(json).ok()?;
1110 Some(Condition::Ann {
1111 column_id: cdef.id,
1112 query,
1113 k,
1114 })
1115}
1116
1117pub(crate) fn translate_sparse_match(
1122 expr: &Expr,
1123 schema: &mongreldb_core::Schema,
1124) -> Option<mongreldb_core::Condition> {
1125 use datafusion::common::ScalarValue;
1126 use mongreldb_core::Condition;
1127
1128 let Expr::ScalarFunction(sf) = expr else {
1129 return None;
1130 };
1131 if !sf.func.name().eq_ignore_ascii_case("sparse_match") || sf.args.len() != 3 {
1132 return None;
1133 }
1134 let (Expr::Column(c), query_expr, k_expr) = (&sf.args[0], &sf.args[1], &sf.args[2]) else {
1135 return None;
1136 };
1137 let cdef = schema.columns.iter().find(|col| col.name == c.name)?;
1138 let json = match query_expr {
1139 Expr::Literal(ScalarValue::Utf8(Some(s)), _) => s.as_str(),
1140 _ => return None,
1141 };
1142 let k: usize = match k_expr {
1143 Expr::Literal(scalar, _) => match scalar {
1144 ScalarValue::Int64(Some(k)) => usize::try_from(*k).ok()?,
1145 ScalarValue::UInt64(Some(k)) => usize::try_from(*k).ok()?,
1146 ScalarValue::Int32(Some(k)) => usize::try_from(*k).ok()?,
1147 _ => return None,
1148 },
1149 _ => return None,
1150 };
1151 let query: Vec<(u32, f32)> = serde_json::from_str(json).ok()?;
1152 Some(Condition::SparseMatch {
1153 column_id: cdef.id,
1154 query,
1155 k,
1156 })
1157}
1158
1159fn flip_op(op: datafusion::logical_expr::Operator) -> Option<datafusion::logical_expr::Operator> {
1161 use datafusion::logical_expr::Operator;
1162 Some(match op {
1163 Operator::Eq => Operator::Eq,
1164 Operator::Lt => Operator::Gt,
1165 Operator::Gt => Operator::Lt,
1166 Operator::LtEq => Operator::GtEq,
1167 Operator::GtEq => Operator::LtEq,
1168 _ => return None,
1169 })
1170}
1171
1172fn int_bounds(op: datafusion::logical_expr::Operator, v: i64) -> Option<(i64, i64)> {
1175 use datafusion::logical_expr::Operator;
1176 Some(match op {
1177 Operator::Gt => (v.saturating_add(1), i64::MAX),
1178 Operator::GtEq => (v, i64::MAX),
1179 Operator::Lt => (i64::MIN, v.saturating_sub(1)),
1180 Operator::LtEq => (i64::MIN, v),
1181 _ => return None,
1182 })
1183}
1184
1185fn float_bounds(op: datafusion::logical_expr::Operator, v: f64) -> Option<(f64, bool, f64, bool)> {
1187 use datafusion::logical_expr::Operator;
1188 Some(match op {
1189 Operator::Gt => (v, false, f64::INFINITY, false),
1190 Operator::GtEq => (v, true, f64::INFINITY, false),
1191 Operator::Lt => (f64::NEG_INFINITY, false, v, false),
1192 Operator::LtEq => (f64::NEG_INFINITY, false, v, true),
1193 _ => return None,
1194 })
1195}
1196
1197fn longest_like_segment(pat: &str) -> Option<Vec<u8>> {
1204 pat.split(['%', '_'])
1205 .map(|s| s.as_bytes())
1206 .max_by_key(|s| s.len())
1207 .filter(|s| !s.is_empty())
1208 .map(|s| s.to_vec())
1209}
1210
1211fn anchored_like_prefix(pat: &str) -> Option<&str> {
1216 let rest = pat.strip_suffix('%')?;
1217 if rest.is_empty() || rest.contains(['%', '_']) {
1218 return None;
1219 }
1220 Some(rest)
1221}
1222
1223fn peel_cast(expr: &Expr) -> std::borrow::Cow<'_, Expr> {
1227 match expr {
1228 Expr::Cast(datafusion::logical_expr::Cast { expr, .. }) => std::borrow::Cow::Borrowed(expr),
1229 _ => std::borrow::Cow::Borrowed(expr),
1230 }
1231}
1232
1233fn try_or_as_bitmap_in(
1238 expr: &Expr,
1239 schema: &mongreldb_core::Schema,
1240) -> Option<mongreldb_core::Condition> {
1241 use datafusion::logical_expr::{BinaryExpr, Operator};
1242 let mut values: Vec<Vec<u8>> = Vec::new();
1243 let mut target_col: Option<u16> = None;
1244 let mut stack = vec![expr];
1245 while let Some(e) = stack.pop() {
1246 match e {
1247 Expr::BinaryExpr(BinaryExpr {
1248 left,
1249 op: Operator::Or,
1250 right,
1251 }) => {
1252 stack.push(left);
1253 stack.push(right);
1254 }
1255 Expr::BinaryExpr(BinaryExpr {
1256 left,
1257 op: Operator::Eq,
1258 right,
1259 }) => {
1260 let (col_name, scalar) = match (left.as_ref(), right.as_ref()) {
1261 (Expr::Column(c), Expr::Literal(s, _)) => (&c.name, s),
1262 (Expr::Literal(s, _), Expr::Column(c)) => (&c.name, s),
1263 _ => return None,
1264 };
1265 let cdef = schema.columns.iter().find(|c| &c.name == col_name)?;
1266 if !schema
1267 .indexes
1268 .iter()
1269 .any(|i| i.column_id == cdef.id && i.kind == mongreldb_core::IndexKind::Bitmap)
1270 {
1271 return None;
1272 }
1273 match target_col {
1274 None => target_col = Some(cdef.id),
1275 Some(id) if id != cdef.id => return None,
1276 _ => {}
1277 }
1278 let v = match scalar {
1279 datafusion::common::ScalarValue::Int64(Some(v)) => {
1280 mongreldb_core::Value::Int64(*v)
1281 }
1282 datafusion::common::ScalarValue::Utf8(Some(s)) => {
1283 mongreldb_core::Value::Bytes(s.as_bytes().to_vec())
1284 }
1285 datafusion::common::ScalarValue::Float64(Some(f)) => {
1286 mongreldb_core::Value::Float64(*f)
1287 }
1288 datafusion::common::ScalarValue::Boolean(Some(b)) => {
1289 mongreldb_core::Value::Bool(*b)
1290 }
1291 _ => return None,
1292 };
1293 values.push(v.encode_key());
1294 }
1295 _ => return None,
1296 }
1297 }
1298 let col_id = target_col?;
1299 if values.is_empty() {
1300 return None;
1301 }
1302 Some(mongreldb_core::Condition::BitmapIn {
1303 column_id: col_id,
1304 values,
1305 })
1306}
1307
1308fn sp_ident_name(expr: &sqlparser::ast::Expr) -> Option<&str> {
1315 use sqlparser::ast::Expr;
1316 match expr {
1317 Expr::Identifier(ident) => Some(ident.value.as_str()),
1318 Expr::CompoundIdentifier(idents) => idents.last().map(|i| i.value.as_str()),
1319 _ => None,
1320 }
1321}
1322
1323fn sp_literal(expr: &sqlparser::ast::Expr) -> Option<mongreldb_core::Value> {
1326 use sqlparser::ast::Expr;
1327 let v = match expr {
1328 Expr::Value(v) => v,
1329 _ => return None,
1330 };
1331 use sqlparser::ast::Value as SpValue;
1332 match &v.value {
1333 SpValue::Number(s, _) => s
1334 .parse::<i64>()
1335 .map(mongreldb_core::Value::Int64)
1336 .or_else(|_| s.parse::<f64>().map(mongreldb_core::Value::Float64))
1337 .ok(),
1338 SpValue::SingleQuotedString(s) => Some(mongreldb_core::Value::Bytes(s.as_bytes().to_vec())),
1339 SpValue::Boolean(b) => Some(mongreldb_core::Value::Bool(*b)),
1340 _ => None,
1341 }
1342}
1343
1344fn is_int_ty(ty: mongreldb_core::schema::TypeId) -> bool {
1345 use mongreldb_core::schema::TypeId::*;
1346 matches!(
1347 ty,
1348 Int8 | Int16 | Int32 | Int64 | UInt8 | UInt16 | UInt32 | UInt64 | TimestampNanos | Date32
1349 )
1350}
1351
1352fn is_float_ty(ty: mongreldb_core::schema::TypeId) -> bool {
1353 matches!(
1354 ty,
1355 mongreldb_core::schema::TypeId::Float32 | mongreldb_core::schema::TypeId::Float64
1356 )
1357}
1358
1359fn translate_sqlparser_predicate(
1362 expr: &sqlparser::ast::Expr,
1363 schema: &mongreldb_core::Schema,
1364) -> Option<mongreldb_core::Condition> {
1365 use mongreldb_core::{schema::ColumnFlags, Condition, IndexKind, Value};
1366 use sqlparser::ast::{BinaryOperator, Expr};
1367
1368 let col_def = |name: &str| schema.columns.iter().find(|c| c.name == name);
1369 let has_bitmap = |cid: u16| {
1370 schema
1371 .indexes
1372 .iter()
1373 .any(|i| i.column_id == cid && i.kind == IndexKind::Bitmap)
1374 };
1375
1376 match expr {
1377 Expr::BinaryOp {
1379 left,
1380 op: BinaryOperator::Or,
1381 right,
1382 } => {
1383 let mut values: Vec<Vec<u8>> = Vec::new();
1384 let mut target: Option<u16> = None;
1385 let mut stack: Vec<&Expr> = vec![left.as_ref(), right.as_ref()];
1386 while let Some(e) = stack.pop() {
1387 match e {
1388 Expr::BinaryOp {
1389 left,
1390 op: BinaryOperator::Or,
1391 right,
1392 } => {
1393 stack.push(left.as_ref());
1394 stack.push(right.as_ref());
1395 }
1396 Expr::BinaryOp {
1397 left,
1398 op: BinaryOperator::Eq,
1399 right,
1400 } => {
1401 let (name, lit) = match (left.as_ref(), right.as_ref()) {
1402 (l, r) if sp_ident_name(l).is_some() && sp_literal(r).is_some() => {
1403 (l, r)
1404 }
1405 (l, r) if sp_ident_name(r).is_some() && sp_literal(l).is_some() => {
1406 (r, l)
1407 }
1408 _ => return None,
1409 };
1410 let cdef = col_def(sp_ident_name(name)?)?;
1411 if !has_bitmap(cdef.id) {
1412 return None;
1413 }
1414 match target {
1415 None => target = Some(cdef.id),
1416 Some(id) if id != cdef.id => return None,
1417 _ => {}
1418 }
1419 values.push(sp_literal(lit)?.encode_key());
1420 }
1421 _ => return None,
1422 }
1423 }
1424 let cid = target?;
1425 (!values.is_empty()).then_some(Condition::BitmapIn {
1426 column_id: cid,
1427 values,
1428 })
1429 }
1430 Expr::BinaryOp { left, op, right } => {
1432 let flipped;
1433 let (col_expr, lit_expr) = match (
1434 sp_ident_name(left),
1435 sp_literal(right),
1436 sp_ident_name(right),
1437 sp_literal(left),
1438 ) {
1439 (Some(_), Some(_), _, _) => {
1440 flipped = false;
1441 (left.as_ref(), right.as_ref())
1442 }
1443 (_, _, Some(_), Some(_)) => {
1444 flipped = true;
1445 (right.as_ref(), left.as_ref())
1446 }
1447 _ => return None,
1448 };
1449 let name = sp_ident_name(col_expr)?;
1450 let cdef = col_def(name)?;
1451 let v = sp_literal(lit_expr)?;
1452 use sqlparser::ast::BinaryOperator::*;
1453 match op {
1456 Eq => {
1457 if has_bitmap(cdef.id) {
1458 Some(Condition::BitmapEq {
1459 column_id: cdef.id,
1460 value: v.encode_key(),
1461 })
1462 } else if cdef.flags.contains(ColumnFlags::PRIMARY_KEY) {
1463 Some(Condition::Pk(v.encode_key()))
1464 } else {
1465 None
1466 }
1467 }
1468 Lt | LtEq | Gt | GtEq if is_int_ty(cdef.ty.clone()) => {
1469 let n = match v {
1470 Value::Int64(n) => n,
1471 _ => return None,
1472 };
1473 let (lo, hi) = match (flipped, op) {
1475 (false, Lt) | (true, Gt) => (i64::MIN, n.saturating_sub(1)),
1476 (false, Gt) | (true, Lt) => (n.saturating_add(1), i64::MAX),
1477 (false, LtEq) | (true, GtEq) => (i64::MIN, n),
1478 (false, GtEq) | (true, LtEq) => (n, i64::MAX),
1479 _ => (i64::MIN, i64::MAX),
1480 };
1481 Some(Condition::Range {
1482 column_id: cdef.id,
1483 lo,
1484 hi,
1485 })
1486 }
1487 Lt | LtEq | Gt | GtEq if is_float_ty(cdef.ty.clone()) => {
1488 let f = match v {
1489 Value::Float64(f) => f,
1490 _ => return None,
1491 };
1492 let (lo, li, hi, hi_i) = match (flipped, op) {
1493 (false, Lt) | (true, Gt) => (f64::NEG_INFINITY, true, f, false),
1494 (false, Gt) | (true, Lt) => (f, false, f64::INFINITY, true),
1495 (false, LtEq) | (true, GtEq) => (f64::NEG_INFINITY, true, f, true),
1496 (false, GtEq) | (true, LtEq) => (f, true, f64::INFINITY, true),
1497 _ => (f64::NEG_INFINITY, true, f64::INFINITY, true),
1498 };
1499 Some(Condition::RangeF64 {
1500 column_id: cdef.id,
1501 lo,
1502 lo_inclusive: li,
1503 hi,
1504 hi_inclusive: hi_i,
1505 })
1506 }
1507 _ => None,
1508 }
1509 }
1510 Expr::Between {
1511 expr,
1512 negated,
1513 low,
1514 high,
1515 } if !negated => {
1516 let name = sp_ident_name(expr)?;
1517 let cdef = col_def(name)?;
1518 if is_int_ty(cdef.ty.clone()) {
1519 let lo = match sp_literal(low)? {
1520 Value::Int64(n) => n,
1521 _ => return None,
1522 };
1523 let hi = match sp_literal(high)? {
1524 Value::Int64(n) => n,
1525 _ => return None,
1526 };
1527 Some(Condition::Range {
1528 column_id: cdef.id,
1529 lo,
1530 hi,
1531 })
1532 } else if is_float_ty(cdef.ty.clone()) {
1533 let lo = match sp_literal(low)? {
1534 Value::Float64(f) => f,
1535 _ => return None,
1536 };
1537 let hi = match sp_literal(high)? {
1538 Value::Float64(f) => f,
1539 _ => return None,
1540 };
1541 Some(Condition::RangeF64 {
1542 column_id: cdef.id,
1543 lo,
1544 lo_inclusive: true,
1545 hi,
1546 hi_inclusive: true,
1547 })
1548 } else {
1549 None
1550 }
1551 }
1552 Expr::InList {
1553 expr,
1554 list,
1555 negated,
1556 } if !negated => {
1557 let name = sp_ident_name(expr)?;
1558 let cdef = col_def(name)?;
1559 if !has_bitmap(cdef.id) {
1560 return None;
1561 }
1562 let values: Vec<Vec<u8>> = list
1563 .iter()
1564 .map(|e| sp_literal(e).map(|v| v.encode_key()))
1565 .collect::<Option<_>>()?;
1566 (!values.is_empty()).then_some(Condition::BitmapIn {
1567 column_id: cdef.id,
1568 values,
1569 })
1570 }
1571 Expr::IsNull(inner) => {
1574 let cid = col_def(sp_ident_name(inner)?)?.id;
1575 Some(Condition::IsNull { column_id: cid })
1576 }
1577 Expr::IsNotNull(inner) => {
1578 let cid = col_def(sp_ident_name(inner)?)?.id;
1579 Some(Condition::IsNotNull { column_id: cid })
1580 }
1581 _ => None,
1582 }
1583}
1584
1585fn translate_sqlparser_filter(
1588 expr: &sqlparser::ast::Expr,
1589 schema: &mongreldb_core::Schema,
1590) -> Option<Vec<mongreldb_core::Condition>> {
1591 use sqlparser::ast::{BinaryOperator, Expr};
1592 let mut out = Vec::new();
1593 let mut stack = vec![expr];
1594 while let Some(e) = stack.pop() {
1595 match e {
1596 Expr::BinaryOp {
1597 left,
1598 op: BinaryOperator::And,
1599 right,
1600 } => {
1601 stack.push(left.as_ref());
1602 stack.push(right.as_ref());
1603 }
1604 other => out.push(translate_sqlparser_predicate(other, schema)?),
1605 }
1606 }
1607 Some(out)
1608}
1609
1610pub struct MongrelSession {
1614 ctx: SessionContext,
1615 db: Option<TableHandle>,
1616 database: Option<Arc<Database>>,
1620 principal: Option<mongreldb_core::Principal>,
1621 principal_catalog_bound: bool,
1622 cache: ResultCache,
1623 plan_cache: parking_lot::Mutex<BoundedLru<String, datafusion::logical_expr::LogicalPlan>>,
1625 tables: scored_sql::TableMap,
1627 views: parking_lot::Mutex<HashMap<String, ViewDef>>,
1631 attached_databases: parking_lot::Mutex<HashMap<String, Arc<Database>>>,
1635 sql_txn: parking_lot::Mutex<Option<Vec<commands::PendingSqlOp>>>,
1639 sql_txn_aborted: parking_lot::Mutex<bool>,
1641 savepoints: parking_lot::Mutex<Vec<(String, usize)>>,
1644 sql_fn_state: Arc<extended_sql_functions::ExtendedSqlState>,
1646 external_modules: Arc<ExternalModuleRegistry>,
1649 scored_runtime: Arc<scored_sql::ScoredRuntime>,
1650 prepared_names: parking_lot::Mutex<std::collections::HashSet<String>>,
1654 query_registry: Arc<SqlQueryRegistry>,
1655 execution_gate: Arc<tokio::sync::Semaphore>,
1656 statement_timeout: parking_lot::RwLock<Option<std::time::Duration>>,
1657 test_hook: parking_lot::Mutex<Option<SqlTestHook>>,
1658}
1659
1660type CacheKey = (String, u64);
1662type ResultCache = parking_lot::Mutex<BoundedLru<CacheKey, Arc<Vec<RecordBatch>>>>;
1663
1664const SESSION_CACHE_MAX_ENTRIES: usize = 1024;
1665
1666struct BoundedLru<K, V> {
1667 entries: HashMap<K, (V, u64)>,
1668 clock: u64,
1669 capacity: usize,
1670}
1671
1672impl<K: Clone + Eq + Hash, V> BoundedLru<K, V> {
1673 fn new(capacity: usize) -> Self {
1674 Self {
1675 entries: HashMap::new(),
1676 clock: 0,
1677 capacity: capacity.max(1),
1678 }
1679 }
1680
1681 fn get<Q>(&mut self, key: &Q) -> Option<&V>
1682 where
1683 K: Borrow<Q>,
1684 Q: Eq + Hash + ?Sized,
1685 {
1686 self.clock = self.clock.wrapping_add(1);
1687 let (value, last_used) = self.entries.get_mut(key)?;
1688 *last_used = self.clock;
1689 Some(value)
1690 }
1691
1692 fn insert(&mut self, key: K, value: V) {
1693 self.clock = self.clock.wrapping_add(1);
1694 if let Some(entry) = self.entries.get_mut(&key) {
1695 *entry = (value, self.clock);
1696 return;
1697 }
1698 if self.entries.len() >= self.capacity {
1699 if let Some(oldest) = self
1701 .entries
1702 .iter()
1703 .min_by_key(|(_, (_, last_used))| *last_used)
1704 .map(|(key, _)| key.clone())
1705 {
1706 self.entries.remove(&oldest);
1707 }
1708 }
1709 self.entries.insert(key, (value, self.clock));
1710 }
1711
1712 fn clear(&mut self) {
1713 self.entries.clear();
1714 self.clock = 0;
1715 }
1716}
1717
1718fn buffered_stream(batches: Vec<RecordBatch>) -> MongrelRecordBatchStream {
1719 let schema = batches
1720 .first()
1721 .map(RecordBatch::schema)
1722 .unwrap_or_else(|| Arc::new(arrow::datatypes::Schema::empty()));
1723 let stream = futures::stream::iter(batches.into_iter().map(Ok::<RecordBatch, DataFusionError>));
1724 Box::pin(RecordBatchStreamAdapter::new(schema, stream))
1725}
1726
1727pub struct ManagedQueryBatches {
1730 batches: Vec<RecordBatch>,
1731 query: RegisteredSqlQuery,
1732 guard: Option<RegisteredQueryGuard>,
1733 permit: Option<tokio::sync::OwnedSemaphorePermit>,
1734}
1735
1736impl ManagedQueryBatches {
1737 pub fn batches(&self) -> &[RecordBatch] {
1738 &self.batches
1739 }
1740
1741 pub fn query(&self) -> &RegisteredSqlQuery {
1742 &self.query
1743 }
1744
1745 pub fn complete(mut self) {
1746 if let Some(guard) = self.guard.take() {
1747 guard.complete();
1748 }
1749 self.permit.take();
1750 }
1751
1752 pub fn fail(mut self) {
1753 if let Some(guard) = self.guard.take() {
1754 guard.fail();
1755 }
1756 self.permit.take();
1757 }
1758}
1759
1760impl Drop for ManagedQueryBatches {
1761 fn drop(&mut self) {
1762 if self.guard.is_some() {
1763 let _ = self
1764 .query
1765 .request_cancel(mongreldb_core::CancellationReason::ClientDisconnected);
1766 if let Some(guard) = self.guard.take() {
1767 guard.fail();
1768 }
1769 }
1770 }
1771}
1772
1773struct ManagedQueryStream {
1774 inner: MongrelRecordBatchStream,
1775 schema: SchemaRef,
1776 query: RegisteredSqlQuery,
1777 guard: Option<RegisteredQueryGuard>,
1778 permit: Option<tokio::sync::OwnedSemaphorePermit>,
1779 deferred_completion: Option<Arc<std::sync::atomic::AtomicBool>>,
1780}
1781
1782#[derive(Clone)]
1783pub struct SqlStreamCompletion {
1784 completed: Arc<std::sync::atomic::AtomicBool>,
1785}
1786
1787impl SqlStreamCompletion {
1788 pub fn complete(&self) {
1789 self.completed
1790 .store(true, std::sync::atomic::Ordering::Release);
1791 }
1792}
1793
1794struct TransactionStatementGuard<'a> {
1795 session: &'a MongrelSession,
1796 savepoint: Option<usize>,
1797 completed: bool,
1798}
1799
1800impl<'a> TransactionStatementGuard<'a> {
1801 fn new(session: &'a MongrelSession, enabled: bool) -> Self {
1802 let savepoint = enabled
1803 .then(|| session.sql_txn.lock().as_ref().map(Vec::len))
1804 .flatten();
1805 Self {
1806 session,
1807 savepoint,
1808 completed: false,
1809 }
1810 }
1811
1812 fn complete(mut self) {
1813 self.completed = true;
1814 }
1815}
1816
1817impl Drop for TransactionStatementGuard<'_> {
1818 fn drop(&mut self) {
1819 if self.completed {
1820 return;
1821 }
1822 if let Some(savepoint) = self.savepoint {
1823 if let Some(ops) = self.session.sql_txn.lock().as_mut() {
1824 ops.truncate(savepoint);
1825 *self.session.sql_txn_aborted.lock() = true;
1826 }
1827 }
1828 }
1829}
1830
1831impl futures::Stream for ManagedQueryStream {
1832 type Item = datafusion::common::Result<RecordBatch>;
1833
1834 fn poll_next(
1835 mut self: std::pin::Pin<&mut Self>,
1836 context: &mut std::task::Context<'_>,
1837 ) -> std::task::Poll<Option<Self::Item>> {
1838 if let Err(error) = self.query.checkpoint() {
1839 if let Some(guard) = self.guard.take() {
1840 guard.fail();
1841 }
1842 self.permit.take();
1843 return std::task::Poll::Ready(Some(Err(DataFusionError::External(Box::new(error)))));
1844 }
1845 match self.inner.as_mut().poll_next(context) {
1846 std::task::Poll::Ready(None) => {
1847 self.query.complete_current_statement();
1848 if self.deferred_completion.is_none() {
1849 if let Some(guard) = self.guard.take() {
1850 guard.complete();
1851 }
1852 self.permit.take();
1853 }
1854 std::task::Poll::Ready(None)
1855 }
1856 std::task::Poll::Ready(Some(Err(error))) => {
1857 if let Some(guard) = self.guard.take() {
1858 guard.fail();
1859 }
1860 self.permit.take();
1861 std::task::Poll::Ready(Some(Err(error)))
1862 }
1863 item => item,
1864 }
1865 }
1866}
1867
1868impl datafusion::physical_plan::RecordBatchStream for ManagedQueryStream {
1869 fn schema(&self) -> SchemaRef {
1870 Arc::clone(&self.schema)
1871 }
1872}
1873
1874impl Drop for ManagedQueryStream {
1875 fn drop(&mut self) {
1876 if self.guard.is_some() {
1877 let completed = self
1878 .deferred_completion
1879 .as_ref()
1880 .is_some_and(|completion| completion.load(std::sync::atomic::Ordering::Acquire));
1881 if completed {
1882 if let Some(guard) = self.guard.take() {
1883 guard.complete();
1884 }
1885 } else {
1886 let _ = self
1887 .query
1888 .request_cancel(mongreldb_core::CancellationReason::ClientDisconnected);
1889 if let Some(guard) = self.guard.take() {
1890 guard.fail();
1891 }
1892 }
1893 }
1894 }
1895}
1896
1897impl MongrelSession {
1898 pub fn new(db: Table) -> Self {
1902 let db = Arc::new(Mutex::new(db));
1903 let handle = TableHandle::from(Arc::clone(&db));
1904 let ctx = SessionContext::new();
1905 let sql_fn_state = Arc::new(extended_sql_functions::ExtendedSqlState::default());
1906 register_mongrel_functions(&ctx, Arc::clone(&sql_fn_state));
1907 let tables = Arc::new(parking_lot::Mutex::new(HashMap::new()));
1908 let scored_runtime = scored_sql::ScoredRuntime::from_env();
1909 scored_sql::register(
1910 &ctx,
1911 Arc::clone(&tables),
1912 None,
1913 None,
1914 false,
1915 Arc::clone(&scored_runtime),
1916 );
1917 let external_modules = Arc::new(ExternalModuleRegistry::default());
1918 Self {
1919 ctx,
1920 db: Some(handle),
1921 database: None,
1922 principal: None,
1923 principal_catalog_bound: false,
1924 cache: parking_lot::Mutex::new(BoundedLru::new(SESSION_CACHE_MAX_ENTRIES)),
1925 plan_cache: parking_lot::Mutex::new(BoundedLru::new(SESSION_CACHE_MAX_ENTRIES)),
1926 tables,
1927 views: parking_lot::Mutex::new(HashMap::new()),
1928 attached_databases: parking_lot::Mutex::new(HashMap::new()),
1929 savepoints: parking_lot::Mutex::new(Vec::new()),
1930 sql_txn: parking_lot::Mutex::new(None),
1931 sql_txn_aborted: parking_lot::Mutex::new(false),
1932 sql_fn_state,
1933 external_modules,
1934 scored_runtime,
1935 prepared_names: parking_lot::Mutex::new(std::collections::HashSet::new()),
1936 query_registry: Arc::new(SqlQueryRegistry::default()),
1937 execution_gate: Arc::new(tokio::sync::Semaphore::new(1)),
1938 statement_timeout: parking_lot::RwLock::new(None),
1939 test_hook: parking_lot::Mutex::new(None),
1940 }
1941 }
1942
1943 pub fn new_with_external_modules(
1944 db: Table,
1945 modules: impl IntoIterator<Item = Arc<dyn ExternalTableModule>>,
1946 ) -> Result<Self> {
1947 let session = Self::new(db);
1948 for module in modules {
1949 session.register_external_module(module)?;
1950 }
1951 Ok(session)
1952 }
1953
1954 pub fn open(database: Arc<Database>) -> Result<Self> {
1959 Self::open_with_external_modules(database, std::iter::empty())
1960 }
1961
1962 pub fn open_as(database: Arc<Database>, principal: mongreldb_core::Principal) -> Result<Self> {
1963 Self::open_with_external_modules_as(database, std::iter::empty(), Some(principal))
1964 }
1965
1966 pub fn open_with_external_modules(
1967 database: Arc<Database>,
1968 modules: impl IntoIterator<Item = Arc<dyn ExternalTableModule>>,
1969 ) -> Result<Self> {
1970 Self::open_with_external_modules_as(database, modules, None)
1971 }
1972
1973 pub fn open_with_external_modules_as(
1974 database: Arc<Database>,
1975 modules: impl IntoIterator<Item = Arc<dyn ExternalTableModule>>,
1976 principal: Option<mongreldb_core::Principal>,
1977 ) -> Result<Self> {
1978 let ctx = SessionContext::new();
1979 let sql_fn_state = Arc::new(extended_sql_functions::ExtendedSqlState::default());
1980 register_mongrel_functions(&ctx, Arc::clone(&sql_fn_state));
1981 let external_modules = Arc::new(ExternalModuleRegistry::default());
1982 let principal_catalog_bound = principal
1983 .as_ref()
1984 .is_some_and(|principal| database.resolve_principal(&principal.username).is_some());
1985 for module in modules {
1986 external_modules.register(module)?;
1987 }
1988
1989 let mut tables: HashMap<String, TableHandle> = HashMap::new();
1990 for name in database.table_names() {
1991 let handle = database.table(&name)?;
1992 let provider = MongrelProvider::new_secured(
1993 handle.clone(),
1994 Arc::clone(&database),
1995 name.clone(),
1996 principal.clone(),
1997 )?;
1998 ctx.register_table(&name, Arc::new(provider))
1999 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2000 tables.insert(name, handle);
2001 }
2002 for entry in database.external_tables() {
2003 let provider = external_modules.external_table_provider(&database, &entry)?;
2004 ctx.register_table(&entry.name, provider)
2005 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2006 }
2007
2008 let primary = {
2011 let mut names: Vec<&String> = tables.keys().collect();
2012 names.sort();
2013 names.first().and_then(|n| tables.get(*n).cloned())
2014 };
2015 let tables = Arc::new(parking_lot::Mutex::new(tables));
2016 let scored_runtime = scored_sql::ScoredRuntime::from_env();
2017 scored_sql::register(
2018 &ctx,
2019 Arc::clone(&tables),
2020 Some(Arc::clone(&database)),
2021 principal.clone(),
2022 principal_catalog_bound,
2023 Arc::clone(&scored_runtime),
2024 );
2025
2026 Ok(Self {
2027 ctx,
2028 db: primary,
2029 database: Some(database),
2030 principal,
2031 principal_catalog_bound,
2032 cache: parking_lot::Mutex::new(BoundedLru::new(SESSION_CACHE_MAX_ENTRIES)),
2033 plan_cache: parking_lot::Mutex::new(BoundedLru::new(SESSION_CACHE_MAX_ENTRIES)),
2034 tables,
2035 views: parking_lot::Mutex::new(HashMap::new()),
2036 attached_databases: parking_lot::Mutex::new(HashMap::new()),
2037 savepoints: parking_lot::Mutex::new(Vec::new()),
2038 sql_txn: parking_lot::Mutex::new(None),
2039 sql_txn_aborted: parking_lot::Mutex::new(false),
2040 sql_fn_state,
2041 external_modules,
2042 scored_runtime,
2043 prepared_names: parking_lot::Mutex::new(std::collections::HashSet::new()),
2044 query_registry: Arc::new(SqlQueryRegistry::default()),
2045 execution_gate: Arc::new(tokio::sync::Semaphore::new(1)),
2046 statement_timeout: parking_lot::RwLock::new(None),
2047 test_hook: parking_lot::Mutex::new(None),
2048 })
2049 }
2050
2051 pub fn query_registry(&self) -> &Arc<SqlQueryRegistry> {
2052 &self.query_registry
2053 }
2054
2055 pub fn with_query_registry(mut self, registry: Arc<SqlQueryRegistry>) -> Self {
2059 self.query_registry = registry;
2060 self
2061 }
2062
2063 pub fn set_statement_timeout(&self, timeout: Option<std::time::Duration>) {
2064 *self.statement_timeout.write() = timeout;
2065 }
2066
2067 pub fn register_query(&self, mut options: SqlQueryOptions) -> Result<RegisteredSqlQuery> {
2068 if let Some(statement_timeout) = *self.statement_timeout.read() {
2069 options.timeout = Some(
2070 options
2071 .timeout
2072 .map_or(statement_timeout, |timeout| timeout.min(statement_timeout)),
2073 );
2074 }
2075 let query = self.query_registry.register(options)?;
2076 self.fire_test_hook(SqlTestHookPoint::AfterRegistration);
2077 Ok(query)
2078 }
2079
2080 pub fn cancel_query(&self, query_id: QueryId) -> CancelOutcome {
2081 self.query_registry.cancel(query_id)
2082 }
2083
2084 #[doc(hidden)]
2085 pub fn set_test_hook(&self, hook: Option<SqlTestHook>) {
2086 *self.test_hook.lock() = hook;
2087 }
2088
2089 #[doc(hidden)]
2090 pub fn sql_test_hook(&self) -> Option<SqlTestHook> {
2091 self.test_hook.lock().clone()
2092 }
2093
2094 #[doc(hidden)]
2095 pub fn staged_sql_operation_count(&self) -> Option<usize> {
2096 self.sql_txn.lock().as_ref().map(Vec::len)
2097 }
2098
2099 pub fn fire_test_hook(&self, point: SqlTestHookPoint) {
2100 let hook = self.test_hook.lock().clone();
2101 if let Some(hook) = hook {
2102 hook(point);
2103 }
2104 }
2105
2106 pub(crate) fn current_query(&self) -> Result<RegisteredSqlQuery> {
2107 CURRENT_SQL_QUERY.try_with(Clone::clone).map_err(|_| {
2108 MongrelQueryError::InvalidQueryState("SQL command has no registered query".into())
2109 })
2110 }
2111
2112 pub fn principal(&self) -> Option<mongreldb_core::Principal> {
2113 self.principal.as_ref().and_then(|principal| {
2114 if self.principal_catalog_bound {
2115 self.database
2116 .as_ref()
2117 .and_then(|database| database.resolve_principal(&principal.username))
2118 } else {
2119 Some(principal.clone())
2120 }
2121 })
2122 }
2123
2124 pub fn set_scored_execution_limits(
2126 &self,
2127 statement_timeout: std::time::Duration,
2128 max_work: usize,
2129 max_fused_candidates: usize,
2130 ) -> Result<()> {
2131 self.scored_runtime
2132 .configure(statement_timeout, max_work, max_fused_candidates)
2133 .map_err(Into::into)
2134 }
2135
2136 fn security_context_active(&self) -> bool {
2137 self.principal.is_some()
2138 || self.database.as_ref().is_some_and(|database| {
2139 database.principal_snapshot().is_some() || {
2140 let security = database.security_catalog();
2141 !security.rls_tables.is_empty()
2142 || !security.policies.is_empty()
2143 || !security.masks.is_empty()
2144 }
2145 })
2146 }
2147
2148 pub fn register_external_module(&self, module: Arc<dyn ExternalTableModule>) -> Result<()> {
2149 self.external_modules.register(module)?;
2150 self.clear_cache();
2151 Ok(())
2152 }
2153
2154 pub fn db(&self) -> Option<&TableHandle> {
2158 self.db.as_ref()
2159 }
2160
2161 pub fn create_view(&self, name: &str, sql: &str) {
2166 self.create_view_with_schema(name, sql, CoreSchema::default(), HashMap::new());
2167 }
2168
2169 pub(crate) fn create_view_with_schema(
2170 &self,
2171 name: &str,
2172 sql: &str,
2173 schema: CoreSchema,
2174 input_types: HashMap<u16, Option<TypeId>>,
2175 ) {
2176 self.views.lock().insert(
2177 name.to_string(),
2178 ViewDef {
2179 sql: sql.to_string(),
2180 schema,
2181 input_types,
2182 },
2183 );
2184 }
2185
2186 pub fn drop_view(&self, name: &str) {
2188 self.views.lock().remove(name);
2189 }
2190
2191 pub(crate) fn view_schema(&self, name: &str) -> Option<CoreSchema> {
2192 self.views.lock().get(name).map(|view| view.schema.clone())
2193 }
2194
2195 pub(crate) fn view_definition(&self, name: &str) -> Option<ViewDef> {
2196 self.views.lock().get(name).cloned()
2197 }
2198
2199 pub async fn register(&self, name: &str) -> Result<()> {
2201 let db = self.db.clone().ok_or(MongrelQueryError::Core(
2202 mongreldb_core::MongrelError::NotFound("no primary table".into()),
2203 ))?;
2204 let provider = MongrelProvider::new_handle(db.clone())?;
2205 self.tables.lock().insert(name.to_string(), db);
2206 self.ctx
2207 .register_table(name, Arc::new(provider))
2208 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2209 Ok(())
2210 }
2211
2212 pub async fn register_db(&self, name: &str, db: Table) -> Result<()> {
2218 let db_arc = Arc::new(Mutex::new(db));
2219 let provider = MongrelProvider::new(db_arc.clone())?;
2220 self.tables.lock().insert(name.to_string(), db_arc.into());
2221 self.ctx
2222 .register_table(name, Arc::new(provider))
2223 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2224 Ok(())
2225 }
2226
2227 fn refresh_registered_table(&self, db: &Arc<Database>, name: &str) -> Result<()> {
2228 self.ctx
2229 .deregister_table(name)
2230 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2231 let handle = db.table(name)?;
2232 let provider = MongrelProvider::new_secured(
2233 handle.clone(),
2234 Arc::clone(db),
2235 name.to_string(),
2236 self.principal.clone(),
2237 )?;
2238 self.ctx
2239 .register_table(name, Arc::new(provider))
2240 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2241 self.tables.lock().insert(name.to_string(), handle);
2242 Ok(())
2243 }
2244
2245 fn try_catalog_introspection(&self, sql: &str) -> Result<Option<Vec<RecordBatch>>> {
2253 let lower = sql.to_ascii_lowercase();
2254 if !lower.contains("information_schema.tables") {
2255 return Ok(None);
2256 }
2257 use arrow::array::{ArrayRef, Int64Array, StringArray};
2258 use arrow::datatypes::{DataType, Field, Schema};
2259 use arrow::record_batch::RecordBatch;
2260
2261 let mut types: Vec<String> = Vec::new();
2262 let mut names: Vec<String> = Vec::new();
2263 let mut tbl_names: Vec<String> = Vec::new();
2264
2265 let principal = self.principal();
2267 let can_see = |name: &str| match &self.database {
2268 Some(database) => database
2269 .select_column_ids_for(name, principal.as_ref())
2270 .is_ok(),
2271 None => true,
2272 };
2273 for name in self.tables.lock().keys().filter(|name| can_see(name)) {
2274 types.push("table".into());
2275 names.push(name.clone());
2276 tbl_names.push(name.clone());
2277 }
2278 for name in self.views.lock().keys() {
2280 types.push("view".into());
2281 names.push(name.clone());
2282 tbl_names.push(name.clone());
2283 }
2284 if let Some(db) = &self.database {
2286 for t in db.triggers() {
2287 let target_name = match &t.target {
2288 mongreldb_core::trigger::TriggerTarget::Table(n)
2289 | mongreldb_core::trigger::TriggerTarget::View(n) => n.clone(),
2290 };
2291 if !can_see(&target_name) {
2292 continue;
2293 }
2294 types.push("trigger".into());
2295 names.push(t.name.clone());
2296 tbl_names.push(target_name);
2297 }
2298 }
2299
2300 let schema = Arc::new(Schema::new(vec![
2301 Field::new("type", DataType::Utf8, false),
2302 Field::new("name", DataType::Utf8, false),
2303 Field::new("tbl_name", DataType::Utf8, false),
2304 Field::new("rootpage", DataType::Int64, false),
2305 Field::new("sql", DataType::Utf8, true),
2306 ]));
2307 let n = names.len();
2308 let rootpages: Vec<i64> = vec![0; n];
2309 let sqls: Vec<Option<&str>> = vec![None; n];
2310 let batch = RecordBatch::try_new(
2311 schema,
2312 vec![
2313 Arc::new(StringArray::from(types)) as ArrayRef,
2314 Arc::new(StringArray::from(names)) as ArrayRef,
2315 Arc::new(StringArray::from(tbl_names)) as ArrayRef,
2316 Arc::new(Int64Array::from(rootpages)) as ArrayRef,
2317 Arc::new(StringArray::from(sqls)) as ArrayRef,
2318 ],
2319 )
2320 .map_err(|e| MongrelQueryError::Arrow(e.to_string()))?;
2321 Ok(Some(vec![batch]))
2322 }
2323
2324 fn try_direct_dispatch(
2330 &self,
2331 sql: &str,
2332 query: &RegisteredSqlQuery,
2333 ) -> Result<Option<Vec<RecordBatch>>> {
2334 query.checkpoint()?;
2335 if self.security_context_active() {
2336 return Ok(None);
2337 }
2338
2339 use arrow::array::ArrayRef;
2340 use mongreldb_core::Condition;
2341 use sqlparser::ast::{Expr, Query, SelectItem, SetExpr, Statement, TableFactor};
2342 use sqlparser::dialect::PostgreSqlDialect;
2343 use sqlparser::parser::Parser;
2344
2345 let Ok(stmts) = Parser::parse_sql(&PostgreSqlDialect {}, sql) else {
2347 return Ok(None);
2348 };
2349 query.checkpoint()?;
2350 if stmts.len() != 1 {
2351 return Ok(None);
2352 }
2353 let Statement::Query(ast_query) = stmts.into_iter().next().unwrap() else {
2354 return Ok(None);
2355 };
2356 let Query { body, .. } = *ast_query;
2357 let select = match *body {
2358 SetExpr::Select(s) => *s,
2359 _ => return Ok(None),
2360 };
2361 let lower_sql = sql.to_lowercase();
2364 if lower_sql.contains(" limit ") || lower_sql.contains(" offset ") {
2365 return Ok(None);
2366 }
2367 use sqlparser::ast::GroupByExpr;
2369 if select.distinct.is_some()
2370 || !matches!(&select.group_by, GroupByExpr::Expressions(e, _) if e.is_empty())
2371 || select.having.is_some()
2372 || select.from.len() != 1
2373 || !select.from[0].joins.is_empty()
2374 {
2375 return Ok(None);
2376 }
2377 let table_name = match &select.from[0].relation {
2378 TableFactor::Table { name, .. } => Some(name.to_string()),
2379 _ => return Ok(None),
2380 };
2381 let Some(table_name) = table_name else {
2382 return Ok(None);
2383 };
2384
2385 if select.selection.is_none() {
2391 return Ok(None);
2392 }
2393
2394 let mut proj_names: Option<Vec<String>> = None;
2396 for item in &select.projection {
2397 match item {
2398 SelectItem::Wildcard(_) => {}
2399 SelectItem::UnnamedExpr(Expr::Identifier(ident)) => {
2400 proj_names
2401 .get_or_insert_with(Vec::new)
2402 .push(ident.value.clone());
2403 }
2404 SelectItem::UnnamedExpr(Expr::CompoundIdentifier(idents)) => {
2405 if let Some(last) = idents.last() {
2406 proj_names
2407 .get_or_insert_with(Vec::new)
2408 .push(last.value.clone());
2409 }
2410 }
2411 _ => return Ok(None),
2412 }
2413 }
2414
2415 let handle = match self.tables.lock().get(&table_name).cloned() {
2417 Some(h) => h,
2418 None => return Ok(None),
2419 };
2420
2421 if let Some(db) = &self.database {
2423 db.require_table(
2424 &table_name,
2425 mongreldb_core::auth_state::RequiredPermission::Select,
2426 )?;
2427 }
2428
2429 let mut db = handle.lock();
2430 let schema = db.schema().clone();
2431 let conditions: Vec<Condition> = match &select.selection {
2434 Some(expr) => match translate_sqlparser_filter(expr, &schema) {
2435 Some(c) => c,
2436 None => return Ok(None),
2437 },
2438 None => Vec::new(),
2439 };
2440 if !conditions.is_empty() && db.ensure_indexes_complete().is_err() {
2441 return Ok(None);
2442 }
2443 let snap = db.snapshot();
2444
2445 let mut col_ids: Vec<u16> = Vec::new();
2447 let mut fields: Vec<arrow::datatypes::Field> = Vec::new();
2448 let resolve_col = |name: &str| -> Option<&mongreldb_core::schema::ColumnDef> {
2449 schema.columns.iter().find(|c| c.name == name)
2450 };
2451 match &proj_names {
2452 None => {
2453 for c in &schema.columns {
2454 col_ids.push(c.id);
2455 fields.push(arrow::datatypes::Field::new(
2456 &c.name,
2457 arrow_conv::arrow_data_type(&c.ty)?,
2458 c.flags.contains(mongreldb_core::ColumnFlags::NULLABLE),
2459 ));
2460 }
2461 }
2462 Some(names) => {
2463 for n in names {
2464 let cdef = match resolve_col(n) {
2465 Some(c) => c,
2466 None => return Ok(None), };
2468 col_ids.push(cdef.id);
2469 fields.push(arrow::datatypes::Field::new(
2470 &cdef.name,
2471 arrow_conv::arrow_data_type(&cdef.ty)?,
2472 cdef.flags.contains(mongreldb_core::ColumnFlags::NULLABLE),
2473 ));
2474 }
2475 }
2476 }
2477
2478 let cols = if !conditions.is_empty() {
2480 match db.query_columns_native_cached_with_control(
2481 &conditions,
2482 Some(&col_ids),
2483 snap,
2484 query.control(),
2485 ) {
2486 Ok(Some(c)) => c,
2487 Ok(None) => db
2488 .visible_columns_native_with_control(snap, Some(&col_ids), query.control())
2489 .map_err(MongrelQueryError::Core)?,
2490 Err(_) => return Ok(None),
2491 }
2492 } else {
2493 db.visible_columns_native_with_control(snap, Some(&col_ids), query.control())
2494 .map_err(MongrelQueryError::Core)?
2495 };
2496 query.checkpoint()?;
2497 drop(db);
2498
2499 let mut arrays: Vec<ArrayRef> = Vec::with_capacity(col_ids.len());
2501 for cid in &col_ids {
2502 query.checkpoint()?;
2503 let col = cols
2504 .iter()
2505 .find(|(id, _)| id == cid)
2506 .map(|(_, c)| c.clone());
2507 let Some(col) = col else { return Ok(None) };
2508 let ty = schema
2509 .columns
2510 .iter()
2511 .find(|c| c.id == *cid)
2512 .map(|c| c.ty.clone())
2513 .unwrap_or(mongreldb_core::schema::TypeId::Int64);
2514 arrays.push(arrow_conv::native_to_array_with_query(
2515 ty,
2516 &col,
2517 Some(query),
2518 )?);
2519 }
2520 let batch_schema = Arc::new(arrow::datatypes::Schema::new(fields));
2521 let batch = RecordBatch::try_new(batch_schema, arrays)
2522 .map_err(|e| MongrelQueryError::Arrow(format!("direct dispatch batch build: {e}")))?;
2523
2524 mongreldb_core::trace::QueryTrace::record(|t| {
2525 t.scan_mode = mongreldb_core::trace::ScanMode::DirectDispatch;
2526 t.planning_nanos = 0; });
2528 Ok(Some(vec![batch]))
2529 }
2530
2531 async fn dataframe(&self, sql: &str) -> Result<datafusion::dataframe::DataFrame> {
2532 let use_plan_cache = !is_prepared_stmt_sql(sql);
2535 if let Some(plan) = use_plan_cache
2536 .then(|| self.plan_cache.lock().get(sql).cloned())
2537 .flatten()
2538 {
2539 return Ok(datafusion::dataframe::DataFrame::new(
2540 self.ctx.state(),
2541 plan,
2542 ));
2543 }
2544
2545 let df = self
2546 .ctx
2547 .sql(sql)
2548 .await
2549 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
2550 if use_plan_cache {
2551 self.plan_cache
2552 .lock()
2553 .insert(sql.to_string(), df.logical_plan().clone());
2554 }
2555 Ok(df)
2556 }
2557
2558 fn prepare_as_of_query(&self, sql: &str) -> Result<Option<AsOfQuery>> {
2559 static AS_OF_RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
2560 static NEXT_TEMP: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
2561 let re = AS_OF_RE.get_or_init(|| {
2562 regex::Regex::new(
2563 r#"(?i)\b(FROM|JOIN)\s+("[^"]+"|[A-Za-z_][A-Za-z0-9_]*)\s+AS\s+OF\s+EPOCH\s+([0-9]+)\b"#,
2564 )
2565 .expect("valid AS OF regex")
2566 });
2567 let mut captures = re.captures_iter(sql);
2568 let Some(capture) = captures.next() else {
2569 return Ok(None);
2570 };
2571 if captures.next().is_some() {
2572 return Err(MongrelQueryError::Schema(
2573 "AS OF EPOCH currently supports one historical table per query".into(),
2574 ));
2575 }
2576 let database = self.database.as_ref().ok_or_else(|| {
2577 MongrelQueryError::Schema("AS OF EPOCH requires a Database-backed session".into())
2578 })?;
2579 let whole = capture.get(0).expect("whole AS OF match");
2580 let keyword = capture.get(1).expect("FROM/JOIN capture").as_str();
2581 let table_token = capture.get(2).expect("table capture").as_str();
2582 let table_name = table_token.trim_matches('"');
2583 let epoch = capture
2584 .get(3)
2585 .expect("epoch capture")
2586 .as_str()
2587 .parse::<u64>()
2588 .map_err(|error| MongrelQueryError::Schema(format!("AS OF epoch: {error}")))?;
2589 let temp_id = NEXT_TEMP.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2590 let temp_name = format!("__mongrel_as_of_{temp_id}");
2591
2592 let mut replace_end = whole.end();
2596 let mut alias = table_token.to_string();
2597 let tail = &sql[replace_end..];
2598 let whitespace = tail.len() - tail.trim_start().len();
2599 let trimmed_tail = tail.trim_start();
2600 if trimmed_tail
2601 .get(..3)
2602 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("as "))
2603 {
2604 let alias_text = &trimmed_tail[3..];
2605 let alias_len = alias_text
2606 .find(|character: char| {
2607 character.is_ascii_whitespace() || character == ',' || character == ';'
2608 })
2609 .unwrap_or(alias_text.len());
2610 if alias_len == 0 {
2611 return Err(MongrelQueryError::Schema(
2612 "AS OF EPOCH AS requires an alias".into(),
2613 ));
2614 }
2615 alias = alias_text[..alias_len].to_string();
2616 replace_end += whitespace + 3 + alias_len;
2617 } else {
2618 let alias_len = trimmed_tail
2619 .find(|character: char| {
2620 character.is_ascii_whitespace() || character == ',' || character == ';'
2621 })
2622 .unwrap_or(trimmed_tail.len());
2623 let candidate = &trimmed_tail[..alias_len];
2624 let keyword = candidate.to_ascii_lowercase();
2625 let clause_keyword = matches!(
2626 keyword.as_str(),
2627 "where"
2628 | "join"
2629 | "left"
2630 | "right"
2631 | "inner"
2632 | "outer"
2633 | "cross"
2634 | "full"
2635 | "on"
2636 | "group"
2637 | "order"
2638 | "having"
2639 | "limit"
2640 | "offset"
2641 | "union"
2642 | "intersect"
2643 | "except"
2644 );
2645 let valid_alias = candidate
2646 .as_bytes()
2647 .first()
2648 .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_' || *byte == b'"');
2649 if alias_len > 0 && valid_alias && !clause_keyword {
2650 alias = candidate.to_string();
2651 replace_end += whitespace + alias_len;
2652 }
2653 }
2654 let replacement = format!("{keyword} {temp_name} AS {alias}");
2655 let mut rewritten = String::with_capacity(sql.len() + replacement.len());
2656 rewritten.push_str(&sql[..whole.start()]);
2657 rewritten.push_str(&replacement);
2658 rewritten.push_str(&sql[replace_end..]);
2659
2660 let (snapshot, retention) = database.snapshot_at_owned(mongreldb_core::Epoch(epoch))?;
2661 let handle = database.table(table_name)?;
2662 let provider = MongrelProvider::new_historical(
2663 handle,
2664 snapshot,
2665 retention,
2666 Some(ProviderSecurity {
2667 database: Arc::clone(database),
2668 table: table_name.to_string(),
2669 principal: self.principal.clone(),
2670 principal_catalog_bound: self.principal_catalog_bound,
2671 }),
2672 )?;
2673 self.ctx
2674 .register_table(&temp_name, Arc::new(provider))
2675 .map_err(|error| MongrelQueryError::DataFusion(error.to_string()))?;
2676 Ok(Some(AsOfQuery {
2677 sql: rewritten,
2678 registration: AsOfRegistration {
2679 ctx: self.ctx.clone(),
2680 table_name: temp_name,
2681 },
2682 }))
2683 }
2684
2685 async fn execute_dataframe_stream(
2686 &self,
2687 dataframe: datafusion::dataframe::DataFrame,
2688 query: &RegisteredSqlQuery,
2689 ) -> Result<MongrelRecordBatchStream> {
2690 query.checkpoint()?;
2691 let task_context = dataframe.task_ctx();
2692 let session_config = task_context
2693 .session_config()
2694 .clone()
2695 .with_extension(Arc::new(SqlTaskContext::new(
2696 query.clone(),
2697 self.sql_test_hook(),
2698 )));
2699 let task_context = Arc::new(task_context.with_session_config(session_config));
2700 let physical_plan = tokio::select! {
2701 biased;
2702 plan = dataframe.create_physical_plan() => plan
2703 .map_err(|error| MongrelQueryError::DataFusion(error.to_string()))?,
2704 _ = query.control().cancelled() => return Err(query.checkpoint().unwrap_err()),
2705 };
2706 query.checkpoint()?;
2707 execute_stream(physical_plan, task_context)
2708 .map_err(|error| MongrelQueryError::DataFusion(error.to_string()))
2709 }
2710
2711 async fn collect_dataframe(
2712 &self,
2713 dataframe: datafusion::dataframe::DataFrame,
2714 query: &RegisteredSqlQuery,
2715 ) -> Result<Vec<RecordBatch>> {
2716 use futures::StreamExt;
2717
2718 let mut stream = self.execute_dataframe_stream(dataframe, query).await?;
2719 let mut batches = Vec::new();
2720 loop {
2721 let item = tokio::select! {
2722 biased;
2723 _ = query.control().cancelled() => return Err(query.checkpoint().unwrap_err()),
2724 item = stream.next() => item,
2725 };
2726 let Some(batch) = item else {
2727 break;
2728 };
2729 batches.push(batch.map_err(|error| MongrelQueryError::DataFusion(error.to_string()))?);
2730 query.checkpoint()?;
2731 }
2732 Ok(batches)
2733 }
2734
2735 async fn run_as_of(
2736 &self,
2737 query: AsOfQuery,
2738 sql_query: &RegisteredSqlQuery,
2739 ) -> Result<Vec<RecordBatch>> {
2740 let AsOfQuery { sql, registration } = query;
2741 let _registration = registration;
2742 let plan_start = std::time::Instant::now();
2743 let dataframe = self
2744 .ctx
2745 .sql(&sql)
2746 .await
2747 .map_err(|error| MongrelQueryError::DataFusion(error.to_string()))?;
2748 mongreldb_core::trace::QueryTrace::record(|trace| {
2749 trace.planning_nanos = plan_start.elapsed().as_nanos() as u64;
2750 });
2751 self.collect_dataframe(dataframe, sql_query).await
2752 }
2753
2754 async fn run_as_of_stream(
2755 &self,
2756 query: AsOfQuery,
2757 sql_query: &RegisteredSqlQuery,
2758 ) -> Result<MongrelRecordBatchStream> {
2759 use futures::StreamExt;
2760
2761 let AsOfQuery { sql, registration } = query;
2762 let dataframe = self
2763 .ctx
2764 .sql(&sql)
2765 .await
2766 .map_err(|error| MongrelQueryError::DataFusion(error.to_string()))?;
2767 let stream = self.execute_dataframe_stream(dataframe, sql_query).await?;
2768 let schema = stream.schema();
2769 let guarded = futures::stream::unfold(
2770 (stream, registration),
2771 |(mut stream, registration)| async move {
2772 stream
2773 .next()
2774 .await
2775 .map(|item| (item, (stream, registration)))
2776 },
2777 );
2778 Ok(Box::pin(RecordBatchStreamAdapter::new(schema, guarded)))
2779 }
2780
2781 pub async fn run_stream_with_options(
2782 &self,
2783 sql: &str,
2784 options: SqlQueryOptions,
2785 ) -> Result<MongrelRecordBatchStream> {
2786 let query = self.register_query(options)?;
2787 self.run_stream_with_query(sql, query).await
2788 }
2789
2790 pub async fn run_stream_with_query(
2791 &self,
2792 sql: &str,
2793 query: RegisteredSqlQuery,
2794 ) -> Result<MongrelRecordBatchStream> {
2795 self.run_stream_with_query_mode(sql, query, false)
2796 .await
2797 .map(|(stream, _)| stream)
2798 }
2799
2800 pub async fn run_stream_with_query_for_serialization(
2801 &self,
2802 sql: &str,
2803 query: RegisteredSqlQuery,
2804 ) -> Result<(MongrelRecordBatchStream, SqlStreamCompletion)> {
2805 let (stream, completion) = self.run_stream_with_query_mode(sql, query, true).await?;
2806 Ok((
2807 stream,
2808 completion.expect("deferred stream completion is present"),
2809 ))
2810 }
2811
2812 async fn run_stream_with_query_mode(
2813 &self,
2814 sql: &str,
2815 query: RegisteredSqlQuery,
2816 defer_completion: bool,
2817 ) -> Result<(MongrelRecordBatchStream, Option<SqlStreamCompletion>)> {
2818 query.set_sql_metadata(sql);
2819 let guard = RegisteredQueryGuard::new(query.clone());
2820 self.fire_test_hook(SqlTestHookPoint::WaitingForSessionPermit);
2821 let permit = tokio::select! {
2822 permit = Arc::clone(&self.execution_gate).acquire_owned() => permit.map_err(|_| {
2823 MongrelQueryError::InvalidQueryState("session execution gate closed".into())
2824 })?,
2825 _ = query.control().cancelled() => {
2826 let error = query.checkpoint().unwrap_err();
2827 guard.fail();
2828 return Err(error);
2829 }
2830 };
2831 query.transition(SqlQueryPhase::Queued, SqlQueryPhase::Planning)?;
2832 self.fire_test_hook(SqlTestHookPoint::Planning);
2833 query.transition(SqlQueryPhase::Planning, SqlQueryPhase::Executing)?;
2834 query.begin_statement(0);
2835 let stream = CURRENT_SQL_QUERY
2836 .scope(query.clone(), async {
2837 tokio::select! {
2838 biased;
2839 result = self.run_stream_internal(sql, &query) => result,
2840 _ = query.control().cancelled() => Err(query.checkpoint().unwrap_err()),
2841 }
2842 })
2843 .await;
2844 let stream = match stream {
2845 Ok(stream) => stream,
2846 Err(error) => {
2847 guard.fail();
2848 return Err(error);
2849 }
2850 };
2851 query.transition(SqlQueryPhase::Executing, SqlQueryPhase::Streaming)?;
2852 let deferred_completion =
2853 defer_completion.then(|| Arc::new(std::sync::atomic::AtomicBool::new(false)));
2854 if defer_completion {
2855 query.begin_serialization()?;
2856 }
2857 let schema = stream.schema();
2858 let completion = deferred_completion
2859 .as_ref()
2860 .map(|completed| SqlStreamCompletion {
2861 completed: Arc::clone(completed),
2862 });
2863 let stream = Box::pin(ManagedQueryStream {
2864 inner: stream,
2865 schema,
2866 query,
2867 guard: Some(guard),
2868 permit: Some(permit),
2869 deferred_completion,
2870 });
2871 Ok((stream, completion))
2872 }
2873
2874 pub async fn run_stream(&self, sql: &str) -> Result<MongrelRecordBatchStream> {
2875 if let Ok(query) = CURRENT_SQL_QUERY.try_with(Clone::clone) {
2876 return Box::pin(self.run_stream_internal(sql, &query)).await;
2877 }
2878 Box::pin(self.run_stream_with_options(sql, SqlQueryOptions::default())).await
2879 }
2880
2881 async fn run_stream_internal(
2886 &self,
2887 sql: &str,
2888 query: &RegisteredSqlQuery,
2889 ) -> Result<MongrelRecordBatchStream> {
2890 query.checkpoint()?;
2891 if strip_explain_query_plan(sql).is_some() {
2892 return self.run_internal(sql, query).await.map(buffered_stream);
2893 }
2894
2895 let trimmed = sql.trim();
2896 if trimmed.contains(';') && !is_trigger_body(trimmed) {
2897 let stmts = split_sql_statements(trimmed);
2898 let non_empty: Vec<&str> = stmts
2899 .iter()
2900 .map(String::as_str)
2901 .filter(|stmt| !stmt.trim().is_empty() && stmt.trim() != ";")
2902 .collect();
2903 if non_empty.is_empty() {
2904 return Ok(buffered_stream(Vec::new()));
2905 }
2906 if stmts.len() > 1 {
2907 for (index, stmt) in non_empty[..non_empty.len() - 1].iter().enumerate() {
2908 query.begin_statement(index);
2909 query.checkpoint()?;
2910 Box::pin(self.run_internal(stmt, query)).await?;
2911 query.complete_statement(index);
2912 }
2913 query.begin_statement(non_empty.len() - 1);
2914 return Box::pin(self.run_stream_internal(non_empty[non_empty.len() - 1], query))
2915 .await;
2916 }
2917 }
2918
2919 query.checkpoint()?;
2920 if let Some(batches) = commands::try_run_command(self, sql, query).await? {
2921 return Ok(buffered_stream(batches));
2922 }
2923
2924 if let Some(as_of_query) = self.prepare_as_of_query(sql)? {
2925 return self.run_as_of_stream(as_of_query, query).await;
2926 }
2927
2928 let resolved = self.resolve_view_sql(sql);
2929 let resolved = self.rewrite_external_module_compat_sql(&resolved);
2930 let resolved = rewrite_compat_function_calls(&resolved);
2931 let effective_sql = normalize_sql(&resolved);
2932 let sql = effective_sql.as_str();
2933
2934 if let Some(batches) = self.try_catalog_introspection(sql)? {
2935 return Ok(buffered_stream(batches));
2936 }
2937
2938 let plan_start = std::time::Instant::now();
2939 let external_module_scan = self.query_references_external_module(sql);
2940 let df = self.dataframe(sql).await?;
2941 self.track_prepared_name(sql);
2942 mongreldb_core::trace::QueryTrace::record(|trace| {
2943 trace.planning_nanos = plan_start.elapsed().as_nanos() as u64;
2944 });
2945 let stream = self.execute_dataframe_stream(df, query).await?;
2946 if external_module_scan {
2947 mongreldb_core::trace::QueryTrace::record(|trace| {
2948 trace.scan_mode = mongreldb_core::trace::ScanMode::ExternalModule;
2949 });
2950 }
2951 Ok(stream)
2952 }
2953
2954 pub async fn run_with_options(
2955 &self,
2956 sql: &str,
2957 options: SqlQueryOptions,
2958 ) -> Result<Vec<RecordBatch>> {
2959 let query = self.register_query(options)?;
2960 self.run_with_query(sql, query).await
2961 }
2962
2963 pub async fn run_with_query(
2964 &self,
2965 sql: &str,
2966 query: RegisteredSqlQuery,
2967 ) -> Result<Vec<RecordBatch>> {
2968 query.set_sql_metadata(sql);
2969 let guard = RegisteredQueryGuard::new(query.clone());
2970 self.fire_test_hook(SqlTestHookPoint::WaitingForSessionPermit);
2971 let _permit = tokio::select! {
2972 permit = Arc::clone(&self.execution_gate).acquire_owned() => permit.map_err(|_| {
2973 MongrelQueryError::InvalidQueryState("session execution gate closed".into())
2974 })?,
2975 _ = query.control().cancelled() => {
2976 let error = query.checkpoint().unwrap_err();
2977 guard.fail();
2978 return Err(error);
2979 }
2980 };
2981 query.transition(SqlQueryPhase::Queued, SqlQueryPhase::Planning)?;
2982 self.fire_test_hook(SqlTestHookPoint::Planning);
2983 query.transition(SqlQueryPhase::Planning, SqlQueryPhase::Executing)?;
2984 query.begin_statement(0);
2985 let result = CURRENT_SQL_QUERY
2986 .scope(query.clone(), async {
2987 tokio::select! {
2988 biased;
2989 result = self.run_internal(sql, &query) => result,
2990 _ = query.control().cancelled() => Err(query.checkpoint().unwrap_err()),
2991 }
2992 })
2993 .await;
2994 match result {
2995 Ok(batches) => {
2996 query.complete_current_statement();
2997 guard.complete();
2998 Ok(batches)
2999 }
3000 Err(error) => {
3001 guard.fail();
3002 Err(error)
3003 }
3004 }
3005 }
3006
3007 pub async fn run_with_query_for_serialization(
3010 &self,
3011 sql: &str,
3012 query: RegisteredSqlQuery,
3013 ) -> Result<ManagedQueryBatches> {
3014 query.set_sql_metadata(sql);
3015 let guard = RegisteredQueryGuard::new(query.clone());
3016 self.fire_test_hook(SqlTestHookPoint::WaitingForSessionPermit);
3017 let permit = tokio::select! {
3018 permit = Arc::clone(&self.execution_gate).acquire_owned() => permit.map_err(|_| {
3019 MongrelQueryError::InvalidQueryState("session execution gate closed".into())
3020 })?,
3021 _ = query.control().cancelled() => {
3022 let error = query.checkpoint().unwrap_err();
3023 guard.fail();
3024 return Err(error);
3025 }
3026 };
3027 query.transition(SqlQueryPhase::Queued, SqlQueryPhase::Planning)?;
3028 self.fire_test_hook(SqlTestHookPoint::Planning);
3029 query.transition(SqlQueryPhase::Planning, SqlQueryPhase::Executing)?;
3030 query.begin_statement(0);
3031 let result = CURRENT_SQL_QUERY
3032 .scope(query.clone(), async {
3033 tokio::select! {
3034 biased;
3035 result = self.run_internal(sql, &query) => result,
3036 _ = query.control().cancelled() => Err(query.checkpoint().unwrap_err()),
3037 }
3038 })
3039 .await;
3040 match result {
3041 Ok(batches) => {
3042 query.complete_current_statement();
3043 query.begin_serialization()?;
3044 Ok(ManagedQueryBatches {
3045 batches,
3046 query,
3047 guard: Some(guard),
3048 permit: Some(permit),
3049 })
3050 }
3051 Err(error) => {
3052 guard.fail();
3053 Err(error)
3054 }
3055 }
3056 }
3057
3058 pub async fn run(&self, sql: &str) -> Result<Vec<RecordBatch>> {
3059 if let Ok(query) = CURRENT_SQL_QUERY.try_with(Clone::clone) {
3060 return Box::pin(self.run_internal(sql, &query)).await;
3061 }
3062 Box::pin(self.run_with_options(sql, SqlQueryOptions::default())).await
3063 }
3064
3065 async fn run_internal(
3070 &self,
3071 sql: &str,
3072 query: &RegisteredSqlQuery,
3073 ) -> Result<Vec<RecordBatch>> {
3074 let normalized = sql.trim().trim_end_matches(';').trim().to_ascii_lowercase();
3075 let full_rollback = matches!(normalized.as_str(), "rollback" | "rollback transaction");
3076 let transaction_open = self.sql_txn.lock().is_some();
3077 if transaction_open && *self.sql_txn_aborted.lock() && !full_rollback {
3078 return Err(MongrelQueryError::TransactionAborted);
3079 }
3080 let statement = TransactionStatementGuard::new(self, transaction_open && !full_rollback);
3081 let result = Box::pin(self.run_internal_body(sql, query)).await;
3082 if result.is_ok() {
3083 statement.complete();
3084 }
3085 result
3086 }
3087
3088 async fn run_internal_body(
3089 &self,
3090 sql: &str,
3091 query: &RegisteredSqlQuery,
3092 ) -> Result<Vec<RecordBatch>> {
3093 query.checkpoint()?;
3094 if let Some(inner) = strip_explain_query_plan(sql) {
3095 return self.explain_query_plan(inner).await;
3096 }
3097
3098 let trimmed = sql.trim();
3104 if trimmed.contains(';') && !is_trigger_body(trimmed) {
3105 let stmts = split_sql_statements(trimmed);
3106 let non_empty: Vec<&String> = stmts
3108 .iter()
3109 .filter(|s| !s.trim().is_empty() && s.trim() != ";")
3110 .collect();
3111 if non_empty.is_empty() {
3112 return Ok(Vec::new());
3113 }
3114 if stmts.len() > 1 {
3115 let mut last = Vec::new();
3116 let mut statement_index = 0;
3117 for stmt in &stmts {
3118 let stmt = stmt.trim();
3119 if stmt.is_empty() {
3120 continue;
3121 }
3122 query.begin_statement(statement_index);
3123 query.checkpoint()?;
3124 last = Box::pin(self.run_internal(stmt, query)).await?;
3125 query.complete_statement(statement_index);
3126 statement_index += 1;
3127 }
3128 return Ok(last);
3129 }
3130 }
3131
3132 query.checkpoint()?;
3134 if let Some(batches) = commands::try_run_command(self, sql, query).await? {
3135 return Ok(batches);
3136 }
3137
3138 let trimmed = sql.trim();
3144 if trimmed.contains(';') {
3145 let stmts = split_sql_statements(trimmed);
3146 if stmts.len() > 1 {
3147 let mut last = Vec::new();
3148 let mut statement_index = 0;
3149 for stmt in &stmts {
3150 let stmt = stmt.trim();
3151 if stmt.is_empty() {
3152 continue;
3153 }
3154 query.begin_statement(statement_index);
3155 query.checkpoint()?;
3156 last = Box::pin(self.run_internal(stmt, query)).await?;
3157 query.complete_statement(statement_index);
3158 statement_index += 1;
3159 }
3160 return Ok(last);
3161 }
3162 }
3163 let lower = sql.trim_start().to_lowercase();
3165 if lower.starts_with("create table") {
3166 if let Some(db) = &self.database {
3167 db.require_for(self.principal().as_ref(), &mongreldb_core::Permission::Ddl)?;
3168 let (name, schema) = parse_create_table(sql)?;
3169 db.create_table(&name, schema)?;
3170 let handle = db.table(&name)?;
3171 let provider = MongrelProvider::new_secured(
3172 handle.clone(),
3173 Arc::clone(db),
3174 name.clone(),
3175 self.principal.clone(),
3176 )?;
3177 self.ctx
3178 .register_table(&name, Arc::new(provider))
3179 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
3180 self.tables.lock().insert(name, handle);
3181 self.invalidate_prepared_statements().await;
3182 self.clear_cache();
3183 return Ok(Vec::new());
3184 }
3185 }
3186 if lower.starts_with("drop table") {
3187 if let Some(db) = &self.database {
3188 db.require_for(self.principal().as_ref(), &mongreldb_core::Permission::Ddl)?;
3189 let (name, if_exists) = parse_drop_table(sql)?;
3190 let drop_result = db.drop_table(&name);
3191 if let Err(e) = drop_result {
3192 let is_not_found = matches!(e, mongreldb_core::MongrelError::NotFound(_));
3194 if !(if_exists && is_not_found) {
3195 return Err(e.into());
3196 }
3197 } else {
3198 self.ctx
3199 .deregister_table(&name)
3200 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
3201 self.tables.lock().remove(&name);
3202 }
3203 self.invalidate_prepared_statements().await;
3204 self.clear_cache();
3205 return Ok(Vec::new());
3206 }
3207 }
3208 if lower.starts_with("alter table") {
3209 if let Some(db) = &self.database {
3210 db.require_for(self.principal().as_ref(), &mongreldb_core::Permission::Ddl)?;
3211 match parse_alter_table(sql)? {
3212 ParsedAlterTable::RenameTable { old_name, new_name } => {
3213 db.rename_table(&old_name, &new_name)?;
3214 self.ctx
3218 .deregister_table(&old_name)
3219 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
3220 self.tables.lock().remove(&old_name);
3221 let handle = db.table(&new_name)?;
3222 let provider = MongrelProvider::new_secured(
3223 handle.clone(),
3224 Arc::clone(db),
3225 new_name.clone(),
3226 self.principal.clone(),
3227 )?;
3228 self.ctx
3229 .register_table(&new_name, Arc::new(provider))
3230 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
3231 self.tables.lock().insert(new_name, handle);
3232 }
3233 ParsedAlterTable::RenameColumn {
3234 table_name,
3235 column_name,
3236 new_name,
3237 } => {
3238 db.alter_column(&table_name, &column_name, AlterColumn::rename(new_name))?;
3239 self.refresh_registered_table(db, &table_name)?;
3240 }
3241 ParsedAlterTable::AlterColumnType {
3242 table_name,
3243 column_name,
3244 ty,
3245 } => {
3246 db.alter_column(&table_name, &column_name, AlterColumn::set_type(ty))?;
3247 self.refresh_registered_table(db, &table_name)?;
3248 }
3249 ParsedAlterTable::SetNotNull {
3250 table_name,
3251 column_name,
3252 } => {
3253 let flags = current_column_flags(db, &table_name, &column_name)?
3254 .without(ColumnFlags::NULLABLE);
3255 db.alter_column(&table_name, &column_name, AlterColumn::set_flags(flags))?;
3256 self.refresh_registered_table(db, &table_name)?;
3257 }
3258 ParsedAlterTable::DropNotNull {
3259 table_name,
3260 column_name,
3261 } => {
3262 let flags = current_column_flags(db, &table_name, &column_name)?
3263 .with(ColumnFlags::NULLABLE);
3264 db.alter_column(&table_name, &column_name, AlterColumn::set_flags(flags))?;
3265 self.refresh_registered_table(db, &table_name)?;
3266 }
3267 }
3268 self.invalidate_prepared_statements().await;
3269 self.clear_cache();
3270 return Ok(Vec::new());
3271 }
3272 }
3273
3274 if let Some(as_of_query) = self.prepare_as_of_query(sql)? {
3275 return self.run_as_of(as_of_query, query).await;
3276 }
3277
3278 let resolved = self.resolve_view_sql(sql);
3281 let resolved = self.rewrite_external_module_compat_sql(&resolved);
3282 let resolved = rewrite_compat_function_calls(&resolved);
3283 let effective_sql = normalize_sql(&resolved);
3287 let sql = effective_sql.as_str();
3288 let epoch = self.cache_epoch();
3292 let key = (sql.to_string(), epoch);
3293 let has_ttl_table = self
3294 .tables
3295 .lock()
3296 .values()
3297 .any(|table| table.lock().ttl().is_some());
3298 let result_cacheable = !extended_sql_functions::contains_volatile_extended_function(sql)
3299 && !is_explain_analyze(sql)
3300 && !is_prepared_stmt_sql(sql)
3301 && !has_ttl_table;
3302 if result_cacheable {
3303 if let Some(hit) = self.cache.lock().get(&key) {
3304 return Ok((**hit).clone());
3305 }
3306 }
3307 if let Some(batches) = self.try_catalog_introspection(sql)? {
3310 if result_cacheable {
3311 query.checkpoint()?;
3312 self.cache.lock().insert(key, Arc::new(batches.clone()));
3313 }
3314 return Ok(batches);
3315 }
3316 if let Some(batches) = self.try_direct_dispatch(sql, query)? {
3321 if result_cacheable {
3322 query.checkpoint()?;
3323 self.cache.lock().insert(key, Arc::new(batches.clone()));
3324 }
3325 return Ok(batches);
3326 }
3327 let plan_start = std::time::Instant::now();
3329 let external_module_scan = self.query_references_external_module(sql);
3330 let df = self.dataframe(sql).await?;
3331 self.track_prepared_name(sql);
3334 let planning_nanos = plan_start.elapsed().as_nanos() as u64;
3337 mongreldb_core::trace::QueryTrace::record(|t| t.planning_nanos = planning_nanos);
3338
3339 let agg_key = sql_cache_key(sql);
3344 let batches = match self.try_native_aggregate(df.logical_plan(), agg_key, query) {
3345 Ok(Some(batch)) => vec![batch],
3346 _ => {
3347 match self.try_fk_join(df.logical_plan(), query) {
3351 Ok(Some(b)) => {
3352 mongreldb_core::trace::QueryTrace::record(|t| {
3354 t.join_mode = mongreldb_core::trace::JoinMode::FkBitmap;
3355 });
3356 b
3357 }
3358 _ => self.collect_dataframe(df, query).await?,
3359 }
3360 }
3361 };
3362 if external_module_scan {
3363 mongreldb_core::trace::QueryTrace::record(|t| {
3364 t.scan_mode = mongreldb_core::trace::ScanMode::ExternalModule;
3365 });
3366 }
3367 if result_cacheable {
3368 query.checkpoint()?;
3369 self.cache.lock().insert(key, Arc::new(batches.clone()));
3370 }
3371 Ok(batches)
3372 }
3373
3374 pub async fn run_sql_traced(
3386 &self,
3387 sql: &str,
3388 ) -> Result<(Vec<RecordBatch>, mongreldb_core::trace::QueryTrace)> {
3389 mongreldb_core::trace::QueryTrace::push_scope();
3390 let result = self.run(sql).await;
3391 let trace = mongreldb_core::trace::QueryTrace::pop_scope();
3392 Ok((result?, trace))
3393 }
3394
3395 pub fn clear_cache(&self) {
3398 self.cache.lock().clear();
3399 self.plan_cache.lock().clear();
3400 }
3401
3402 fn track_prepared_name(&self, sql: &str) {
3406 let lower = sql.trim_start().to_ascii_lowercase();
3407 if let Some(rest) = lower.strip_prefix("prepare ") {
3408 if let Some(name) = parse_stmt_ident(rest) {
3409 self.prepared_names.lock().insert(name);
3410 }
3411 } else if let Some(rest) = lower.strip_prefix("deallocate ") {
3412 if let Some(name) = parse_stmt_ident(rest) {
3413 self.prepared_names.lock().remove(&name);
3414 }
3415 }
3416 }
3417
3418 pub async fn invalidate_prepared_statements(&self) {
3425 let names: Vec<String> = self.prepared_names.lock().drain().collect();
3426 for name in names {
3427 let sql = format!("DEALLOCATE {name}");
3431 if self.ctx.sql(&sql).await.is_err() {
3432 }
3434 }
3435 }
3436
3437 async fn explain_query_plan(&self, sql: &str) -> Result<Vec<RecordBatch>> {
3438 let explain_sql = format!("EXPLAIN {}", sql.trim().trim_end_matches(';'));
3439 let batches = self
3440 .ctx
3441 .sql(&explain_sql)
3442 .await
3443 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?
3444 .collect()
3445 .await
3446 .map_err(|e| MongrelQueryError::DataFusion(e.to_string()))?;
3447 let mut detail = self.mongrel_query_plan_details(sql);
3448 for batch in &batches {
3449 if batch.num_columns() < 2 {
3450 continue;
3451 }
3452 let Some(plan_type) = batch.column(0).as_any().downcast_ref::<StringArray>() else {
3453 continue;
3454 };
3455 let Some(plan) = batch.column(1).as_any().downcast_ref::<StringArray>() else {
3456 continue;
3457 };
3458 for row in 0..batch.num_rows() {
3459 let prefix = plan_type.value(row);
3460 for line in plan.value(row).lines() {
3461 let line = line.trim();
3462 if !line.is_empty() {
3463 detail.push(format!("DATAFUSION {prefix}: {line}"));
3464 }
3465 }
3466 }
3467 }
3468 if detail.is_empty() {
3469 detail.push("plan unavailable".to_string());
3470 }
3471 let ids = (0..detail.len()).map(|i| i as i64).collect::<Vec<_>>();
3472 let parents = vec![0_i64; detail.len()];
3473 let notused = vec![0_i64; detail.len()];
3474 let schema = Arc::new(arrow::datatypes::Schema::new(vec![
3475 arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false),
3476 arrow::datatypes::Field::new("parent", arrow::datatypes::DataType::Int64, false),
3477 arrow::datatypes::Field::new("notused", arrow::datatypes::DataType::Int64, false),
3478 arrow::datatypes::Field::new("detail", arrow::datatypes::DataType::Utf8, false),
3479 ]));
3480 let batch = RecordBatch::try_new(
3481 schema,
3482 vec![
3483 Arc::new(Int64Array::from(ids)) as ArrayRef,
3484 Arc::new(Int64Array::from(parents)),
3485 Arc::new(Int64Array::from(notused)),
3486 Arc::new(StringArray::from(detail)),
3487 ],
3488 )
3489 .map_err(|e| MongrelQueryError::Arrow(e.to_string()))?;
3490 Ok(vec![batch])
3491 }
3492
3493 fn mongrel_query_plan_details(&self, sql: &str) -> Vec<String> {
3494 use sqlparser::ast::{GroupByExpr, OrderByKind, SetExpr, Statement};
3495 use sqlparser::dialect::PostgreSqlDialect;
3496 use sqlparser::parser::Parser;
3497
3498 let Ok(stmts) = Parser::parse_sql(&PostgreSqlDialect {}, sql) else {
3499 return Vec::new();
3500 };
3501 let Some(Statement::Query(query)) = stmts.first() else {
3502 return Vec::new();
3503 };
3504
3505 fn collect(session: &MongrelSession, query: &sqlparser::ast::Query, out: &mut Vec<String>) {
3506 use sqlparser::ast::{SetOperator, TableWithJoins};
3507 match query.body.as_ref() {
3508 SetExpr::Select(select) => {
3509 for TableWithJoins { relation, joins } in &select.from {
3510 session.push_table_plan(relation, select.selection.as_ref(), out);
3511 for join in joins {
3512 session.push_table_plan(&join.relation, None, out);
3513 }
3514 }
3515 if select.distinct.is_some() {
3516 out.push("USE TEMP B-TREE FOR DISTINCT".to_string());
3517 }
3518 let grouped = match &select.group_by {
3519 GroupByExpr::All(_) => true,
3520 GroupByExpr::Expressions(exprs, _) => !exprs.is_empty(),
3521 };
3522 if grouped {
3523 out.push("USE TEMP B-TREE FOR GROUP BY".to_string());
3524 }
3525 let ordered = query.order_by.as_ref().is_some_and(|order_by| {
3526 matches!(order_by.kind, OrderByKind::All(_))
3527 || matches!(&order_by.kind, OrderByKind::Expressions(exprs) if !exprs.is_empty())
3528 });
3529 if ordered {
3530 out.push("USE TEMP B-TREE FOR ORDER BY".to_string());
3531 }
3532 }
3533 SetExpr::Query(query) => collect(session, query, out),
3534 SetExpr::SetOperation {
3535 left, op, right, ..
3536 } => {
3537 let label = match op {
3538 SetOperator::Union => "COMPOUND QUERY UNION",
3539 SetOperator::Except => "COMPOUND QUERY EXCEPT",
3540 SetOperator::Intersect => "COMPOUND QUERY INTERSECT",
3541 _ => "COMPOUND QUERY",
3542 };
3543 out.push(label.to_string());
3544 collect_set_expr(session, left, out);
3545 collect_set_expr(session, right, out);
3546 }
3547 _ => {}
3548 }
3549 }
3550
3551 fn collect_set_expr(session: &MongrelSession, expr: &SetExpr, out: &mut Vec<String>) {
3552 match expr {
3553 SetExpr::Select(select) => {
3554 for table in &select.from {
3555 session.push_table_plan(&table.relation, select.selection.as_ref(), out);
3556 }
3557 }
3558 SetExpr::Query(query) => collect(session, query, out),
3559 SetExpr::SetOperation { left, right, .. } => {
3560 collect_set_expr(session, left, out);
3561 collect_set_expr(session, right, out);
3562 }
3563 _ => {}
3564 }
3565 }
3566
3567 let mut out = Vec::new();
3568 collect(self, query, &mut out);
3569 out
3570 }
3571
3572 fn push_table_plan(
3573 &self,
3574 relation: &sqlparser::ast::TableFactor,
3575 selection: Option<&sqlparser::ast::Expr>,
3576 out: &mut Vec<String>,
3577 ) {
3578 let sqlparser::ast::TableFactor::Table { name, alias, .. } = relation else {
3579 out.push("SCAN SUBQUERY".to_string());
3580 return;
3581 };
3582 let table_name = name.to_string();
3583 let display_name = alias
3584 .as_ref()
3585 .map(|alias| alias.name.value.clone())
3586 .unwrap_or_else(|| table_name.clone());
3587 let Some(handle) = self.tables.lock().get(&table_name).cloned() else {
3588 out.push(format!("SCAN {display_name}"));
3589 return;
3590 };
3591 let schema = handle.lock().schema().clone();
3592 let searchable = selection
3593 .and_then(|expr| translate_sqlparser_filter(expr, &schema))
3594 .is_some_and(|conditions| !conditions.is_empty());
3595 if searchable {
3596 out.push(format!("SEARCH {display_name} USING MONGREL INDEX"));
3597 } else {
3598 out.push(format!("SCAN {display_name}"));
3599 }
3600 }
3601
3602 fn resolve_view_sql(&self, sql: &str) -> String {
3607 let views = self.views.lock();
3608 if views.is_empty() {
3609 return sql.to_string();
3610 }
3611 let mut result = sql.to_string();
3612 for (name, view) in views.iter() {
3613 result = replace_from_view(&result, name, &view.sql);
3614 }
3615 result
3616 }
3617
3618 fn rewrite_external_module_compat_sql(&self, sql: &str) -> String {
3619 let Some(db) = &self.database else {
3620 return sql.to_string();
3621 };
3622 rewrite_fts_match_compat_sql(sql, db)
3623 }
3624
3625 fn query_references_external_module(&self, sql: &str) -> bool {
3626 use sqlparser::ast::Statement;
3627 use sqlparser::dialect::PostgreSqlDialect;
3628 use sqlparser::parser::Parser;
3629
3630 Parser::parse_sql(&PostgreSqlDialect {}, sql)
3631 .ok()
3632 .is_some_and(|statements| {
3633 statements.iter().any(|statement| match statement {
3634 Statement::Query(query) => self.query_uses_external_module(query),
3635 _ => false,
3636 })
3637 })
3638 }
3639
3640 fn query_uses_external_module(&self, query: &sqlparser::ast::Query) -> bool {
3641 self.set_expr_uses_external_module(query.body.as_ref())
3642 }
3643
3644 fn set_expr_uses_external_module(&self, expr: &sqlparser::ast::SetExpr) -> bool {
3645 use sqlparser::ast::SetExpr;
3646
3647 match expr {
3648 SetExpr::Select(select) => select
3649 .from
3650 .iter()
3651 .any(|table| self.table_with_joins_uses_external_module(table)),
3652 SetExpr::Query(query) => self.query_uses_external_module(query),
3653 SetExpr::SetOperation { left, right, .. } => {
3654 self.set_expr_uses_external_module(left)
3655 || self.set_expr_uses_external_module(right)
3656 }
3657 _ => false,
3658 }
3659 }
3660
3661 fn table_with_joins_uses_external_module(
3662 &self,
3663 table: &sqlparser::ast::TableWithJoins,
3664 ) -> bool {
3665 self.table_factor_uses_external_module(&table.relation)
3666 || table
3667 .joins
3668 .iter()
3669 .any(|join| self.table_factor_uses_external_module(&join.relation))
3670 }
3671
3672 fn table_factor_uses_external_module(&self, relation: &sqlparser::ast::TableFactor) -> bool {
3673 use sqlparser::ast::{Expr, TableFactor};
3674
3675 match relation {
3676 TableFactor::Table { name, args, .. } => {
3677 let table_name = name.to_string();
3678 self.database
3679 .as_ref()
3680 .is_some_and(|db| db.external_table(&table_name).is_some())
3681 || (args.is_some() && self.external_modules.contains(&table_name))
3682 }
3683 TableFactor::Function { name, .. } => self.external_modules.contains(&name.to_string()),
3684 TableFactor::TableFunction {
3685 expr: Expr::Function(func),
3686 ..
3687 } => self.external_modules.contains(&func.name.to_string()),
3688 TableFactor::Derived { subquery, .. } => self.query_uses_external_module(subquery),
3689 _ => false,
3690 }
3691 }
3692
3693 fn cache_epoch(&self) -> u64 {
3696 if let Some(db) = &self.database {
3697 db.visible_epoch().0
3698 } else {
3699 self.combined_epoch()
3700 }
3701 }
3702
3703 fn combined_epoch(&self) -> u64 {
3704 let primary = self.db.as_ref().expect("no primary table");
3705 let mut combined = primary.lock().snapshot().epoch.0;
3706 let tables = self.tables.lock();
3707 for arc in tables.values() {
3708 if !arc.ptr_eq(primary) {
3709 let e = arc.lock().snapshot().epoch.0;
3710 combined = combined.wrapping_mul(31).wrapping_add(e);
3711 }
3712 }
3713 combined
3714 }
3715
3716 fn try_native_aggregate(
3720 &self,
3721 plan: &datafusion::logical_expr::LogicalPlan,
3722 cache_key: u64,
3723 query: &RegisteredSqlQuery,
3724 ) -> Result<Option<RecordBatch>> {
3725 if self.security_context_active() {
3726 return Ok(None);
3727 }
3728 let Some(primary) = self.db.as_ref() else {
3729 return Ok(None);
3730 };
3731 let mut db = primary.lock();
3732 let schema = db.schema().clone();
3733 let snap = db.snapshot();
3734 native_agg::try_native_aggregate(&mut db, &schema, snap, plan, cache_key, Some(query))
3735 }
3736
3737 fn try_fk_join(
3741 &self,
3742 plan: &datafusion::logical_expr::LogicalPlan,
3743 query: &RegisteredSqlQuery,
3744 ) -> Result<Option<Vec<RecordBatch>>> {
3745 if self.security_context_active() {
3746 return Ok(None);
3747 }
3748 let tables = self.tables.lock();
3749 fk_join::try_fk_join(&tables, plan, Some(query))
3750 }
3751
3752 pub fn context(&self) -> &SessionContext {
3753 &self.ctx
3754 }
3755
3756 pub fn register_scalar_udf(&self, f: ScalarUDF) {
3762 self.ctx.register_udf(f);
3763 self.clear_cache();
3764 }
3765
3766 pub fn register_aggregate_udf(&self, f: AggregateUDF) {
3768 self.ctx.register_udaf(f);
3769 self.clear_cache();
3770 }
3771
3772 pub fn register_window_udf(&self, f: WindowUDF) {
3774 self.ctx.register_udwf(f);
3775 self.clear_cache();
3776 }
3777}
3778
3779fn register_mongrel_functions(
3780 ctx: &SessionContext,
3781 sql_fn_state: Arc<extended_sql_functions::ExtendedSqlState>,
3782) {
3783 ctx.register_udf(ScalarUDF::from(udf::AnnSearchUdf::new()));
3784 ctx.register_udf(ScalarUDF::from(udf::SparseMatchUdf::new()));
3785 ctx.register_udf(ScalarUDF::from(udf::RTreeIntersectsUdf::new()));
3786 ctx.register_udf(ScalarUDF::from(udf::FtsRankUdf::new()));
3787 for udaf in percentile::percentile_udafs() {
3788 ctx.register_udaf(udaf);
3789 }
3790 extended_sql_functions::register_extended_sql_functions_with_state(ctx, sql_fn_state);
3791}
3792
3793fn is_trigger_body(sql: &str) -> bool {
3796 let lower = sql.to_ascii_lowercase();
3797 if !lower.contains("create trigger") && !lower.contains("create or replace trigger") {
3798 return false;
3799 }
3800 lower.contains("begin") && lower.contains("end")
3801}
3802
3803fn split_sql_statements(sql: &str) -> Vec<String> {
3808 let b = sql.as_bytes();
3809 let n = b.len();
3810 let mut stmts = Vec::new();
3811 let mut current = String::new();
3812 let mut i = 0;
3813 while i < n {
3814 let c = b[i];
3815 if c == b'-' && i + 1 < n && b[i + 1] == b'-' {
3817 while i < n && b[i] != b'\n' {
3818 current.push(c as char);
3819 i += 1;
3820 }
3821 continue;
3822 }
3823 if c == b'/' && i + 1 < n && b[i + 1] == b'*' {
3825 current.push_str("/*");
3826 i += 2;
3827 let mut depth = 1;
3828 while i + 1 < n && depth > 0 {
3829 if b[i] == b'/' && b[i + 1] == b'*' {
3830 depth += 1;
3831 current.push_str("/*");
3832 i += 2;
3833 } else if b[i] == b'*' && b[i + 1] == b'/' {
3834 depth -= 1;
3835 current.push_str("*/");
3836 i += 2;
3837 } else {
3838 current.push(b[i] as char);
3839 i += 1;
3840 }
3841 }
3842 continue;
3843 }
3844 if c == b'\'' {
3846 current.push('\'');
3847 i += 1;
3848 while i < n {
3849 current.push(b[i] as char);
3850 if b[i] == b'\'' {
3851 if i + 1 < n && b[i + 1] == b'\'' {
3853 current.push('\'');
3854 i += 2;
3855 continue;
3856 }
3857 i += 1;
3858 break;
3859 }
3860 i += 1;
3861 }
3862 continue;
3863 }
3864 if c == b'"' {
3866 current.push('"');
3867 i += 1;
3868 while i < n {
3869 current.push(b[i] as char);
3870 if b[i] == b'"' {
3871 if i + 1 < n && b[i + 1] == b'"' {
3872 current.push('"');
3873 i += 2;
3874 continue;
3875 }
3876 i += 1;
3877 break;
3878 }
3879 i += 1;
3880 }
3881 continue;
3882 }
3883 if c == b'$' {
3885 let start = i;
3887 let mut tag_end = i + 1;
3888 while tag_end < n && b[tag_end] != b'$' && b[tag_end].is_ascii_alphanumeric() {
3889 tag_end += 1;
3890 }
3891 if tag_end < n && b[tag_end] == b'$' {
3892 let tag = &sql[start..=tag_end];
3893 current.push_str(tag);
3894 i = tag_end + 1;
3895 while i < n {
3897 if b[i] == b'$' && sql[i..].starts_with(tag) {
3898 current.push_str(tag);
3899 i += tag.len();
3900 break;
3901 }
3902 current.push(b[i] as char);
3903 i += 1;
3904 }
3905 continue;
3906 }
3907 }
3908 if c == b';' {
3910 current.push(';');
3911 let trimmed = current.trim();
3912 if !trimmed.is_empty() && trimmed != ";" {
3913 stmts.push(current.clone());
3914 }
3915 current.clear();
3916 i += 1;
3917 continue;
3918 }
3919 current.push(c as char);
3920 i += 1;
3921 }
3922 let trimmed = current.trim();
3924 if !trimmed.is_empty() {
3925 stmts.push(current);
3926 }
3927 stmts
3928}
3929
3930fn strip_explain_query_plan(sql: &str) -> Option<&str> {
3931 let trimmed = sql.trim_start();
3932 let lower = trimmed.to_ascii_lowercase();
3933 if !lower.starts_with("explain") {
3934 return None;
3935 }
3936 let after_explain = trimmed.get(7..)?.trim_start();
3937 let after_explain_lower = after_explain.to_ascii_lowercase();
3938 if !after_explain_lower.starts_with("query") {
3939 return None;
3940 }
3941 let after_query = after_explain.get(5..)?.trim_start();
3942 let after_query_lower = after_query.to_ascii_lowercase();
3943 if !after_query_lower.starts_with("plan") {
3944 return None;
3945 }
3946 Some(after_query.get(4..)?.trim_start())
3947}
3948
3949fn is_prepared_stmt_sql(sql: &str) -> bool {
3955 let lower = sql.trim_start().to_ascii_lowercase();
3956 lower.starts_with("prepare ")
3957 || lower.starts_with("execute ")
3958 || lower.starts_with("deallocate ")
3959}
3960
3961fn parse_stmt_ident(s: &str) -> Option<String> {
3965 let tok = s
3966 .trim_start()
3967 .split(|c: char| c.is_whitespace() || c == '(')
3968 .next()?;
3969 let t = tok.trim_matches(|c| c == '"' || c == '`' || c == '\'');
3970 (!t.is_empty()).then(|| t.to_string())
3971}
3972
3973fn is_explain_analyze(sql: &str) -> bool {
3978 let trimmed = sql.trim_start();
3979 let lower = trimmed.to_ascii_lowercase();
3980 if !lower.starts_with("explain") {
3981 return false;
3982 }
3983 let after = trimmed[7..].trim_start();
3984 after.to_ascii_lowercase().starts_with("analyze")
3985}
3986
3987#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3988enum SqlCompatTokenKind {
3989 Ident,
3990 String,
3991 Dot,
3992 LParen,
3993 RParen,
3994 Comma,
3995}
3996
3997#[derive(Debug, Clone)]
3998struct SqlCompatToken {
3999 kind: SqlCompatTokenKind,
4000 raw: String,
4001 normalized: String,
4002 start: usize,
4003 end: usize,
4004}
4005
4006#[derive(Debug, Clone)]
4007struct FtsMatchBinding {
4008 query_ref: String,
4009}
4010
4011#[derive(Debug, Clone)]
4012struct SqlReplacement {
4013 start: usize,
4014 end: usize,
4015 replacement: String,
4016}
4017
4018fn rewrite_fts_match_compat_sql(sql: &str, db: &Database) -> String {
4019 let tokens = sql_compat_tokens(sql);
4020 if tokens.is_empty() {
4021 return sql.to_string();
4022 }
4023 let bindings = fts_match_bindings(sql, db, &tokens);
4024 if bindings.is_empty() {
4025 return sql.to_string();
4026 }
4027 let unique_refs = bindings
4028 .values()
4029 .map(|binding| binding.query_ref.as_str())
4030 .collect::<HashSet<_>>();
4031 let unique_binding = if unique_refs.len() == 1 {
4032 bindings.values().next().cloned()
4033 } else {
4034 None
4035 };
4036 let mut replacements = Vec::new();
4037 for (idx, token) in tokens.iter().enumerate() {
4038 if token.kind != SqlCompatTokenKind::Ident || token.normalized != "match" {
4039 continue;
4040 }
4041 let Some(rhs) = tokens.get(idx + 1) else {
4042 continue;
4043 };
4044 if rhs.kind != SqlCompatTokenKind::String {
4045 continue;
4046 }
4047 let Some((lhs_start, _lhs_end, query_ref)) =
4048 fts_match_lhs_query_ref(&tokens, idx, &bindings, unique_binding.as_ref())
4049 else {
4050 continue;
4051 };
4052 replacements.push(SqlReplacement {
4053 start: lhs_start,
4054 end: rhs.end,
4055 replacement: format!("{query_ref}.query = {}", rhs.raw),
4056 });
4057 }
4058 apply_sql_replacements(sql, &replacements)
4059}
4060
4061fn fts_match_lhs_query_ref(
4062 tokens: &[SqlCompatToken],
4063 match_idx: usize,
4064 bindings: &HashMap<String, FtsMatchBinding>,
4065 unique_binding: Option<&FtsMatchBinding>,
4066) -> Option<(usize, usize, String)> {
4067 if match_idx == 0 {
4068 return None;
4069 }
4070 let lhs = tokens.get(match_idx - 1)?;
4071 if lhs.kind != SqlCompatTokenKind::Ident {
4072 return None;
4073 }
4074
4075 if match_idx >= 3
4076 && tokens.get(match_idx - 2)?.kind == SqlCompatTokenKind::Dot
4077 && tokens.get(match_idx - 3)?.kind == SqlCompatTokenKind::Ident
4078 {
4079 let owner = tokens.get(match_idx - 3)?;
4080 let binding = bindings.get(&owner.normalized)?;
4081 if lhs.normalized == "query" || lhs.normalized == "text" {
4082 return Some((owner.start, lhs.end, binding.query_ref.clone()));
4083 }
4084 return None;
4085 }
4086
4087 if let Some(binding) = bindings.get(&lhs.normalized) {
4088 return Some((lhs.start, lhs.end, binding.query_ref.clone()));
4089 }
4090 if lhs.normalized == "text" {
4091 let binding = unique_binding?;
4092 return Some((lhs.start, lhs.end, binding.query_ref.clone()));
4093 }
4094 None
4095}
4096
4097fn fts_match_bindings(
4098 sql: &str,
4099 db: &Database,
4100 tokens: &[SqlCompatToken],
4101) -> HashMap<String, FtsMatchBinding> {
4102 let mut out = HashMap::new();
4103 let mut i = 0;
4104 while i < tokens.len() {
4105 let token = &tokens[i];
4106 let starts_table_ref = token.kind == SqlCompatTokenKind::Ident
4107 && matches!(token.normalized.as_str(), "from" | "join");
4108 if !starts_table_ref {
4109 i += 1;
4110 continue;
4111 }
4112 let mut table_idx = i + 1;
4113 if tokens
4114 .get(table_idx)
4115 .is_some_and(|token| token.kind == SqlCompatTokenKind::LParen)
4116 {
4117 i += 1;
4118 continue;
4119 }
4120 let Some(table) = tokens.get(table_idx) else {
4121 break;
4122 };
4123 if table.kind != SqlCompatTokenKind::Ident {
4124 i += 1;
4125 continue;
4126 }
4127 let mut table_name = table.normalized.clone();
4128 let mut table_ref = table.raw.clone();
4129 if tokens
4130 .get(table_idx + 1)
4131 .is_some_and(|token| token.kind == SqlCompatTokenKind::Dot)
4132 && tokens
4133 .get(table_idx + 2)
4134 .is_some_and(|token| token.kind == SqlCompatTokenKind::Ident)
4135 {
4136 let qualified = tokens.get(table_idx + 2).unwrap();
4137 table_name = qualified.normalized.clone();
4138 table_ref = sql[table.start..qualified.end].to_string();
4139 table_idx += 2;
4140 }
4141 if !is_fts_docs_table(db, &table_name) {
4142 i = table_idx + 1;
4143 continue;
4144 }
4145 let mut query_ref = table_ref.clone();
4146 let mut alias_key = None;
4147 let mut next = table_idx + 1;
4148 if tokens.get(next).is_some_and(|token| {
4149 token.kind == SqlCompatTokenKind::Ident && token.normalized == "as"
4150 }) {
4151 next += 1;
4152 }
4153 if let Some(alias) = tokens.get(next) {
4154 if alias.kind == SqlCompatTokenKind::Ident && !is_table_ref_boundary(&alias.normalized)
4155 {
4156 alias_key = Some(alias.normalized.clone());
4157 query_ref = alias.raw.clone();
4158 next += 1;
4159 }
4160 }
4161 out.insert(
4162 table_name,
4163 FtsMatchBinding {
4164 query_ref: query_ref.clone(),
4165 },
4166 );
4167 if let Some(alias_key) = alias_key {
4168 out.insert(alias_key, FtsMatchBinding { query_ref });
4169 }
4170 i = next;
4171 }
4172 out
4173}
4174
4175fn is_fts_docs_table(db: &Database, name: &str) -> bool {
4176 db.external_table(name)
4177 .is_some_and(|entry| entry.module == "fts_docs")
4178}
4179
4180fn is_table_ref_boundary(normalized: &str) -> bool {
4181 matches!(
4182 normalized,
4183 "where"
4184 | "join"
4185 | "left"
4186 | "right"
4187 | "inner"
4188 | "outer"
4189 | "full"
4190 | "cross"
4191 | "on"
4192 | "using"
4193 | "group"
4194 | "order"
4195 | "having"
4196 | "limit"
4197 | "offset"
4198 | "union"
4199 | "except"
4200 | "intersect"
4201 )
4202}
4203
4204fn sql_compat_tokens(sql: &str) -> Vec<SqlCompatToken> {
4205 let bytes = sql.as_bytes();
4206 let mut tokens = Vec::new();
4207 let mut i = 0;
4208 while i < bytes.len() {
4209 match bytes[i] {
4210 b if b.is_ascii_whitespace() => i += 1,
4211 b'-' if i + 1 < bytes.len() && bytes[i + 1] == b'-' => {
4212 i += 2;
4213 while i < bytes.len() && bytes[i] != b'\n' {
4214 i += 1;
4215 }
4216 }
4217 b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => {
4218 i = skip_block_comment(bytes, i);
4219 }
4220 b'\'' => {
4221 let end = skip_quoted(bytes, i, b'\'');
4222 tokens.push(sql_token(sql, SqlCompatTokenKind::String, i, end));
4223 i = end;
4224 }
4225 b'E' | b'e' if i + 1 < bytes.len() && bytes[i + 1] == b'\'' => {
4226 let end = skip_quoted(bytes, i + 1, b'\'');
4227 tokens.push(sql_token(sql, SqlCompatTokenKind::String, i, end));
4228 i = end;
4229 }
4230 b'$' => {
4231 let (end, matched) = skip_dollar_quoted(bytes, i);
4232 if matched {
4233 tokens.push(sql_token(sql, SqlCompatTokenKind::String, i, end));
4234 i = end;
4235 } else {
4236 i += 1;
4237 }
4238 }
4239 b'"' => {
4240 let end = skip_quoted(bytes, i, b'"');
4241 let raw = sql[i..end].to_string();
4242 let normalized = unquote_sql_ident(&raw).to_ascii_lowercase();
4243 tokens.push(SqlCompatToken {
4244 kind: SqlCompatTokenKind::Ident,
4245 raw,
4246 normalized,
4247 start: i,
4248 end,
4249 });
4250 i = end;
4251 }
4252 b'.' => {
4253 tokens.push(sql_token(sql, SqlCompatTokenKind::Dot, i, i + 1));
4254 i += 1;
4255 }
4256 b'(' => {
4257 tokens.push(sql_token(sql, SqlCompatTokenKind::LParen, i, i + 1));
4258 i += 1;
4259 }
4260 b')' => {
4261 tokens.push(sql_token(sql, SqlCompatTokenKind::RParen, i, i + 1));
4262 i += 1;
4263 }
4264 b',' => {
4265 tokens.push(sql_token(sql, SqlCompatTokenKind::Comma, i, i + 1));
4266 i += 1;
4267 }
4268 b if is_sql_ident_byte(b) => {
4269 let start = i;
4270 i += 1;
4271 while i < bytes.len() && is_sql_ident_byte(bytes[i]) {
4272 i += 1;
4273 }
4274 tokens.push(sql_token(sql, SqlCompatTokenKind::Ident, start, i));
4275 }
4276 _ => i += 1,
4277 }
4278 }
4279 tokens
4280}
4281
4282fn sql_token(sql: &str, kind: SqlCompatTokenKind, start: usize, end: usize) -> SqlCompatToken {
4283 let raw = sql[start..end].to_string();
4284 SqlCompatToken {
4285 kind,
4286 normalized: raw.to_ascii_lowercase(),
4287 raw,
4288 start,
4289 end,
4290 }
4291}
4292
4293fn unquote_sql_ident(raw: &str) -> String {
4294 if raw.len() >= 2 && raw.starts_with('"') && raw.ends_with('"') {
4295 raw[1..raw.len() - 1].replace("\"\"", "\"")
4296 } else {
4297 raw.to_string()
4298 }
4299}
4300
4301fn apply_sql_replacements(sql: &str, replacements: &[SqlReplacement]) -> String {
4302 if replacements.is_empty() {
4303 return sql.to_string();
4304 }
4305 let mut ordered = replacements.to_vec();
4306 ordered.sort_by_key(|replacement| replacement.start);
4307 let mut out = String::with_capacity(sql.len());
4308 let mut cursor = 0;
4309 for replacement in ordered {
4310 if replacement.start < cursor || replacement.end > sql.len() {
4311 continue;
4312 }
4313 out.push_str(&sql[cursor..replacement.start]);
4314 out.push_str(&replacement.replacement);
4315 cursor = replacement.end;
4316 }
4317 out.push_str(&sql[cursor..]);
4318 out
4319}
4320
4321fn rewrite_compat_function_calls(sql: &str) -> String {
4322 let bytes = sql.as_bytes();
4323 let mut out = String::with_capacity(sql.len());
4324 let mut i = 0;
4325 while i < bytes.len() {
4326 match bytes[i] {
4327 b'\'' => i = copy_quoted_to_string(&mut out, bytes, i, b'\''),
4328 b'"' => i = copy_quoted_to_string(&mut out, bytes, i, b'"'),
4329 b'E' | b'e' if i + 1 < bytes.len() && bytes[i + 1] == b'\'' => {
4330 out.push(bytes[i] as char);
4331 i += 1;
4332 i = copy_quoted_to_string(&mut out, bytes, i, b'\'');
4333 }
4334 b'-' if i + 1 < bytes.len() && bytes[i + 1] == b'-' => {
4335 out.push('-');
4336 out.push('-');
4337 i += 2;
4338 while i < bytes.len() {
4339 let ch = bytes[i] as char;
4340 out.push(ch);
4341 i += 1;
4342 if ch == '\n' {
4343 break;
4344 }
4345 }
4346 }
4347 b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => {
4348 let start = i;
4349 i = skip_block_comment(bytes, i);
4350 out.push_str(&sql[start..i.min(bytes.len())]);
4351 }
4352 b'$' => {
4353 let start_len = out.len();
4354 let (next, matched) = copy_dollar_quoted_to_string(&mut out, bytes, i);
4355 if matched {
4356 i = next;
4357 } else {
4358 out.truncate(start_len);
4359 out.push('$');
4360 i += 1;
4361 }
4362 }
4363 b'g' | b'G' | b'm' | b'M' | b't' | b'T' => {
4364 if let Some((replacement, next)) = compat_function_rewrite_at(sql, i) {
4365 out.push_str(&replacement);
4366 i = next;
4367 } else {
4368 out.push(bytes[i] as char);
4369 i += 1;
4370 }
4371 }
4372 _ => {
4373 out.push(bytes[i] as char);
4374 i += 1;
4375 }
4376 }
4377 }
4378 out
4379}
4380
4381fn compat_function_rewrite_at(sql: &str, start: usize) -> Option<(String, usize)> {
4382 let bytes = sql.as_bytes();
4383 let (name, kind) = if ident_eq_at(bytes, start, b"max") {
4384 ("max", CompatRewriteKind::ScalarMax)
4385 } else if ident_eq_at(bytes, start, b"min") {
4386 ("min", CompatRewriteKind::ScalarMin)
4387 } else if ident_eq_at(bytes, start, b"group_concat") {
4388 ("group_concat", CompatRewriteKind::GroupConcat)
4389 } else if ident_eq_at(bytes, start, b"total") {
4390 ("total", CompatRewriteKind::Total)
4391 } else {
4392 return None;
4393 };
4394 let before_ok = start == 0 || !is_sql_ident_byte(bytes[start - 1]);
4395 let after_name = start + name.len();
4396 let after_ok = bytes
4397 .get(after_name)
4398 .is_some_and(|b| !is_sql_ident_byte(*b));
4399 if !before_ok || !after_ok {
4400 return None;
4401 }
4402 let mut open = after_name;
4403 while open < bytes.len() && bytes[open].is_ascii_whitespace() {
4404 open += 1;
4405 }
4406 if bytes.get(open) != Some(&b'(') {
4407 return None;
4408 }
4409 let summary = call_arg_summary(sql, open)?;
4410 match kind {
4411 CompatRewriteKind::ScalarMax if summary.top_level_commas > 0 => {
4412 Some(("__mongreldb_scalar_max(".to_string(), open + 1))
4413 }
4414 CompatRewriteKind::ScalarMin if summary.top_level_commas > 0 => {
4415 Some(("__mongreldb_scalar_min(".to_string(), open + 1))
4416 }
4417 CompatRewriteKind::GroupConcat => {
4418 let args = &sql[open + 1..summary.close];
4419 let rewritten = if summary.top_level_commas == 0 {
4420 format!("string_agg({args}, ',')")
4421 } else {
4422 format!("string_agg({args})")
4423 };
4424 Some((rewritten, summary.close + 1))
4425 }
4426 CompatRewriteKind::Total if summary.top_level_commas == 0 => {
4427 let args = &sql[open + 1..summary.close];
4428 let suffix_end = aggregate_suffix_end(sql, summary.close + 1);
4429 let suffix = &sql[summary.close + 1..suffix_end];
4430 Some((
4431 format!("coalesce(cast(sum({args}){suffix} as double), 0.0)"),
4432 suffix_end,
4433 ))
4434 }
4435 _ => None,
4436 }
4437}
4438
4439#[derive(Clone, Copy)]
4440enum CompatRewriteKind {
4441 ScalarMax,
4442 ScalarMin,
4443 GroupConcat,
4444 Total,
4445}
4446
4447fn ident_eq_at(bytes: &[u8], start: usize, ident: &[u8]) -> bool {
4448 bytes
4449 .get(start..start + ident.len())
4450 .is_some_and(|slice| slice.eq_ignore_ascii_case(ident))
4451}
4452
4453fn is_sql_ident_byte(b: u8) -> bool {
4454 b.is_ascii_alphanumeric() || b == b'_' || b == b'$'
4455}
4456
4457fn keyword_at(bytes: &[u8], start: usize, keyword: &[u8]) -> bool {
4458 if !ident_eq_at(bytes, start, keyword) {
4459 return false;
4460 }
4461 let before_ok = start == 0 || !is_sql_ident_byte(bytes[start - 1]);
4462 let after = start + keyword.len();
4463 let after_ok = after >= bytes.len() || !is_sql_ident_byte(bytes[after]);
4464 before_ok && after_ok
4465}
4466
4467fn skip_sql_whitespace(bytes: &[u8], mut i: usize) -> usize {
4468 while i < bytes.len() && bytes[i].is_ascii_whitespace() {
4469 i += 1;
4470 }
4471 i
4472}
4473
4474fn aggregate_suffix_end(sql: &str, start: usize) -> usize {
4475 let bytes = sql.as_bytes();
4476 let mut suffix_end = start;
4477 let mut i = skip_sql_whitespace(bytes, start);
4478
4479 if keyword_at(bytes, i, b"filter") {
4480 let open = skip_sql_whitespace(bytes, i + b"filter".len());
4481 if bytes.get(open) != Some(&b'(') {
4482 return start;
4483 }
4484 let Some(summary) = call_arg_summary(sql, open) else {
4485 return start;
4486 };
4487 suffix_end = summary.close + 1;
4488 i = skip_sql_whitespace(bytes, suffix_end);
4489 }
4490
4491 if keyword_at(bytes, i, b"over") {
4492 let after_over = skip_sql_whitespace(bytes, i + b"over".len());
4493 if bytes.get(after_over) == Some(&b'(') {
4494 let Some(summary) = call_arg_summary(sql, after_over) else {
4495 return suffix_end;
4496 };
4497 suffix_end = summary.close + 1;
4498 } else {
4499 let mut end = after_over;
4500 while end < bytes.len() && is_sql_ident_byte(bytes[end]) {
4501 end += 1;
4502 }
4503 if end > after_over {
4504 suffix_end = end;
4505 }
4506 }
4507 }
4508
4509 suffix_end
4510}
4511
4512struct CallArgSummary {
4513 close: usize,
4514 top_level_commas: usize,
4515}
4516
4517fn call_arg_summary(sql: &str, open: usize) -> Option<CallArgSummary> {
4518 let bytes = sql.as_bytes();
4519 let mut depth = 1;
4520 let mut i = open + 1;
4521 let mut top_level_commas = 0;
4522 while i < bytes.len() {
4523 match bytes[i] {
4524 b'\'' => i = skip_quoted(bytes, i, b'\''),
4525 b'"' => i = skip_quoted(bytes, i, b'"'),
4526 b'E' | b'e' if i + 1 < bytes.len() && bytes[i + 1] == b'\'' => {
4527 i = skip_quoted(bytes, i + 1, b'\'')
4528 }
4529 b'$' => {
4530 let (next, matched) = skip_dollar_quoted(bytes, i);
4531 i = if matched { next } else { i + 1 };
4532 }
4533 b'-' if i + 1 < bytes.len() && bytes[i + 1] == b'-' => {
4534 i += 2;
4535 while i < bytes.len() && bytes[i] != b'\n' {
4536 i += 1;
4537 }
4538 }
4539 b'/' if i + 1 < bytes.len() && bytes[i + 1] == b'*' => {
4540 i = skip_block_comment(bytes, i);
4541 }
4542 b'(' => {
4543 depth += 1;
4544 i += 1;
4545 }
4546 b')' => {
4547 depth -= 1;
4548 if depth == 0 {
4549 return Some(CallArgSummary {
4550 close: i,
4551 top_level_commas,
4552 });
4553 }
4554 i += 1;
4555 }
4556 b',' if depth == 1 => {
4557 top_level_commas += 1;
4558 i += 1;
4559 }
4560 _ => i += 1,
4561 }
4562 }
4563 None
4564}
4565
4566fn copy_quoted_to_string(out: &mut String, bytes: &[u8], start: usize, delim: u8) -> usize {
4567 let end = skip_quoted(bytes, start, delim);
4568 out.push_str(std::str::from_utf8(&bytes[start..end]).unwrap_or_default());
4569 end
4570}
4571
4572fn skip_quoted(bytes: &[u8], start: usize, delim: u8) -> usize {
4573 let mut i = start;
4574 if i < bytes.len() {
4575 i += 1;
4576 }
4577 while i < bytes.len() {
4578 if bytes[i] == delim {
4579 i += 1;
4580 if i < bytes.len() && bytes[i] == delim {
4581 i += 1;
4582 continue;
4583 }
4584 break;
4585 }
4586 i += 1;
4587 }
4588 i
4589}
4590
4591fn copy_dollar_quoted_to_string(out: &mut String, bytes: &[u8], start: usize) -> (usize, bool) {
4592 let (end, matched) = skip_dollar_quoted(bytes, start);
4593 if matched {
4594 out.push_str(std::str::from_utf8(&bytes[start..end]).unwrap_or_default());
4595 }
4596 (end, matched)
4597}
4598
4599fn skip_dollar_quoted(bytes: &[u8], start: usize) -> (usize, bool) {
4600 if bytes.get(start) != Some(&b'$') {
4601 return (start, false);
4602 }
4603 let mut j = start + 1;
4604 while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
4605 j += 1;
4606 }
4607 if bytes.get(j) != Some(&b'$') {
4608 return (start, false);
4609 }
4610 let tag = &bytes[start..=j];
4611 let mut i = j + 1;
4612 while i + tag.len() <= bytes.len() {
4613 if &bytes[i..i + tag.len()] == tag {
4614 return (i + tag.len(), true);
4615 }
4616 i += 1;
4617 }
4618 (start, false)
4619}
4620
4621fn skip_block_comment(bytes: &[u8], start: usize) -> usize {
4622 let mut i = start + 2;
4623 let mut depth = 1;
4624 while i + 1 < bytes.len() && depth > 0 {
4625 if bytes[i] == b'/' && bytes[i + 1] == b'*' {
4626 depth += 1;
4627 i += 2;
4628 } else if bytes[i] == b'*' && bytes[i + 1] == b'/' {
4629 depth -= 1;
4630 i += 2;
4631 } else {
4632 i += 1;
4633 }
4634 }
4635 i
4636}
4637
4638fn sql_cache_key(sql: &str) -> u64 {
4640 use std::hash::{Hash, Hasher};
4641 let mut h = std::collections::hash_map::DefaultHasher::new();
4642 sql.hash(&mut h);
4643 h.finish()
4644}
4645
4646fn replace_from_view(sql: &str, name: &str, view_sql: &str) -> String {
4653 let lower = sql.to_ascii_lowercase();
4654 let bytes = lower.as_bytes();
4655 let name_b = name.as_bytes();
4656 let mut i = 0usize;
4657 while let Some(rel) = lower[i..].find("from") {
4658 let from_start = i + rel;
4659 let after_from = from_start + 4;
4660 i = after_from;
4661 if from_start > 0 && is_ident_byte(bytes[from_start - 1]) {
4663 continue;
4664 }
4665 let mut j = after_from;
4667 while j < bytes.len() && bytes[j].is_ascii_whitespace() {
4668 j += 1;
4669 }
4670 if j == after_from || !bytes[j..].starts_with(name_b) {
4671 continue;
4672 }
4673 let after_name = j + name_b.len();
4674 if after_name < bytes.len() && is_ident_byte(bytes[after_name]) {
4676 continue;
4677 }
4678 let mut out = String::with_capacity(sql.len() + view_sql.len() + name.len() + 8);
4681 out.push_str(&sql[..from_start]);
4682 out.push_str(&sql[from_start..j]);
4683 out.push('(');
4684 out.push_str(view_sql);
4685 out.push_str(") AS ");
4686 out.push_str(name);
4687 out.push_str(&sql[after_name..]);
4688 return out;
4689 }
4690 sql.to_string()
4691}
4692
4693fn is_ident_byte(b: u8) -> bool {
4694 b.is_ascii_alphanumeric() || b == b'_'
4695}
4696
4697fn normalize_sql(sql: &str) -> String {
4707 let b = sql.as_bytes();
4708 let n = b.len();
4709 let mut out: Vec<u8> = Vec::with_capacity(n);
4710 let mut want_space = false;
4713 let mut i = 0usize;
4714 while i < n {
4715 let c = b[i];
4716 if c.is_ascii_whitespace() {
4720 want_space = true;
4721 i += 1;
4722 continue;
4723 }
4724 if c == b'-' && i + 1 < n && b[i + 1] == b'-' {
4725 i += 2;
4727 while i < n && b[i] != b'\n' {
4728 i += 1;
4729 }
4730 want_space = !out.is_empty();
4731 continue;
4732 }
4733 if c == b'/' && i + 1 < n && b[i + 1] == b'*' {
4734 i += 2;
4737 let mut depth = 1usize;
4738 while i + 1 < n && depth > 0 {
4739 if b[i] == b'/' && b[i + 1] == b'*' {
4740 depth += 1;
4741 i += 2;
4742 } else if b[i] == b'*' && b[i + 1] == b'/' {
4743 depth -= 1;
4744 i += 2;
4745 } else {
4746 i += 1;
4747 }
4748 }
4749 want_space = !out.is_empty();
4750 continue;
4751 }
4752 if want_space && !out.is_empty() {
4754 out.push(b' ');
4755 }
4756 want_space = false;
4757 match c {
4758 b'E' | b'e' if i + 1 < n && b[i + 1] == b'\'' => {
4760 out.push(c);
4761 i += 1;
4762 i = copy_quoted(&mut out, b, i, n, b'\'');
4763 continue;
4764 }
4765 b'\'' => {
4767 i = copy_quoted(&mut out, b, i, n, b'\'');
4768 continue;
4769 }
4770 b'"' => {
4772 i = copy_quoted(&mut out, b, i, n, b'"');
4773 continue;
4774 }
4775 b'$' => {
4777 let (consumed, matched) = copy_dollar_quoted(&mut out, b, i, n);
4778 if matched {
4779 i = consumed;
4780 continue;
4781 }
4782 out.push(c);
4783 i += 1;
4784 continue;
4785 }
4786 _ => {
4787 out.push(c);
4788 i += 1;
4789 }
4790 }
4791 }
4792 String::from_utf8(out).unwrap_or_else(|_| sql.to_string())
4793}
4794
4795fn copy_quoted(out: &mut Vec<u8>, b: &[u8], start: usize, n: usize, delim: u8) -> usize {
4799 out.push(b[start]);
4800 let mut i = start + 1;
4801 while i < n {
4802 let c = b[i];
4803 out.push(c);
4804 if c == delim {
4805 if i + 1 < n && b[i + 1] == delim {
4807 out.push(b[i + 1]);
4808 i += 2;
4809 continue;
4810 }
4811 return i + 1;
4812 }
4813 i += 1;
4814 }
4815 i
4816}
4817
4818fn copy_dollar_quoted(out: &mut Vec<u8>, b: &[u8], start: usize, n: usize) -> (usize, bool) {
4822 let mut j = start + 1;
4826 let tag_start = j;
4827 while j < n && b[j] != b'$' && is_dollar_tag_byte(b[j]) {
4828 j += 1;
4829 }
4830 if j >= n || b[j] != b'$' {
4831 return (start + 1, false);
4832 }
4833 if tag_start < j && !(b[tag_start].is_ascii_alphabetic() || b[tag_start] == b'_') {
4834 return (start + 1, false);
4835 }
4836 let close_end = j + 1; let delim = &b[start..close_end];
4838 out.extend_from_slice(delim);
4840 let mut k = close_end;
4842 while k + delim.len() <= n {
4843 if &b[k..k + delim.len()] == delim {
4844 out.extend_from_slice(delim);
4845 return (k + delim.len(), true);
4846 }
4847 out.push(b[k]);
4848 k += 1;
4849 }
4850 out.extend_from_slice(&b[close_end..n]);
4852 (n, true)
4853}
4854
4855fn is_dollar_tag_byte(b: u8) -> bool {
4856 b.is_ascii_alphanumeric() || b == b'_'
4857}
4858
4859fn strip_prefix_ci<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
4861 let bytes = s.as_bytes();
4862 let pb = prefix.as_bytes();
4863 if bytes.len() >= pb.len() && bytes[..pb.len()].eq_ignore_ascii_case(pb) {
4864 Some(&s[pb.len()..])
4865 } else {
4866 None
4867 }
4868}
4869
4870const COLUMN_CONSTRAINTS: &[(&str, u32)] = &[
4883 ("primary key", ColumnFlags::PRIMARY_KEY),
4884 ("autoincrement", ColumnFlags::AUTO_INCREMENT),
4891 ("auto_increment", ColumnFlags::AUTO_INCREMENT),
4892];
4893
4894fn parse_column_constraints(constraint_text: &str) -> ColumnFlags {
4900 let normalized = constraint_text.to_lowercase();
4901 let mut flags = ColumnFlags::empty();
4902 for (phrase, bit) in COLUMN_CONSTRAINTS {
4903 if normalized.contains(phrase) {
4904 flags = flags.with(*bit);
4905 }
4906 }
4907 flags
4908}
4909
4910fn parse_sql_type(ty_str: &str) -> Result<mongreldb_core::schema::TypeId> {
4911 use mongreldb_core::schema::TypeId;
4912
4913 match ty_str.trim().trim_end_matches(';').to_lowercase().as_str() {
4914 "bigint" | "int8" | "int64" | "integer" | "int" => Ok(TypeId::Int64),
4915 "double" | "float8" | "float64" | "real" | "float" => Ok(TypeId::Float64),
4916 "varchar" | "text" | "string" | "bytes" => Ok(TypeId::Bytes),
4917 "boolean" | "bool" => Ok(TypeId::Bool),
4918 other => Err(MongrelQueryError::Schema(format!(
4919 "unsupported column type: {other}"
4920 ))),
4921 }
4922}
4923
4924fn parse_create_table(sql: &str) -> Result<(String, mongreldb_core::schema::Schema)> {
4930 use mongreldb_core::schema::*;
4931
4932 let open = sql
4933 .find('(')
4934 .ok_or(MongrelQueryError::Schema("CREATE TABLE missing '('".into()))?;
4935 let close = sql
4936 .rfind(')')
4937 .ok_or(MongrelQueryError::Schema("CREATE TABLE missing ')'".into()))?;
4938 let head = sql[..open].trim();
4939 let after_kw = strip_prefix_ci(head, "CREATE TABLE")
4940 .or_else(|| strip_prefix_ci(head, "create table"))
4941 .unwrap_or("")
4942 .trim();
4943 let after_kw = after_kw
4945 .strip_prefix("IF NOT EXISTS")
4946 .or_else(|| after_kw.strip_prefix("if not exists"))
4947 .map(str::trim)
4948 .unwrap_or(after_kw);
4949 let name = after_kw.trim_matches('"').to_string();
4950 if name.is_empty() {
4951 return Err(MongrelQueryError::Schema(
4952 "CREATE TABLE missing table name".into(),
4953 ));
4954 }
4955
4956 let body = &sql[open + 1..close];
4957 let mut columns = Vec::new();
4958 let schema_id: u64 = 0; for (i, raw) in body.split(',').enumerate() {
4960 let part = raw.trim();
4961 if part.is_empty() {
4962 continue;
4963 }
4964 let mut tokens = part.split_whitespace();
4965 let col_name = tokens
4966 .next()
4967 .ok_or(MongrelQueryError::Schema("missing column name".into()))?
4968 .trim_matches('"');
4969 let ty_str = tokens
4970 .next()
4971 .ok_or(MongrelQueryError::Schema("missing column type".into()))?
4972 .to_lowercase();
4973 let ty = parse_sql_type(&ty_str)?;
4974 let constraint_clause: String = tokens.collect::<Vec<_>>().join(" ");
4978 let flags = parse_column_constraints(&constraint_clause);
4979 columns.push(ColumnDef {
4980 id: (i + 1) as u16,
4981 name: col_name.to_string(),
4982 ty,
4983 flags,
4984 default_value: None,
4985 });
4986 }
4987
4988 Ok((
4989 name,
4990 Schema {
4991 schema_id,
4992 columns,
4993 indexes: vec![],
4994 colocation: vec![],
4995 constraints: Default::default(),
4996 clustered: false,
4997 },
4998 ))
4999}
5000
5001fn parse_drop_table(sql: &str) -> Result<(String, bool)> {
5003 let head = sql.trim();
5004 let after_kw = strip_prefix_ci(head, "DROP TABLE")
5005 .or_else(|| strip_prefix_ci(head, "drop table"))
5006 .unwrap_or("")
5007 .trim();
5008 let (rest, if_exists) = if let Some(r) = after_kw
5010 .strip_prefix("IF EXISTS")
5011 .or_else(|| after_kw.strip_prefix("if exists"))
5012 .map(str::trim)
5013 {
5014 (r, true)
5015 } else {
5016 (after_kw, false)
5017 };
5018 let name = rest.trim_matches(';').trim_matches('"').trim();
5019 if name.is_empty() {
5020 return Err(MongrelQueryError::Schema(
5021 "DROP TABLE missing table name".into(),
5022 ));
5023 }
5024 Ok((name.to_string(), if_exists))
5025}
5026
5027enum ParsedAlterTable {
5028 RenameTable {
5029 old_name: String,
5030 new_name: String,
5031 },
5032 RenameColumn {
5033 table_name: String,
5034 column_name: String,
5035 new_name: String,
5036 },
5037 AlterColumnType {
5038 table_name: String,
5039 column_name: String,
5040 ty: mongreldb_core::schema::TypeId,
5041 },
5042 SetNotNull {
5043 table_name: String,
5044 column_name: String,
5045 },
5046 DropNotNull {
5047 table_name: String,
5048 column_name: String,
5049 },
5050}
5051
5052fn current_column_flags(db: &Arc<Database>, table: &str, column: &str) -> Result<ColumnFlags> {
5053 let handle = db.table(table)?;
5054 let table = handle.lock();
5055 table
5056 .schema()
5057 .column(column)
5058 .map(|c| c.flags)
5059 .ok_or_else(|| MongrelQueryError::Schema(format!("unknown column {column}")))
5060}
5061
5062fn parse_alter_table(sql: &str) -> Result<ParsedAlterTable> {
5063 let trimmed = strip_statement_semicolon(sql.trim());
5064 let after_kw = strip_prefix_ci(trimmed, "ALTER TABLE")
5065 .ok_or_else(|| MongrelQueryError::Schema("not an ALTER TABLE statement".into()))?
5066 .trim();
5067 let (table_name, rest) = take_sql_ident(after_kw, "ALTER TABLE missing table name")?;
5068 let rest = rest.trim();
5069
5070 if let Some(after) = strip_prefix_ci(rest, "RENAME TO") {
5071 let new_name = parse_trailing_identifier(after, "ALTER TABLE missing new table name")?;
5072 return Ok(ParsedAlterTable::RenameTable {
5073 old_name: table_name,
5074 new_name,
5075 });
5076 }
5077
5078 if let Some(after) = strip_prefix_ci(rest, "RENAME COLUMN") {
5079 let (column_name, after_col) =
5080 take_sql_ident(after, "ALTER TABLE RENAME COLUMN missing column name")?;
5081 let after_to = strip_prefix_ci(after_col.trim(), "TO").ok_or_else(|| {
5082 MongrelQueryError::Schema("ALTER TABLE RENAME COLUMN missing TO".into())
5083 })?;
5084 let new_name = parse_trailing_identifier(
5085 after_to,
5086 "ALTER TABLE RENAME COLUMN missing new column name",
5087 )?;
5088 return Ok(ParsedAlterTable::RenameColumn {
5089 table_name,
5090 column_name,
5091 new_name,
5092 });
5093 }
5094
5095 let after_alter = strip_prefix_ci(rest, "ALTER COLUMN")
5096 .or_else(|| strip_prefix_ci(rest, "ALTER"))
5097 .ok_or_else(|| {
5098 MongrelQueryError::Schema(
5099 "ALTER TABLE must be RENAME TO, RENAME COLUMN, or ALTER COLUMN".into(),
5100 )
5101 })?;
5102 let (column_name, action) =
5103 take_sql_ident(after_alter, "ALTER TABLE ALTER COLUMN missing column name")?;
5104 let action = action.trim();
5105
5106 if let Some(after_type) =
5107 strip_prefix_ci(action, "TYPE").or_else(|| strip_prefix_ci(action, "SET DATA TYPE"))
5108 {
5109 let ty = parse_type_tail(after_type)?;
5110 return Ok(ParsedAlterTable::AlterColumnType {
5111 table_name,
5112 column_name,
5113 ty,
5114 });
5115 }
5116 if strip_prefix_ci(action, "SET NOT NULL").is_some() {
5117 return Ok(ParsedAlterTable::SetNotNull {
5118 table_name,
5119 column_name,
5120 });
5121 }
5122 if strip_prefix_ci(action, "DROP NOT NULL").is_some() {
5123 return Ok(ParsedAlterTable::DropNotNull {
5124 table_name,
5125 column_name,
5126 });
5127 }
5128
5129 Err(MongrelQueryError::Schema(
5130 "unsupported ALTER COLUMN action".into(),
5131 ))
5132}
5133
5134fn strip_statement_semicolon(s: &str) -> &str {
5135 s.trim().trim_end_matches(';').trim()
5136}
5137
5138fn take_sql_ident<'a>(s: &'a str, missing: &str) -> Result<(String, &'a str)> {
5139 let s = s.trim();
5140 if s.is_empty() {
5141 return Err(MongrelQueryError::Schema(missing.into()));
5142 }
5143 if let Some(rest) = s.strip_prefix('"') {
5144 let Some(end) = rest.find('"') else {
5145 return Err(MongrelQueryError::Schema(
5146 "unterminated quoted identifier".into(),
5147 ));
5148 };
5149 let ident = rest[..end].to_string();
5150 if ident.is_empty() {
5151 return Err(MongrelQueryError::Schema(missing.into()));
5152 }
5153 return Ok((ident, &rest[end + 1..]));
5154 }
5155 let end = s.find(|c: char| c.is_ascii_whitespace()).unwrap_or(s.len());
5156 let ident = s[..end].trim_matches('"').to_string();
5157 if ident.is_empty() {
5158 return Err(MongrelQueryError::Schema(missing.into()));
5159 }
5160 Ok((ident, &s[end..]))
5161}
5162
5163fn parse_trailing_identifier(s: &str, missing: &str) -> Result<String> {
5164 let (ident, rest) = take_sql_ident(s, missing)?;
5165 if !strip_statement_semicolon(rest).is_empty() {
5166 return Err(MongrelQueryError::Schema(
5167 "unexpected tokens after identifier".into(),
5168 ));
5169 }
5170 Ok(ident)
5171}
5172
5173fn parse_type_tail(s: &str) -> Result<mongreldb_core::schema::TypeId> {
5174 let tail = strip_statement_semicolon(s);
5175 let ty = tail
5176 .split_whitespace()
5177 .next()
5178 .ok_or_else(|| MongrelQueryError::Schema("ALTER COLUMN TYPE missing type".into()))?;
5179 parse_sql_type(ty)
5180}
5181
5182#[cfg(test)]
5183mod tests {
5184 use super::*;
5185
5186 #[test]
5187 fn bounded_lru_evicts_least_recently_used() {
5188 let mut cache = BoundedLru::new(2);
5189 cache.insert("a", 1);
5190 cache.insert("b", 2);
5191 assert_eq!(cache.get("a"), Some(&1));
5192 cache.insert("c", 3);
5193 assert_eq!(cache.entries.len(), 2);
5194 assert_eq!(cache.get("b"), None);
5195 assert_eq!(cache.get("a"), Some(&1));
5196 assert_eq!(cache.get("c"), Some(&3));
5197 }
5198
5199 #[tokio::test]
5200 async fn streaming_query_bypasses_result_cache() {
5201 use futures::StreamExt;
5202
5203 let dir = tempfile::tempdir().unwrap();
5204 let database = Arc::new(Database::create(dir.path()).unwrap());
5205 let session = MongrelSession::open(database).unwrap();
5206 let mut stream = session.run_stream("SELECT 1 AS value").await.unwrap();
5207 let batch = stream.next().await.unwrap().unwrap();
5208 assert_eq!(batch.num_rows(), 1);
5209 assert!(stream.next().await.is_none());
5210 assert!(session.cache.lock().entries.is_empty());
5211 }
5212
5213 #[tokio::test]
5214 async fn ttl_tables_bypass_epoch_keyed_result_cache() {
5215 let dir = tempfile::tempdir().unwrap();
5216 let database = Arc::new(Database::create(dir.path()).unwrap());
5217 let session = MongrelSession::open(database).unwrap();
5218 session
5219 .run(
5220 "CREATE TABLE events (id BIGINT PRIMARY KEY, ts TIMESTAMP) \
5221 TTL_COLUMN ts TTL '1 day'",
5222 )
5223 .await
5224 .unwrap();
5225 session.run("SELECT * FROM events").await.unwrap();
5226 assert!(session.cache.lock().entries.is_empty());
5227 }
5228
5229 #[test]
5230 fn normalize_collapses_and_trims_whitespace() {
5231 assert_eq!(normalize_sql("SELECT * FROM t"), "SELECT * FROM t");
5232 assert_eq!(normalize_sql(" SELECT * FROM t "), "SELECT * FROM t");
5233 assert_eq!(
5234 normalize_sql("\n\tSELECT\n*\nFROM\n\tt\n"),
5235 "SELECT * FROM t"
5236 );
5237 assert_eq!(
5238 normalize_sql("SELECT a, b FROM t"),
5239 normalize_sql("SELECT a, b FROM t")
5240 );
5241 }
5242
5243 #[test]
5244 fn boolean_ai_predicate_detection_uses_sql_tokens() {
5245 assert!(contains_boolean_ai_predicate(
5246 "SELECT * FROM docs WHERE ANN_SEARCH (embedding, '[1]', 1)"
5247 ));
5248 assert!(contains_boolean_ai_predicate(
5249 "PREPARE q AS SELECT * FROM docs WHERE sparse_match(sparse, '[]', 1)"
5250 ));
5251 assert!(!contains_boolean_ai_predicate(
5252 "SELECT 'ann_search(x)', sparse_search_scored('docs', 'sparse', '[]', 1) /* ann_search(x) */"
5253 ));
5254 }
5255
5256 #[test]
5257 fn normalize_preserves_string_literal_whitespace() {
5258 assert_eq!(
5259 normalize_sql("SELECT 'hello world' FROM t"),
5260 "SELECT 'hello world' FROM t"
5261 );
5262 assert_eq!(
5263 normalize_sql("SELECT 'it''s ok' FROM t"),
5264 "SELECT 'it''s ok' FROM t"
5265 );
5266 assert_eq!(
5267 normalize_sql(" SELECT 'a b' FROM t "),
5268 "SELECT 'a b' FROM t"
5269 );
5270 }
5271
5272 #[test]
5273 fn normalize_preserves_quoted_identifier_and_dollar_quote() {
5274 assert_eq!(
5275 normalize_sql(" SELECT \"my col\" FROM t "),
5276 "SELECT \"my col\" FROM t"
5277 );
5278 assert_eq!(
5279 normalize_sql(" SELECT $$a b$$ FROM t "),
5280 "SELECT $$a b$$ FROM t"
5281 );
5282 assert_eq!(
5283 normalize_sql("SELECT $tag$body with spaces$tag$ FROM t"),
5284 "SELECT $tag$body with spaces$tag$ FROM t"
5285 );
5286 }
5287
5288 #[test]
5289 fn normalize_strips_comments() {
5290 assert_eq!(
5291 normalize_sql("SELECT 1 -- trailing comment\nFROM t"),
5292 "SELECT 1 FROM t"
5293 );
5294 assert_eq!(
5295 normalize_sql("SELECT /* block */ 1 FROM t"),
5296 "SELECT 1 FROM t"
5297 );
5298 assert_eq!(
5300 normalize_sql("SELECT /* 'not a string' */ 1 FROM t"),
5301 "SELECT 1 FROM t"
5302 );
5303 assert_eq!(
5305 normalize_sql("SELECT /* outer /* inner */ still outer */ 1 FROM t"),
5306 "SELECT 1 FROM t"
5307 );
5308 }
5309
5310 #[test]
5311 fn normalize_escape_string_preserved() {
5312 assert_eq!(
5313 normalize_sql("SELECT E'line\\nbreak' FROM t"),
5314 "SELECT E'line\\nbreak' FROM t"
5315 );
5316 }
5317
5318 #[test]
5319 fn replace_from_view_matches_whole_word_only() {
5320 let out = replace_from_view("SELECT * FROM logs", "log", "SELECT 1");
5321 assert_eq!(out, "SELECT * FROM logs");
5322
5323 let out = replace_from_view("SELECT * FROM log", "log", "SELECT 1");
5324 assert_eq!(out, "SELECT * FROM (SELECT 1) AS log");
5325
5326 let out = replace_from_view("select * from log where x", "log", "SELECT 1");
5327 assert_eq!(out, "select * from (SELECT 1) AS log where x");
5328
5329 let out = replace_from_view("SELECT * FROM log)", "log", "SELECT 1");
5330 assert_eq!(out, "SELECT * FROM (SELECT 1) AS log)");
5331
5332 let out = replace_from_view("SELECT * xfrom log", "log", "SELECT 1");
5333 assert_eq!(out, "SELECT * xfrom log");
5334 }
5335
5336 #[test]
5337 fn compat_function_rewrite_handles_sqlite_compatibility_calls() {
5338 assert_eq!(
5339 rewrite_compat_function_calls("select max(id), min(id) from t"),
5340 "select max(id), min(id) from t"
5341 );
5342 assert_eq!(
5343 rewrite_compat_function_calls("select max(1, min(2, 3), 'max(4,5)')"),
5344 "select __mongreldb_scalar_max(1, __mongreldb_scalar_min(2, 3), 'max(4,5)')"
5345 );
5346 assert_eq!(
5347 rewrite_compat_function_calls("select /* max(1,2) */ min(1, (2 + 3))"),
5348 "select /* max(1,2) */ __mongreldb_scalar_min(1, (2 + 3))"
5349 );
5350 assert_eq!(
5351 rewrite_compat_function_calls("select max_value, min_value from t"),
5352 "select max_value, min_value from t"
5353 );
5354 assert_eq!(
5355 rewrite_compat_function_calls(
5356 "select group_concat(label), group_concat(label, '|') from t"
5357 ),
5358 "select string_agg(label, ','), string_agg(label, '|') from t"
5359 );
5360 assert_eq!(
5361 rewrite_compat_function_calls("select total(val), total(val) filter (where grp = 2) from t"),
5362 "select coalesce(cast(sum(val) as double), 0.0), coalesce(cast(sum(val) filter (where grp = 2) as double), 0.0) from t"
5363 );
5364 assert_eq!(
5365 rewrite_compat_function_calls(
5366 "select total(val) over (partition by grp order by id) from t"
5367 ),
5368 "select coalesce(cast(sum(val) over (partition by grp order by id) as double), 0.0) from t"
5369 );
5370 }
5371}