1#![no_std]
6
7extern crate alloc;
8
9pub mod aggregate;
10pub mod copy;
11pub mod describe;
12pub mod eval;
13pub mod fts;
14pub mod json;
15pub mod memoize;
16pub mod plan_cache;
17pub mod publications;
18pub mod query_stats;
19pub mod reorder;
20pub mod selectivity;
21pub mod statistics;
22pub mod subscriptions;
23pub mod triggers;
24pub mod users;
25
26pub use crate::users::{Role, ScramSecrets, UserError, UserStore};
27
28use alloc::borrow::Cow;
29use alloc::boxed::Box;
30use alloc::collections::BTreeMap;
31use alloc::string::{String, ToString};
32use alloc::vec::Vec;
33use core::fmt;
34
35use spg_sql::ast::{
36 BinOp, ColumnDef, ColumnName, ColumnTypeName, CreateIndexStatement, CreatePublicationStatement,
37 CreateSubscriptionStatement, CreateTableStatement, CreateUserStatement, Expr, FrameBound,
38 FrameKind, FromClause, IndexMethod, InsertStatement, JoinKind, Literal, OrderBy, SelectItem,
39 SelectStatement, Statement, TableRef, UnOp, UnionKind, VecEncoding as SqlVecEncoding,
40 WindowFrame,
41};
42pub use spg_sql::ast::Statement as ParsedStatement;
46use spg_sql::parser::{self, ParseError};
47use spg_storage::{
48 Catalog, ColumnSchema, CompactReport, DataType, IndexKey, IndexKind, Row, StorageError, Table,
49 TableSchema, Value, VecEncoding,
50};
51
52use crate::eval::{EvalContext, EvalError};
53
54#[derive(Debug, Clone, PartialEq)]
56#[non_exhaustive]
57pub enum QueryResult {
58 CommandOk {
67 affected: usize,
68 modified_catalog: bool,
69 },
70 Rows {
72 columns: Vec<ColumnSchema>,
73 rows: Vec<Row>,
74 },
75}
76
77#[derive(Debug, Clone, PartialEq)]
83#[non_exhaustive]
84pub enum EngineError {
85 Parse(ParseError),
86 Storage(StorageError),
87 Eval(EvalError),
88 Unsupported(String),
90 TransactionAlreadyOpen,
92 NoActiveTransaction,
94 WriteRequired,
99 RowLimitExceeded(usize),
102 Cancelled,
108}
109
110impl fmt::Display for EngineError {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::Parse(e) => write!(f, "parse: {e}"),
114 Self::Storage(e) => write!(f, "storage: {e}"),
115 Self::Eval(e) => write!(f, "eval: {e}"),
116 Self::Unsupported(s) => write!(f, "unsupported: {s}"),
117 Self::TransactionAlreadyOpen => f.write_str("a transaction is already open"),
118 Self::NoActiveTransaction => f.write_str("no active transaction"),
119 Self::WriteRequired => {
120 f.write_str("statement requires a write lock (use execute, not execute_readonly)")
121 }
122 Self::RowLimitExceeded(n) => {
123 write!(f, "query exceeded max_query_rows={n}")
124 }
125 Self::Cancelled => f.write_str("query cancelled (timeout or client request)"),
126 }
127 }
128}
129
130impl From<ParseError> for EngineError {
131 fn from(e: ParseError) -> Self {
132 Self::Parse(e)
133 }
134}
135impl From<StorageError> for EngineError {
136 fn from(e: StorageError) -> Self {
137 Self::Storage(e)
138 }
139}
140impl From<EvalError> for EngineError {
141 fn from(e: EvalError) -> Self {
142 Self::Eval(e)
143 }
144}
145
146pub type ClockFn = fn() -> i64;
155
156pub type SaltFn = fn() -> [u8; 16];
163
164pub type MonotonicNowFn = fn() -> u64;
180
181#[derive(Debug, Clone, Copy)]
182struct Deadline {
183 now_fn: MonotonicNowFn,
184 deadline_us: u64,
186}
187
188#[derive(Debug, Clone, Copy)]
189pub struct CancelToken<'a> {
190 flag: Option<&'a core::sync::atomic::AtomicBool>,
191 deadline: Option<Deadline>,
198}
199
200impl<'a> CancelToken<'a> {
201 #[must_use]
202 pub const fn none() -> Self {
203 Self {
204 flag: None,
205 deadline: None,
206 }
207 }
208
209 #[must_use]
210 pub const fn from_flag(f: &'a core::sync::atomic::AtomicBool) -> Self {
211 Self {
212 flag: Some(f),
213 deadline: None,
214 }
215 }
216
217 #[must_use]
225 pub const fn with_deadline(mut self, now_fn: MonotonicNowFn, deadline_us: u64) -> Self {
226 self.deadline = Some(Deadline {
227 now_fn,
228 deadline_us,
229 });
230 self
231 }
232
233 #[must_use]
234 pub fn is_cancelled(self) -> bool {
235 if self
236 .flag
237 .is_some_and(|f| f.load(core::sync::atomic::Ordering::Relaxed))
238 {
239 return true;
240 }
241 if let Some(d) = self.deadline
245 && (d.now_fn)() >= d.deadline_us
246 {
247 return true;
248 }
249 false
250 }
251
252 #[inline]
256 pub fn check(self) -> Result<(), EngineError> {
257 if self.is_cancelled() {
258 Err(EngineError::Cancelled)
259 } else {
260 Ok(())
261 }
262 }
263}
264
265const ENVELOPE_MAGIC: &[u8; 8] = b"SPGENV01";
323const ENVELOPE_VERSION_V1: u8 = 1;
324const ENVELOPE_VERSION_V2: u8 = 2;
325const ENVELOPE_VERSION_V3: u8 = 3;
326const ENVELOPE_VERSION_V4: u8 = 4;
327const ENVELOPE_VERSION_V5: u8 = 5;
328
329fn build_envelope(catalog: &[u8], users: &[u8], pubs: &[u8], subs: &[u8], stats: &[u8]) -> Vec<u8> {
330 let mut out = Vec::with_capacity(
331 8 + 1
332 + 4
333 + catalog.len()
334 + 4
335 + users.len()
336 + 4
337 + pubs.len()
338 + 4
339 + subs.len()
340 + 4
341 + stats.len()
342 + 4,
343 );
344 out.extend_from_slice(ENVELOPE_MAGIC);
345 out.push(ENVELOPE_VERSION_V5);
346 out.extend_from_slice(
347 &u32::try_from(catalog.len())
348 .expect("≤ 4G catalog")
349 .to_le_bytes(),
350 );
351 out.extend_from_slice(catalog);
352 out.extend_from_slice(
353 &u32::try_from(users.len())
354 .expect("≤ 4G users")
355 .to_le_bytes(),
356 );
357 out.extend_from_slice(users);
358 out.extend_from_slice(
359 &u32::try_from(pubs.len())
360 .expect("≤ 4G publications")
361 .to_le_bytes(),
362 );
363 out.extend_from_slice(pubs);
364 out.extend_from_slice(
365 &u32::try_from(subs.len())
366 .expect("≤ 4G subscriptions")
367 .to_le_bytes(),
368 );
369 out.extend_from_slice(subs);
370 out.extend_from_slice(
371 &u32::try_from(stats.len())
372 .expect("≤ 4G statistics")
373 .to_le_bytes(),
374 );
375 out.extend_from_slice(stats);
376 let crc = spg_crypto::crc32::crc32(&out);
377 out.extend_from_slice(&crc.to_le_bytes());
378 out
379}
380
381enum EnvelopeParse<'a> {
388 Bare,
389 Pair {
390 catalog: &'a [u8],
391 users: &'a [u8],
392 publications: Option<&'a [u8]>,
393 subscriptions: Option<&'a [u8]>,
394 statistics: Option<&'a [u8]>,
395 },
396 CrcMismatch {
397 expected: u32,
398 computed: u32,
399 },
400}
401
402fn split_envelope(buf: &[u8]) -> EnvelopeParse<'_> {
407 if buf.len() < 8 + 1 + 4 || &buf[..8] != ENVELOPE_MAGIC {
408 return EnvelopeParse::Bare;
409 }
410 let version = buf[8];
411 if !matches!(
412 version,
413 ENVELOPE_VERSION_V1
414 | ENVELOPE_VERSION_V2
415 | ENVELOPE_VERSION_V3
416 | ENVELOPE_VERSION_V4
417 | ENVELOPE_VERSION_V5
418 ) {
419 return EnvelopeParse::Bare;
420 }
421 let mut p = 9usize;
422 let Some(cat_len_bytes) = buf.get(p..p + 4) else {
423 return EnvelopeParse::Bare;
424 };
425 let Ok(cat_len_arr) = cat_len_bytes.try_into() else {
426 return EnvelopeParse::Bare;
427 };
428 let cat_len = u32::from_le_bytes(cat_len_arr) as usize;
429 p += 4;
430 if p + cat_len + 4 > buf.len() {
431 return EnvelopeParse::Bare;
432 }
433 let catalog = &buf[p..p + cat_len];
434 p += cat_len;
435 let Some(user_len_bytes) = buf.get(p..p + 4) else {
436 return EnvelopeParse::Bare;
437 };
438 let Ok(user_len_arr) = user_len_bytes.try_into() else {
439 return EnvelopeParse::Bare;
440 };
441 let user_len = u32::from_le_bytes(user_len_arr) as usize;
442 p += 4;
443 if p + user_len > buf.len() {
444 return EnvelopeParse::Bare;
445 }
446 let users = &buf[p..p + user_len];
447 p += user_len;
448 let publications = if matches!(
449 version,
450 ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
451 ) {
452 let Some(pubs_len_bytes) = buf.get(p..p + 4) else {
454 return EnvelopeParse::Bare;
455 };
456 let Ok(pubs_len_arr) = pubs_len_bytes.try_into() else {
457 return EnvelopeParse::Bare;
458 };
459 let pubs_len = u32::from_le_bytes(pubs_len_arr) as usize;
460 p += 4;
461 if p + pubs_len > buf.len() {
462 return EnvelopeParse::Bare;
463 }
464 let pubs_slice = &buf[p..p + pubs_len];
465 p += pubs_len;
466 Some(pubs_slice)
467 } else {
468 None
469 };
470 let subscriptions = if matches!(version, ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5) {
471 let Some(subs_len_bytes) = buf.get(p..p + 4) else {
473 return EnvelopeParse::Bare;
474 };
475 let Ok(subs_len_arr) = subs_len_bytes.try_into() else {
476 return EnvelopeParse::Bare;
477 };
478 let subs_len = u32::from_le_bytes(subs_len_arr) as usize;
479 p += 4;
480 if p + subs_len > buf.len() {
481 return EnvelopeParse::Bare;
482 }
483 let subs_slice = &buf[p..p + subs_len];
484 p += subs_len;
485 Some(subs_slice)
486 } else {
487 None
488 };
489 let statistics = if version == ENVELOPE_VERSION_V5 {
490 let Some(stats_len_bytes) = buf.get(p..p + 4) else {
492 return EnvelopeParse::Bare;
493 };
494 let Ok(stats_len_arr) = stats_len_bytes.try_into() else {
495 return EnvelopeParse::Bare;
496 };
497 let stats_len = u32::from_le_bytes(stats_len_arr) as usize;
498 p += 4;
499 if p + stats_len > buf.len() {
500 return EnvelopeParse::Bare;
501 }
502 let stats_slice = &buf[p..p + stats_len];
503 p += stats_len;
504 Some(stats_slice)
505 } else {
506 None
507 };
508 if matches!(
509 version,
510 ENVELOPE_VERSION_V2 | ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
511 ) {
512 if p + 4 != buf.len() {
513 return EnvelopeParse::Bare;
514 }
515 let Ok(crc_arr) = buf[p..p + 4].try_into() else {
516 return EnvelopeParse::Bare;
517 };
518 let expected = u32::from_le_bytes(crc_arr);
519 let computed = spg_crypto::crc32::crc32(&buf[..p]);
520 if expected != computed {
521 return EnvelopeParse::CrcMismatch { expected, computed };
522 }
523 } else if p != buf.len() {
524 return EnvelopeParse::Bare;
526 }
527 EnvelopeParse::Pair {
528 catalog,
529 users,
530 publications,
531 subscriptions,
532 statistics,
533 }
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
546pub struct TxId(pub u64);
547
548pub const IMPLICIT_TX: TxId = TxId(0);
551
552pub const COMPACTION_TARGET_DEFAULT_BYTES: u64 = 4 * 1024 * 1024;
558
559#[derive(Debug, Default, Clone)]
564struct TxState {
565 catalog: Catalog,
570 savepoints: Vec<(String, Catalog)>,
576}
577
578#[derive(Debug, Clone)]
592pub struct CatalogSnapshot {
593 catalog: Catalog,
594 statistics: statistics::Statistics,
595 clock: Option<ClockFn>,
596 max_query_rows: Option<usize>,
597}
598
599#[derive(Debug, Default)]
600pub struct Engine {
601 catalog: Catalog,
604 tx_catalogs: BTreeMap<TxId, TxState>,
609 current_tx: Option<TxId>,
614 next_tx_id: u64,
617 backslash_escapes: bool,
626 clock: Option<ClockFn>,
629 salt_fn: Option<SaltFn>,
633 max_query_rows: Option<usize>,
639 users: UserStore,
645 publications: publications::Publications,
649 subscriptions: subscriptions::Subscriptions,
653 statistics: statistics::Statistics,
657 plan_cache: plan_cache::PlanCache,
661 query_stats: query_stats::QueryStats,
665 activity_provider: Option<ActivityProvider>,
672 audit_chain_provider: Option<AuditChainProvider>,
677 audit_verifier: Option<AuditVerifier>,
678 slow_query_threshold_us: Option<u64>,
684 slow_query_logger: Option<SlowQueryLogger>,
685 session_params: BTreeMap<String, String>,
692 trigger_recursion_depth: u32,
700 foreign_key_checks: bool,
709 meta_views_materialised: bool,
718 pending_foreign_keys: Vec<(alloc::string::String, spg_sql::ast::ForeignKeyConstraint)>,
719}
720
721const MAX_TRIGGER_RECURSION: u32 = 16;
725
726pub type SlowQueryLogger = fn(&str, u64);
730
731fn render_create_table(name: &str, columns: &[ColumnSchema]) -> String {
736 let mut out = alloc::format!("CREATE TABLE {name} (");
737 for (i, col) in columns.iter().enumerate() {
738 if i > 0 {
739 out.push_str(", ");
740 }
741 out.push_str(&col.name);
742 out.push(' ');
743 out.push_str(&render_data_type(col.ty));
744 if !col.nullable {
745 out.push_str(" NOT NULL");
746 }
747 if col.auto_increment {
748 out.push_str(" AUTO_INCREMENT");
749 }
750 }
751 out.push(')');
752 out
753}
754
755fn render_data_type(ty: DataType) -> String {
756 match ty {
757 DataType::SmallInt => "SMALLINT".into(),
758 DataType::Int => "INT".into(),
759 DataType::BigInt => "BIGINT".into(),
760 DataType::Float => "FLOAT".into(),
761 DataType::Text => "TEXT".into(),
762 DataType::Varchar(n) => alloc::format!("VARCHAR({n})"),
763 DataType::Char(n) => alloc::format!("CHAR({n})"),
764 DataType::Bool => "BOOL".into(),
765 DataType::Vector { dim, encoding } => match encoding {
766 spg_storage::VecEncoding::F32 => alloc::format!("VECTOR({dim})"),
767 spg_storage::VecEncoding::Sq8 => alloc::format!("VECTOR({dim}) USING SQ8"),
768 spg_storage::VecEncoding::F16 => alloc::format!("VECTOR({dim}) USING HALF"),
769 },
770 DataType::Numeric { precision, scale } => {
771 alloc::format!("NUMERIC({precision},{scale})")
772 }
773 DataType::Date => "DATE".into(),
774 DataType::Timestamp => "TIMESTAMP".into(),
775 DataType::Interval => "INTERVAL".into(),
776 DataType::Json => "JSON".into(),
777 DataType::Jsonb => "JSONB".into(),
778 DataType::Timestamptz => "TIMESTAMPTZ".into(),
779 DataType::Bytes => "BYTEA".into(),
780 DataType::TextArray => "TEXT[]".into(),
781 DataType::IntArray => "INT[]".into(),
782 DataType::BigIntArray => "BIGINT[]".into(),
783 DataType::TsVector => "TSVECTOR".into(),
784 DataType::TsQuery => "TSQUERY".into(),
785 DataType::Uuid => "UUID".into(),
786 DataType::Time => "TIME".into(),
787 DataType::Year => "YEAR".into(),
788 DataType::TimeTz => "TIMETZ".into(),
789 DataType::Money => "MONEY".into(),
790 DataType::Range(k) => k.keyword().into(),
791 DataType::Hstore => "HSTORE".into(),
792 DataType::IntArray2D => "INT[][]".into(),
793 DataType::BigIntArray2D => "BIGINT[][]".into(),
794 DataType::TextArray2D => "TEXT[][]".into(),
795 }
796}
797
798#[derive(Debug, Clone)]
802pub struct ActivityRow {
803 pub pid: u32,
804 pub user: String,
805 pub started_at_us: i64,
806 pub current_sql: String,
807 pub wait_event: String,
808 pub elapsed_us: i64,
809 pub in_transaction: bool,
810 pub application_name: String,
814}
815
816pub type ActivityProvider = fn() -> Vec<ActivityRow>;
819
820#[derive(Debug, Clone)]
823pub struct AuditRow {
824 pub seq: i64,
825 pub ts_ms: i64,
826 pub prev_hash_hex: String,
827 pub entry_hash_hex: String,
828 pub sql: String,
829}
830
831pub type AuditChainProvider = fn() -> Vec<AuditRow>;
836pub type AuditVerifier = fn() -> (i64, i64);
837
838impl Engine {
839 pub fn new() -> Self {
840 Self {
841 catalog: Catalog::new(),
842 tx_catalogs: BTreeMap::new(),
843 current_tx: None,
844 backslash_escapes: false,
845 next_tx_id: 1,
846 clock: None,
847 salt_fn: None,
848 max_query_rows: None,
849 users: UserStore::new(),
850 publications: publications::Publications::new(),
851 subscriptions: subscriptions::Subscriptions::new(),
852 statistics: statistics::Statistics::new(),
853 plan_cache: plan_cache::PlanCache::new(),
854 query_stats: query_stats::QueryStats::new(),
855 activity_provider: None,
856 audit_chain_provider: None,
857 audit_verifier: None,
858 slow_query_threshold_us: None,
859 slow_query_logger: None,
860 session_params: BTreeMap::new(),
861 trigger_recursion_depth: 0,
862 foreign_key_checks: true,
863 meta_views_materialised: false,
864 pending_foreign_keys: Vec::new(),
865 }
866 }
867
868 #[must_use]
877 pub fn clone_snapshot(&self) -> CatalogSnapshot {
878 CatalogSnapshot {
879 catalog: self.active_catalog().clone(),
880 statistics: self.statistics.clone(),
881 clock: self.clock,
882 max_query_rows: self.max_query_rows,
883 }
884 }
885
886 pub fn execute_readonly_on_snapshot(
894 snapshot: &CatalogSnapshot,
895 sql: &str,
896 ) -> Result<QueryResult, EngineError> {
897 Self::execute_readonly_on_snapshot_with_cancel(snapshot, sql, CancelToken::none())
898 }
899
900 pub fn execute_readonly_on_snapshot_with_cancel(
907 snapshot: &CatalogSnapshot,
908 sql: &str,
909 cancel: CancelToken<'_>,
910 ) -> Result<QueryResult, EngineError> {
911 let transient = Engine {
912 catalog: snapshot.catalog.clone(),
913 statistics: snapshot.statistics.clone(),
914 clock: snapshot.clock,
915 max_query_rows: snapshot.max_query_rows,
916 ..Engine::default()
917 };
918 transient.execute_readonly_with_cancel(sql, cancel)
919 }
920
921 pub fn execute_readonly_prepared_on_snapshot(
937 snapshot: &CatalogSnapshot,
938 stmt: Statement,
939 params: &[Value],
940 ) -> Result<QueryResult, EngineError> {
941 Self::execute_readonly_prepared_on_snapshot_with_cancel(
942 snapshot,
943 stmt,
944 params,
945 CancelToken::none(),
946 )
947 }
948
949 pub fn execute_readonly_prepared_on_snapshot_with_cancel(
952 snapshot: &CatalogSnapshot,
953 mut stmt: Statement,
954 params: &[Value],
955 cancel: CancelToken<'_>,
956 ) -> Result<QueryResult, EngineError> {
957 cancel.check()?;
958 substitute_placeholders(&mut stmt, params)?;
959 let transient = Engine {
960 catalog: snapshot.catalog.clone(),
961 statistics: snapshot.statistics.clone(),
962 clock: snapshot.clock,
963 max_query_rows: snapshot.max_query_rows,
964 ..Engine::default()
965 };
966 transient.execute_readonly_stmt_with_cancel(stmt, cancel)
967 }
968
969 pub fn describe_prepared_on_snapshot(
975 snapshot: &CatalogSnapshot,
976 stmt: &Statement,
977 ) -> (Vec<u32>, Vec<ColumnSchema>) {
978 describe::describe_prepared(stmt, &snapshot.catalog)
979 }
980
981 #[must_use]
989 pub fn is_readonly_sql(sql: &str) -> bool {
990 parser::parse_statement(sql)
991 .as_ref()
992 .map(spg_sql::ast::Statement::is_readonly)
993 .unwrap_or(false)
994 }
995
996 pub fn prepare_on_snapshot(
1011 snapshot: &CatalogSnapshot,
1012 sql: &str,
1013 ) -> Result<Statement, ParseError> {
1014 let mut stmt = parser::parse_statement(sql)?;
1015 let now_micros = snapshot.clock.map(|f| f());
1016 rewrite_clock_calls(&mut stmt, now_micros);
1017 if let Statement::Select(s) = &mut stmt {
1018 expand_group_by_all(s);
1019 resolve_order_by_position(s);
1020 reorder::reorder_joins(s, &snapshot.catalog, &snapshot.statistics);
1021 }
1022 Ok(stmt)
1023 }
1024
1025 pub fn restore(catalog: Catalog) -> Self {
1028 Self {
1029 catalog,
1030 tx_catalogs: BTreeMap::new(),
1031 current_tx: None,
1032 backslash_escapes: false,
1033 next_tx_id: 1,
1034 clock: None,
1035 salt_fn: None,
1036 max_query_rows: None,
1037 users: UserStore::new(),
1038 publications: publications::Publications::new(),
1039 subscriptions: subscriptions::Subscriptions::new(),
1040 statistics: statistics::Statistics::new(),
1041 plan_cache: plan_cache::PlanCache::new(),
1042 query_stats: query_stats::QueryStats::new(),
1043 activity_provider: None,
1044 audit_chain_provider: None,
1045 audit_verifier: None,
1046 slow_query_threshold_us: None,
1047 slow_query_logger: None,
1048 session_params: BTreeMap::new(),
1049 trigger_recursion_depth: 0,
1050 foreign_key_checks: true,
1051 meta_views_materialised: false,
1052 pending_foreign_keys: Vec::new(),
1053 }
1054 }
1055
1056 pub fn restore_envelope(buf: &[u8]) -> Result<Self, EngineError> {
1063 match split_envelope(buf) {
1064 EnvelopeParse::Pair {
1065 catalog: catalog_bytes,
1066 users: user_bytes,
1067 publications: pub_bytes,
1068 subscriptions: sub_bytes,
1069 statistics: stats_bytes,
1070 } => {
1071 let catalog = Catalog::deserialize(catalog_bytes).map_err(EngineError::Storage)?;
1072 let users = users::deserialize_users(user_bytes)
1073 .map_err(|e| EngineError::Unsupported(alloc::format!("users restore: {e}")))?;
1074 let publications = match pub_bytes {
1075 Some(b) => publications::Publications::deserialize(b).map_err(|e| {
1076 EngineError::Unsupported(alloc::format!("publications restore: {e:?}"))
1077 })?,
1078 None => publications::Publications::new(),
1079 };
1080 let subscriptions = match sub_bytes {
1081 Some(b) => subscriptions::Subscriptions::deserialize(b).map_err(|e| {
1082 EngineError::Unsupported(alloc::format!("subscriptions restore: {e:?}"))
1083 })?,
1084 None => subscriptions::Subscriptions::new(),
1085 };
1086 let statistics = match stats_bytes {
1087 Some(b) => statistics::Statistics::deserialize(b).map_err(|e| {
1088 EngineError::Unsupported(alloc::format!("statistics restore: {e:?}"))
1089 })?,
1090 None => statistics::Statistics::new(),
1091 };
1092 Ok(Self {
1093 catalog,
1094 tx_catalogs: BTreeMap::new(),
1095 current_tx: None,
1096 backslash_escapes: false,
1097 next_tx_id: 1,
1098 clock: None,
1099 salt_fn: None,
1100 max_query_rows: None,
1101 users,
1102 publications,
1103 subscriptions,
1104 statistics,
1105 plan_cache: plan_cache::PlanCache::new(),
1106 query_stats: query_stats::QueryStats::new(),
1107 activity_provider: None,
1108 audit_chain_provider: None,
1109 audit_verifier: None,
1110 slow_query_threshold_us: None,
1111 slow_query_logger: None,
1112 session_params: BTreeMap::new(),
1113 trigger_recursion_depth: 0,
1114 foreign_key_checks: true,
1115 meta_views_materialised: false,
1116 pending_foreign_keys: Vec::new(),
1117 })
1118 }
1119 EnvelopeParse::CrcMismatch { expected, computed } => {
1120 Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1121 "snapshot envelope CRC32 mismatch (expected={expected:#010x}, computed={computed:#010x})"
1122 ))))
1123 }
1124 EnvelopeParse::Bare => {
1125 let catalog = Catalog::deserialize(buf).map_err(EngineError::Storage)?;
1126 Ok(Self::restore(catalog))
1127 }
1128 }
1129 }
1130
1131 pub const fn users(&self) -> &UserStore {
1132 &self.users
1133 }
1134
1135 pub fn create_user(
1139 &mut self,
1140 name: &str,
1141 password: &str,
1142 role: Role,
1143 salt: [u8; 16],
1144 ) -> Result<(), UserError> {
1145 self.users.create(name, password, role, salt)?;
1146 let scram_salt = self.salt_fn.map_or_else(
1152 || {
1153 let mut s = [0u8; users::SCRAM_SALT_LEN];
1154 let digest = spg_crypto::hash(name.as_bytes());
1155 s.copy_from_slice(&digest[16..32]);
1158 s
1159 },
1160 |f| f(),
1161 );
1162 self.users
1163 .enable_scram(name, password, scram_salt, users::SCRAM_DEFAULT_ITERS)?;
1164 Ok(())
1165 }
1166
1167 pub fn drop_user(&mut self, name: &str) -> Result<(), UserError> {
1168 self.users.drop(name)
1169 }
1170
1171 pub fn verify_user(&self, name: &str, password: &str) -> Option<Role> {
1172 self.users.verify(name, password)
1173 }
1174
1175 #[must_use]
1178 pub const fn with_clock(mut self, clock: ClockFn) -> Self {
1179 self.clock = Some(clock);
1180 self
1181 }
1182
1183 #[must_use]
1186 pub const fn with_salt_fn(mut self, f: SaltFn) -> Self {
1187 self.salt_fn = Some(f);
1188 self
1189 }
1190
1191 #[must_use]
1197 pub const fn with_max_query_rows(mut self, n: usize) -> Self {
1198 self.max_query_rows = Some(n);
1199 self
1200 }
1201
1202 pub const fn catalog(&self) -> &Catalog {
1206 &self.catalog
1207 }
1208
1209 pub fn snapshot(&self) -> Vec<u8> {
1217 if self.users.is_empty()
1218 && self.publications.is_empty()
1219 && self.subscriptions.is_empty()
1220 && self.statistics.is_empty()
1221 {
1222 self.catalog.serialize()
1223 } else {
1224 build_envelope(
1225 &self.catalog.serialize(),
1226 &users::serialize_users(&self.users),
1227 &self.publications.serialize(),
1228 &self.subscriptions.serialize(),
1229 &self.statistics.serialize(),
1230 )
1231 }
1232 }
1233
1234 pub fn in_transaction(&self) -> bool {
1239 !self.tx_catalogs.is_empty()
1240 }
1241
1242 pub fn alloc_tx_id(&mut self) -> TxId {
1251 let id = TxId(self.next_tx_id);
1252 self.next_tx_id = self.next_tx_id.saturating_add(1);
1253 id
1254 }
1255
1256 pub fn replace_catalog(&mut self, catalog: Catalog) {
1276 self.catalog = catalog;
1277 }
1278
1279 pub fn freeze_oldest_to_cold(
1287 &mut self,
1288 table_name: &str,
1289 index_name: &str,
1290 max_rows: usize,
1291 ) -> Result<spg_storage::FreezeReport, EngineError> {
1292 let report = self
1293 .active_catalog_mut()
1294 .freeze_oldest_to_cold(table_name, index_name, max_rows)
1295 .map_err(EngineError::Storage)?;
1296 if let Some(t) = self.active_catalog_mut().get_mut(table_name) {
1297 t.mark_cold_row_count_stale();
1298 }
1299 Ok(report)
1300 }
1301
1302 pub fn receive_cold_segment(
1316 &mut self,
1317 segment_id: u32,
1318 bytes: Vec<u8>,
1319 ) -> Result<(), EngineError> {
1320 let mut new_cat = self.catalog.clone();
1321 match new_cat.load_segment_bytes_at(segment_id, bytes) {
1322 Ok(()) => {
1323 self.replace_catalog(new_cat);
1324 Ok(())
1325 }
1326 Err(StorageError::Corrupt(msg)) if msg.contains("already occupied") => Ok(()),
1327 Err(e) => Err(EngineError::Storage(e)),
1328 }
1329 }
1330
1331 pub fn compact_cold_segments_with_target(
1345 &mut self,
1346 target_segment_bytes: u64,
1347 ) -> Result<Vec<(String, String, CompactReport)>, EngineError> {
1348 let table_names = self.active_catalog().table_names();
1349 let mut reports: Vec<(String, String, CompactReport)> = Vec::new();
1350 for tname in table_names {
1351 if is_internal_table_name(&tname) {
1352 continue;
1353 }
1354 let idx_names: Vec<String> = {
1355 let Some(t) = self.active_catalog().get(&tname) else {
1356 continue;
1357 };
1358 t.indices()
1359 .iter()
1360 .filter(|i| matches!(i.kind, IndexKind::BTree(_)))
1361 .map(|i| i.name.clone())
1362 .collect()
1363 };
1364 for iname in idx_names {
1365 let report = self
1366 .active_catalog_mut()
1367 .compact_cold_segments(&tname, &iname, target_segment_bytes)
1368 .map_err(EngineError::Storage)?;
1369 if report.merged_segment_id.is_some() {
1370 if let Some(t) = self.active_catalog_mut().get_mut(&tname) {
1371 t.mark_cold_row_count_stale();
1372 }
1373 reports.push((tname.clone(), iname, report));
1374 }
1375 }
1376 }
1377 Ok(reports)
1378 }
1379
1380 fn active_catalog(&self) -> &Catalog {
1381 match self.current_tx {
1382 Some(t) => self
1383 .tx_catalogs
1384 .get(&t)
1385 .map_or(&self.catalog, |s| &s.catalog),
1386 None => &self.catalog,
1387 }
1388 }
1389
1390 fn resolve_plpgsql_block_subqueries(
1412 &self,
1413 block: &mut spg_sql::ast::PlPgSqlBlock,
1414 cancel: CancelToken<'_>,
1415 ) -> Result<(), EngineError> {
1416 for d in &mut block.declarations {
1417 if let Some(e) = &mut d.default {
1418 self.resolve_expr_subqueries(e, cancel)?;
1419 }
1420 }
1421 self.resolve_plpgsql_stmts_subqueries(&mut block.statements, cancel)
1422 }
1423
1424 fn resolve_plpgsql_stmts_subqueries(
1425 &self,
1426 stmts: &mut [spg_sql::ast::PlPgSqlStmt],
1427 cancel: CancelToken<'_>,
1428 ) -> Result<(), EngineError> {
1429 use spg_sql::ast::PlPgSqlStmt;
1430 for stmt in stmts {
1431 match stmt {
1432 PlPgSqlStmt::Assign { value, .. } => {
1433 self.resolve_expr_subqueries(value, cancel)?;
1434 }
1435 PlPgSqlStmt::Return(spg_sql::ast::ReturnTarget::Expr(e)) => {
1436 self.resolve_expr_subqueries(e, cancel)?;
1437 }
1438 PlPgSqlStmt::Return(_) => {}
1439 PlPgSqlStmt::If {
1440 branches,
1441 else_branch,
1442 } => {
1443 for (cond, body) in branches.iter_mut() {
1444 self.resolve_expr_subqueries(cond, cancel)?;
1445 self.resolve_plpgsql_stmts_subqueries(body, cancel)?;
1446 }
1447 self.resolve_plpgsql_stmts_subqueries(else_branch, cancel)?;
1448 }
1449 PlPgSqlStmt::Raise { args, .. } => {
1450 for a in args {
1451 self.resolve_expr_subqueries(a, cancel)?;
1452 }
1453 }
1454 PlPgSqlStmt::EmbeddedSql(_) => {
1455 }
1459 PlPgSqlStmt::SelectInto { body, .. } => {
1460 self.resolve_select_subqueries(body, cancel)?;
1466 }
1467 }
1468 }
1469 Ok(())
1470 }
1471
1472 fn exec_do_block(
1473 &mut self,
1474 body: spg_sql::ast::PlPgSqlBlock,
1475 ) -> Result<QueryResult, EngineError> {
1476 let mut body = body;
1485 self.resolve_plpgsql_block_subqueries(&mut body, CancelToken::none())?;
1486 let dts = self
1487 .session_param("default_text_search_config")
1488 .map(String::from);
1489 let engine_cell = core::cell::RefCell::new(&mut *self);
1502 let resolver_fn =
1503 |stmt: &spg_sql::ast::Statement| -> Result<Value, triggers::TriggerError> {
1504 let mut eng = engine_cell.borrow_mut();
1505 let r = eng
1506 .execute_stmt_with_cancel(stmt.clone(), CancelToken::none())
1507 .map_err(|e| triggers::TriggerError::EvalFailed {
1508 function: "DO".into(),
1509 cause: eval::EvalError::TypeMismatch {
1510 detail: alloc::format!("SELECT … INTO failed: {e}"),
1511 },
1512 })?;
1513 match r {
1514 QueryResult::Rows { rows, .. } => match rows.into_iter().next() {
1515 Some(row) => Ok(row.values.into_iter().next().unwrap_or(Value::Null)),
1516 None => Ok(Value::Null),
1517 },
1518 _ => Err(triggers::TriggerError::EvalFailed {
1519 function: "DO".into(),
1520 cause: eval::EvalError::TypeMismatch {
1521 detail: "SELECT … INTO body must be a SELECT".into(),
1522 },
1523 }),
1524 }
1525 };
1526 let collected =
1527 triggers::execute_do_block_top_level(&body, dts.as_deref(), Some(&resolver_fn))
1528 .map_err(|e| {
1529 EngineError::Storage(StorageError::Corrupt(alloc::format!("DO: {e}")))
1530 })?;
1531 for stmt in collected {
1537 self.execute_stmt_with_cancel(stmt, CancelToken::none())?;
1541 }
1542 Ok(QueryResult::CommandOk {
1543 affected: 0,
1544 modified_catalog: !self.in_transaction(),
1545 })
1546 }
1547
1548 fn snapshot_row_triggers(
1549 &self,
1550 table: &str,
1551 event: &str,
1552 timing: &str,
1553 ) -> Vec<spg_storage::FunctionDef> {
1554 let cat = self.active_catalog();
1555 cat.triggers()
1556 .iter()
1557 .filter(|t| {
1558 t.enabled
1561 && t.table == table
1562 && t.timing.eq_ignore_ascii_case(timing)
1563 && t.for_each.eq_ignore_ascii_case("row")
1564 && t.events.iter().any(|e| e.eq_ignore_ascii_case(event))
1565 })
1566 .filter_map(|t| cat.functions().get(&t.function).cloned())
1567 .collect()
1568 }
1569
1570 fn snapshot_update_row_triggers(
1575 &self,
1576 table: &str,
1577 timing: &str,
1578 ) -> Vec<(spg_storage::FunctionDef, Vec<String>)> {
1579 let cat = self.active_catalog();
1580 cat.triggers()
1581 .iter()
1582 .filter(|t| {
1583 t.enabled
1585 && t.table == table
1586 && t.timing.eq_ignore_ascii_case(timing)
1587 && t.for_each.eq_ignore_ascii_case("row")
1588 && t.events.iter().any(|e| e.eq_ignore_ascii_case("UPDATE"))
1589 })
1590 .filter_map(|t| {
1591 cat.functions()
1592 .get(&t.function)
1593 .cloned()
1594 .map(|fd| (fd, t.update_columns.clone()))
1595 })
1596 .collect()
1597 }
1598
1599 fn execute_deferred_trigger_stmts(
1608 &mut self,
1609 deferred: Vec<triggers::DeferredEmbeddedStmt>,
1610 cancel: CancelToken<'_>,
1611 ) -> Result<(), EngineError> {
1612 for d in deferred {
1613 if self.trigger_recursion_depth >= MAX_TRIGGER_RECURSION {
1614 return Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1615 "trigger embedded SQL recursion depth {} exceeded (trigger function \
1616 {:?} would push past the {} cap — check for trigger cycles)",
1617 self.trigger_recursion_depth,
1618 d.function,
1619 MAX_TRIGGER_RECURSION,
1620 ))));
1621 }
1622 self.trigger_recursion_depth += 1;
1623 let res = self.execute_stmt_with_cancel(d.stmt, cancel);
1624 self.trigger_recursion_depth -= 1;
1625 res?;
1626 }
1627 Ok(())
1628 }
1629
1630 fn active_catalog_mut(&mut self) -> &mut Catalog {
1631 let tx = self.current_tx;
1632 match tx {
1633 Some(t) => match self.tx_catalogs.get_mut(&t) {
1634 Some(s) => &mut s.catalog,
1635 None => &mut self.catalog,
1636 },
1637 None => &mut self.catalog,
1638 }
1639 }
1640
1641 pub fn execute_readonly(&self, sql: &str) -> Result<QueryResult, EngineError> {
1653 self.execute_readonly_with_cancel(sql, CancelToken::none())
1654 }
1655
1656 pub fn execute_readonly_with_cancel(
1662 &self,
1663 sql: &str,
1664 cancel: CancelToken<'_>,
1665 ) -> Result<QueryResult, EngineError> {
1666 cancel.check()?;
1667 let mut stmt = parser::parse_statement_with(sql, self.backslash_escapes)?;
1668 let now_micros = self.clock.map(|f| f());
1669 rewrite_clock_calls(&mut stmt, now_micros);
1670 if let Statement::Select(s) = &mut stmt {
1671 resolve_order_by_position(s);
1672 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1674 }
1675 self.execute_readonly_stmt_with_cancel(stmt, cancel)
1676 }
1677
1678 fn execute_readonly_stmt_with_cancel(
1688 &self,
1689 stmt: Statement,
1690 cancel: CancelToken<'_>,
1691 ) -> Result<QueryResult, EngineError> {
1692 let result = match stmt {
1693 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1694 Statement::ShowTables => Ok(self.exec_show_tables()),
1695 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1696 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1697 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1698 Statement::ShowStatus => Ok(self.exec_show_status()),
1699 Statement::ShowVariables => Ok(self.exec_show_variables()),
1700 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1701 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1702 Statement::ShowUsers => Ok(self.exec_show_users()),
1703 Statement::ShowPublications => Ok(self.exec_show_publications()),
1704 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1705 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1706 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1707 )),
1708 Statement::Explain(e) => self.exec_explain(&e, cancel),
1709 _ => Err(EngineError::WriteRequired),
1710 };
1711 self.enforce_row_limit(result)
1712 }
1713
1714 fn enforce_row_limit(
1718 &self,
1719 result: Result<QueryResult, EngineError>,
1720 ) -> Result<QueryResult, EngineError> {
1721 if let (Ok(QueryResult::Rows { rows, .. }), Some(cap)) = (&result, self.max_query_rows)
1722 && rows.len() > cap
1723 {
1724 return Err(EngineError::RowLimitExceeded(cap));
1725 }
1726 result
1727 }
1728
1729 pub fn execute(&mut self, sql: &str) -> Result<QueryResult, EngineError> {
1730 self.execute_in_with_cancel(sql, IMPLICIT_TX, CancelToken::none())
1731 }
1732
1733 pub fn execute_with_cancel(
1738 &mut self,
1739 sql: &str,
1740 cancel: CancelToken<'_>,
1741 ) -> Result<QueryResult, EngineError> {
1742 self.execute_in_with_cancel(sql, IMPLICIT_TX, cancel)
1743 }
1744
1745 pub fn execute_in(&mut self, sql: &str, tx_id: TxId) -> Result<QueryResult, EngineError> {
1752 self.execute_in_with_cancel(sql, tx_id, CancelToken::none())
1753 }
1754
1755 pub fn execute_in_with_cancel(
1761 &mut self,
1762 sql: &str,
1763 tx_id: TxId,
1764 cancel: CancelToken<'_>,
1765 ) -> Result<QueryResult, EngineError> {
1766 let saved = self.current_tx;
1767 self.current_tx = Some(tx_id);
1768 let result = self.execute_inner_with_cancel(sql, cancel);
1769 self.current_tx = saved;
1770 result
1771 }
1772
1773 pub fn prepare(&self, sql: &str) -> Result<Statement, ParseError> {
1785 let mut stmt = parser::parse_statement_with(sql, self.backslash_escapes)?;
1786 let now_micros = self.clock.map(|f| f());
1787 rewrite_clock_calls(&mut stmt, now_micros);
1788 if let Statement::Select(s) = &mut stmt {
1789 expand_group_by_all(s);
1793 resolve_order_by_position(s);
1794 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1797 }
1798 Ok(stmt)
1799 }
1800
1801 pub fn prepare_cached(&mut self, sql: &str) -> Result<Statement, ParseError> {
1813 let current_version = self.statistics.version();
1816 if let Some(plan) = self.plan_cache.get(sql) {
1817 if plan.statistics_version == current_version {
1818 return Ok(plan.stmt.clone());
1819 }
1820 }
1822 self.plan_cache.evict(sql);
1823 let stmt = self.prepare(sql)?;
1824 let source_tables = plan_cache::collect_source_tables(&stmt);
1825 let plan = plan_cache::PreparedPlan {
1826 stmt: stmt.clone(),
1827 statistics_version: current_version,
1828 source_tables,
1829 describe_columns: alloc::vec::Vec::new(),
1830 };
1831 self.plan_cache.insert(String::from(sql), plan);
1832 Ok(stmt)
1833 }
1834
1835 pub fn plan_cache(&self) -> &plan_cache::PlanCache {
1837 &self.plan_cache
1838 }
1839
1840 pub fn plan_cache_mut(&mut self) -> &mut plan_cache::PlanCache {
1842 &mut self.plan_cache
1843 }
1844
1845 pub fn describe_prepared(&self, stmt: &Statement) -> (Vec<u32>, Vec<ColumnSchema>) {
1851 describe::describe_prepared(stmt, self.active_catalog())
1852 }
1853
1854 pub fn execute_prepared(
1864 &mut self,
1865 stmt: Statement,
1866 params: &[Value],
1867 ) -> Result<QueryResult, EngineError> {
1868 self.execute_prepared_with_cancel(stmt, params, CancelToken::none())
1869 }
1870
1871 pub fn execute_prepared_with_cancel(
1876 &mut self,
1877 mut stmt: Statement,
1878 params: &[Value],
1879 cancel: CancelToken<'_>,
1880 ) -> Result<QueryResult, EngineError> {
1881 substitute_placeholders(&mut stmt, params)?;
1882 let saved = self.current_tx;
1893 self.current_tx = Some(IMPLICIT_TX);
1894 let result = self.execute_stmt_with_cancel(stmt, cancel);
1895 self.current_tx = saved;
1896 result
1897 }
1898
1899 fn execute_inner_with_cancel(
1900 &mut self,
1901 sql: &str,
1902 cancel: CancelToken<'_>,
1903 ) -> Result<QueryResult, EngineError> {
1904 cancel.check()?;
1905 let stmt = self.prepare(sql)?;
1906 let start_us = self.clock.map(|f| f());
1910 let result = self.execute_stmt_with_cancel(stmt, cancel);
1911 if let (Some(t0), Ok(_)) = (start_us, &result) {
1912 let now = self.clock.map_or(t0, |f| f());
1913 let elapsed = now.saturating_sub(t0).max(0) as u64;
1914 self.query_stats.record(sql, elapsed, now as u64);
1915 if let (Some(threshold), Some(logger)) =
1918 (self.slow_query_threshold_us, self.slow_query_logger)
1919 && elapsed >= threshold
1920 {
1921 logger(sql, elapsed);
1922 }
1923 }
1924 result
1925 }
1926
1927 fn execute_stmt_with_cancel(
1928 &mut self,
1929 stmt: Statement,
1930 cancel: CancelToken<'_>,
1931 ) -> Result<QueryResult, EngineError> {
1932 cancel.check()?;
1933 let mut stmt = stmt;
1947 self.pre_resolve_sequence_calls_in_statement(&mut stmt)?;
1956 let result = match stmt {
1957 Statement::CreateTable(s) => self.exec_create_table(s),
1958 Statement::CreateExtension(_) => Ok(QueryResult::CommandOk {
1962 affected: 0,
1963 modified_catalog: false,
1964 }),
1965 Statement::DoBlock(body) => self.exec_do_block(body),
1975 Statement::Empty => Ok(QueryResult::CommandOk {
1979 affected: 0,
1980 modified_catalog: false,
1981 }),
1982 Statement::DropTable { names, if_exists } => self.exec_drop_table(names, if_exists),
1983 Statement::DropIndex { name, if_exists } => self.exec_drop_index(name, if_exists),
1984 Statement::CreateIndex(s) => self.exec_create_index(s),
1985 Statement::Insert(s) => self.exec_insert(s),
1986 Statement::Update(mut s) => {
1987 for (_, e) in &mut s.assignments {
1993 self.resolve_expr_subqueries(e, cancel)?;
1994 }
1995 if let Some(w) = &mut s.where_ {
1996 self.resolve_expr_subqueries(w, cancel)?;
1997 }
1998 self.exec_update_cancel(&s, cancel)
1999 }
2000 Statement::Delete(mut s) => {
2001 if let Some(w) = &mut s.where_ {
2002 self.resolve_expr_subqueries(w, cancel)?;
2003 }
2004 self.exec_delete_cancel(&s, cancel)
2005 }
2006 Statement::Merge(s) => self.exec_merge_cancel(&s, cancel),
2007 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
2008 Statement::Begin => self.exec_begin(),
2009 Statement::Commit => self.exec_commit(),
2010 Statement::Rollback => self.exec_rollback(),
2011 Statement::Savepoint(name) => self.exec_savepoint(name),
2012 Statement::RollbackToSavepoint(name) => self.exec_rollback_to_savepoint(&name),
2013 Statement::ReleaseSavepoint(name) => self.exec_release_savepoint(&name),
2014 Statement::ShowTables => Ok(self.exec_show_tables()),
2015 Statement::ShowDatabases => Ok(self.exec_show_databases()),
2016 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
2017 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
2018 Statement::ShowStatus => Ok(self.exec_show_status()),
2019 Statement::ShowVariables => Ok(self.exec_show_variables()),
2020 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
2021 Statement::ShowColumns(table) => self.exec_show_columns(&table),
2022 Statement::ShowUsers => Ok(self.exec_show_users()),
2023 Statement::ShowPublications => Ok(self.exec_show_publications()),
2024 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
2025 Statement::CreateUser(s) => self.exec_create_user(&s),
2026 Statement::DropUser(name) => self.exec_drop_user(&name),
2027 Statement::Explain(e) => self.exec_explain(&e, cancel),
2028 Statement::AlterIndex(s) => self.exec_alter_index(s),
2029 Statement::AlterTable(s) => self.exec_alter_table(s),
2030 Statement::CreatePublication(s) => self.exec_create_publication(s),
2031 Statement::DropPublication(name) => self.exec_drop_publication(&name),
2032 Statement::CreateSubscription(s) => self.exec_create_subscription(s),
2033 Statement::DropSubscription(name) => self.exec_drop_subscription(&name),
2034 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
2041 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
2042 )),
2043 Statement::Analyze(target) => self.exec_analyze(target.as_deref()),
2045 Statement::CompactColdSegments => self.exec_compact_cold_segments(),
2047 Statement::SetParameter { name, value } => {
2052 self.set_session_param(name, value);
2053 Ok(QueryResult::CommandOk {
2054 affected: 0,
2055 modified_catalog: false,
2056 })
2057 }
2058 Statement::SetParameterList(pairs) => {
2064 for (name, value) in pairs {
2065 self.set_session_param(name, value);
2066 }
2067 Ok(QueryResult::CommandOk {
2068 affected: 0,
2069 modified_catalog: false,
2070 })
2071 }
2072 Statement::CreateFunction(s) => self.exec_create_function(s),
2076 Statement::CreateTrigger(s) => self.exec_create_trigger(s),
2077 Statement::DropTrigger {
2078 name,
2079 table,
2080 if_exists,
2081 } => self.exec_drop_trigger(&name, &table, if_exists),
2082 Statement::DropFunction { name, if_exists } => {
2083 self.exec_drop_function(&name, if_exists)
2084 }
2085 Statement::CreateSequence(s) => self.exec_create_sequence(s),
2086 Statement::AlterSequence(s) => self.exec_alter_sequence(s),
2087 Statement::DropSequence { names, if_exists } => {
2088 self.exec_drop_sequence(&names, if_exists)
2089 }
2090 Statement::CreateView(s) => self.exec_create_view(s),
2091 Statement::DropView { names, if_exists } => self.exec_drop_view(&names, if_exists),
2092 Statement::CreateMaterializedView(s) => self.exec_create_materialized_view(s),
2093 Statement::RefreshMaterializedView { name, with_data } => {
2094 self.exec_refresh_materialized_view(&name, with_data)
2095 }
2096 Statement::DropMaterializedView { names, if_exists } => {
2097 self.exec_drop_materialized_view(&names, if_exists)
2098 }
2099 Statement::CreateType(s) => self.exec_create_type(s),
2100 Statement::DropType { names, if_exists } => self.exec_drop_type(&names, if_exists),
2101 Statement::CreateDomain(s) => self.exec_create_domain(s),
2102 Statement::DropDomain { names, if_exists } => self.exec_drop_domain(&names, if_exists),
2103 Statement::CreateSchema {
2104 name,
2105 if_not_exists,
2106 } => self.exec_create_schema(name, if_not_exists),
2107 Statement::DropSchema { names, if_exists } => self.exec_drop_schema(&names, if_exists),
2108 Statement::ResetParameter(target) => {
2109 match target {
2110 None => self.session_params.clear(),
2111 Some(name) => {
2112 self.session_params.remove(&name.to_ascii_lowercase());
2113 }
2114 }
2115 Ok(QueryResult::CommandOk {
2116 affected: 0,
2117 modified_catalog: false,
2118 })
2119 }
2120 };
2121 self.enforce_row_limit(result)
2122 }
2123
2124 fn exec_create_publication(
2132 &mut self,
2133 s: CreatePublicationStatement,
2134 ) -> Result<QueryResult, EngineError> {
2135 self.publications
2141 .create(s.name, s.scope)
2142 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE PUBLICATION: {e:?}")))?;
2143 Ok(QueryResult::CommandOk {
2144 affected: 1,
2145 modified_catalog: true,
2146 })
2147 }
2148
2149 fn exec_drop_publication(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2154 let removed = self.publications.drop(name);
2155 Ok(QueryResult::CommandOk {
2156 affected: usize::from(removed),
2157 modified_catalog: removed,
2158 })
2159 }
2160
2161 pub const fn publications(&self) -> &publications::Publications {
2166 &self.publications
2167 }
2168
2169 fn exec_create_subscription(
2174 &mut self,
2175 s: CreateSubscriptionStatement,
2176 ) -> Result<QueryResult, EngineError> {
2177 let sub = subscriptions::Subscription {
2181 conn_str: s.conn_str,
2182 publications: s.publications,
2183 enabled: true,
2184 last_received_pos: 0,
2185 };
2186 self.subscriptions
2187 .create(s.name, sub)
2188 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE SUBSCRIPTION: {e:?}")))?;
2189 Ok(QueryResult::CommandOk {
2190 affected: 1,
2191 modified_catalog: true,
2192 })
2193 }
2194
2195 fn exec_drop_subscription(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2203 let removed = self.subscriptions.drop(name);
2204 Ok(QueryResult::CommandOk {
2205 affected: usize::from(removed),
2206 modified_catalog: removed,
2207 })
2208 }
2209
2210 pub const fn subscriptions(&self) -> &subscriptions::Subscriptions {
2215 &self.subscriptions
2216 }
2217
2218 pub fn subscription_advance(&mut self, name: &str, pos: u64) -> bool {
2224 self.subscriptions.update_last_received_pos(name, pos)
2225 }
2226
2227 fn exec_show_subscriptions(&self) -> QueryResult {
2233 let columns = alloc::vec![
2234 ColumnSchema::new("name", DataType::Text, false),
2235 ColumnSchema::new("conn_str", DataType::Text, false),
2236 ColumnSchema::new("publications", DataType::Text, false),
2237 ColumnSchema::new("enabled", DataType::Bool, false),
2238 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2239 ];
2240 let rows: Vec<Row> = self
2241 .subscriptions
2242 .iter()
2243 .map(|(name, sub)| {
2244 Row::new(alloc::vec![
2245 Value::Text(name.clone()),
2246 Value::Text(sub.conn_str.clone()),
2247 Value::Text(sub.publications.join(", ")),
2248 Value::Bool(sub.enabled),
2249 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2250 ])
2251 })
2252 .collect();
2253 QueryResult::Rows { columns, rows }
2254 }
2255
2256 fn exec_spg_statistic(&self) -> QueryResult {
2261 let columns = alloc::vec![
2262 ColumnSchema::new("table_name", DataType::Text, false),
2263 ColumnSchema::new("column_name", DataType::Text, false),
2264 ColumnSchema::new("null_frac", DataType::Float, false),
2265 ColumnSchema::new("n_distinct", DataType::BigInt, false),
2266 ColumnSchema::new("histogram_bounds", DataType::Text, false),
2267 ColumnSchema::new("cold_row_count", DataType::BigInt, false),
2272 ];
2273 let rows: Vec<Row> = self
2274 .statistics
2275 .iter()
2276 .map(|((t, c), s)| {
2277 let cold = self
2278 .catalog
2279 .get(t)
2280 .map_or(0, |table| table.cold_row_count());
2281 Row::new(alloc::vec![
2282 Value::Text(t.clone()),
2283 Value::Text(c.clone()),
2284 Value::Float(f64::from(s.null_frac)),
2285 Value::BigInt(i64::try_from(s.n_distinct).unwrap_or(i64::MAX)),
2286 Value::Text(render_histogram_bounds(&s.histogram_bounds)),
2287 Value::BigInt(i64::try_from(cold).unwrap_or(i64::MAX)),
2288 ])
2289 })
2290 .collect();
2291 QueryResult::Rows { columns, rows }
2292 }
2293
2294 fn exec_spg_stat_replication(&self) -> QueryResult {
2301 let columns = alloc::vec![
2302 ColumnSchema::new("name", DataType::Text, false),
2303 ColumnSchema::new("conn_str", DataType::Text, false),
2304 ColumnSchema::new("publications", DataType::Text, false),
2305 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2306 ColumnSchema::new("enabled", DataType::Bool, false),
2307 ];
2308 let rows: Vec<Row> = self
2309 .subscriptions
2310 .iter()
2311 .map(|(name, sub)| {
2312 Row::new(alloc::vec![
2313 Value::Text(name.clone()),
2314 Value::Text(sub.conn_str.clone()),
2315 Value::Text(sub.publications.join(",")),
2316 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2317 Value::Bool(sub.enabled),
2318 ])
2319 })
2320 .collect();
2321 QueryResult::Rows { columns, rows }
2322 }
2323
2324 fn exec_spg_stat_segment(&self) -> QueryResult {
2336 let columns = alloc::vec![
2337 ColumnSchema::new("segment_id", DataType::BigInt, false),
2338 ColumnSchema::new("table_name", DataType::Text, false),
2339 ColumnSchema::new("num_rows", DataType::BigInt, false),
2340 ColumnSchema::new("num_pages", DataType::BigInt, false),
2341 ColumnSchema::new("total_bytes", DataType::BigInt, false),
2342 ];
2343 let mut segment_owners: alloc::collections::BTreeMap<u32, String> = BTreeMap::new();
2349 for tname in self.catalog.table_names() {
2350 if is_internal_table_name(&tname) {
2351 continue;
2352 }
2353 let Some(t) = self.catalog.get(&tname) else {
2354 continue;
2355 };
2356 for idx in t.indices() {
2357 if let spg_storage::IndexKind::BTree(map) = &idx.kind {
2358 for (_, locs) in map.iter() {
2359 for loc in locs {
2360 if let spg_storage::RowLocator::Cold { segment_id, .. } = loc {
2361 segment_owners
2362 .entry(*segment_id)
2363 .or_insert_with(|| tname.clone());
2364 }
2365 }
2366 }
2367 }
2368 }
2369 }
2370 let rows: Vec<Row> = self
2371 .catalog
2372 .cold_segment_ids_global()
2373 .iter()
2374 .filter_map(|&id| {
2375 let seg = self.catalog.cold_segment(id)?;
2376 let meta = seg.meta();
2377 let owner = segment_owners.get(&id).cloned().unwrap_or_default();
2378 Some(Row::new(alloc::vec![
2379 Value::BigInt(i64::from(id)),
2380 Value::Text(owner),
2381 Value::BigInt(i64::try_from(meta.num_rows).unwrap_or(i64::MAX)),
2382 Value::BigInt(i64::from(meta.num_pages)),
2383 Value::BigInt(i64::try_from(meta.total_bytes).unwrap_or(i64::MAX)),
2384 ]))
2385 })
2386 .collect();
2387 QueryResult::Rows { columns, rows }
2388 }
2389
2390 fn exec_spg_stat_query(&self) -> QueryResult {
2396 let columns = alloc::vec![
2397 ColumnSchema::new("sql", DataType::Text, false),
2398 ColumnSchema::new("exec_count", DataType::BigInt, false),
2399 ColumnSchema::new("total_us", DataType::BigInt, false),
2400 ColumnSchema::new("mean_us", DataType::BigInt, false),
2401 ColumnSchema::new("max_us", DataType::BigInt, false),
2402 ColumnSchema::new("last_seen_us", DataType::BigInt, false),
2403 ];
2404 let rows: Vec<Row> = self
2405 .query_stats
2406 .snapshot()
2407 .into_iter()
2408 .map(|(sql, s)| {
2409 let mean = if s.exec_count == 0 {
2410 0
2411 } else {
2412 s.total_us / s.exec_count
2413 };
2414 Row::new(alloc::vec![
2415 Value::Text(sql),
2416 Value::BigInt(i64::try_from(s.exec_count).unwrap_or(i64::MAX)),
2417 Value::BigInt(i64::try_from(s.total_us).unwrap_or(i64::MAX)),
2418 Value::BigInt(i64::try_from(mean).unwrap_or(i64::MAX)),
2419 Value::BigInt(i64::try_from(s.max_us).unwrap_or(i64::MAX)),
2420 Value::BigInt(i64::try_from(s.last_seen_us).unwrap_or(i64::MAX)),
2421 ])
2422 })
2423 .collect();
2424 QueryResult::Rows { columns, rows }
2425 }
2426
2427 #[must_use]
2432 pub const fn with_activity_provider(mut self, f: ActivityProvider) -> Self {
2433 self.activity_provider = Some(f);
2434 self
2435 }
2436
2437 #[must_use]
2439 pub const fn with_audit_providers(
2440 mut self,
2441 chain: AuditChainProvider,
2442 verify: AuditVerifier,
2443 ) -> Self {
2444 self.audit_chain_provider = Some(chain);
2445 self.audit_verifier = Some(verify);
2446 self
2447 }
2448
2449 #[must_use]
2454 pub const fn with_slow_query_log(mut self, threshold_us: u64, logger: SlowQueryLogger) -> Self {
2455 self.slow_query_threshold_us = Some(threshold_us);
2456 self.slow_query_logger = Some(logger);
2457 self
2458 }
2459
2460 pub fn set_plan_cache_max(&mut self, n: usize) {
2464 self.plan_cache.set_max_entries(n);
2465 }
2466
2467 fn exec_spg_stat_activity(&self) -> QueryResult {
2472 let columns = alloc::vec![
2473 ColumnSchema::new("pid", DataType::Int, false),
2474 ColumnSchema::new("user", DataType::Text, false),
2475 ColumnSchema::new("started_at_us", DataType::BigInt, false),
2476 ColumnSchema::new("current_sql", DataType::Text, false),
2477 ColumnSchema::new("wait_event", DataType::Text, false),
2478 ColumnSchema::new("elapsed_us", DataType::BigInt, false),
2479 ColumnSchema::new("in_transaction", DataType::Bool, false),
2480 ColumnSchema::new("application_name", DataType::Text, false),
2481 ];
2482 let rows: Vec<Row> = self
2483 .activity_provider
2484 .map(|f| f())
2485 .unwrap_or_default()
2486 .into_iter()
2487 .map(|r| {
2488 Row::new(alloc::vec![
2489 Value::Int(i32::try_from(r.pid).unwrap_or(i32::MAX)),
2490 Value::Text(r.user),
2491 Value::BigInt(r.started_at_us),
2492 Value::Text(r.current_sql),
2493 Value::Text(r.wait_event),
2494 Value::BigInt(r.elapsed_us),
2495 Value::Bool(r.in_transaction),
2496 Value::Text(r.application_name),
2497 ])
2498 })
2499 .collect();
2500 QueryResult::Rows { columns, rows }
2501 }
2502
2503 fn exec_spg_table_ddl(&self) -> QueryResult {
2507 let columns = alloc::vec![
2508 ColumnSchema::new("table_name", DataType::Text, false),
2509 ColumnSchema::new("ddl", DataType::Text, false),
2510 ];
2511 let rows: Vec<Row> = self
2512 .catalog
2513 .table_names()
2514 .into_iter()
2515 .filter(|n| !is_internal_table_name(n))
2516 .filter_map(|name| {
2517 let table = self.catalog.get(&name)?;
2518 let ddl = render_create_table(&name, &table.schema().columns);
2519 Some(Row::new(alloc::vec![Value::Text(name), Value::Text(ddl),]))
2520 })
2521 .collect();
2522 QueryResult::Rows { columns, rows }
2523 }
2524
2525 fn exec_spg_role_ddl(&self) -> QueryResult {
2529 let columns = alloc::vec![
2530 ColumnSchema::new("role_name", DataType::Text, false),
2531 ColumnSchema::new("ddl", DataType::Text, false),
2532 ];
2533 let rows: Vec<Row> = self
2534 .users
2535 .iter()
2536 .map(|(name, rec)| {
2537 let ddl = alloc::format!(
2538 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}'",
2539 rec.role.as_str(),
2540 );
2541 Row::new(alloc::vec![
2542 Value::Text(String::from(name)),
2543 Value::Text(ddl)
2544 ])
2545 })
2546 .collect();
2547 QueryResult::Rows { columns, rows }
2548 }
2549
2550 fn exec_spg_database_ddl(&self) -> QueryResult {
2556 let columns = alloc::vec![ColumnSchema::new("ddl", DataType::Text, false)];
2557 let mut out = String::new();
2558 for (name, rec) in self.users.iter() {
2559 out.push_str(&alloc::format!(
2560 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}';\n",
2561 rec.role.as_str(),
2562 ));
2563 }
2564 for name in self.catalog.table_names() {
2565 if is_internal_table_name(&name) {
2566 continue;
2567 }
2568 if let Some(table) = self.catalog.get(&name) {
2569 out.push_str(&render_create_table(&name, &table.schema().columns));
2570 out.push_str(";\n");
2571 }
2572 }
2573 QueryResult::Rows {
2574 columns,
2575 rows: alloc::vec![Row::new(alloc::vec![Value::Text(out)])],
2576 }
2577 }
2578
2579 fn exec_spg_audit_chain(&self) -> QueryResult {
2583 let columns = alloc::vec![
2584 ColumnSchema::new("seq", DataType::BigInt, false),
2585 ColumnSchema::new("ts_ms", DataType::BigInt, false),
2586 ColumnSchema::new("prev_hash", DataType::Text, false),
2587 ColumnSchema::new("entry_hash", DataType::Text, false),
2588 ColumnSchema::new("sql", DataType::Text, false),
2589 ];
2590 let rows: Vec<Row> = self
2591 .audit_chain_provider
2592 .map(|f| f())
2593 .unwrap_or_default()
2594 .into_iter()
2595 .map(|r| {
2596 Row::new(alloc::vec![
2597 Value::BigInt(r.seq),
2598 Value::BigInt(r.ts_ms),
2599 Value::Text(r.prev_hash_hex),
2600 Value::Text(r.entry_hash_hex),
2601 Value::Text(r.sql),
2602 ])
2603 })
2604 .collect();
2605 QueryResult::Rows { columns, rows }
2606 }
2607
2608 fn exec_spg_audit_verify(&self) -> QueryResult {
2614 let columns = alloc::vec![
2615 ColumnSchema::new("verified_count", DataType::BigInt, false),
2616 ColumnSchema::new("broken_at_seq", DataType::BigInt, false),
2617 ];
2618 let (verified, broken) = self.audit_verifier.map(|f| f()).unwrap_or((0, -1));
2619 let row = Row::new(alloc::vec![Value::BigInt(verified), Value::BigInt(broken),]);
2620 QueryResult::Rows {
2621 columns,
2622 rows: alloc::vec![row],
2623 }
2624 }
2625
2626 pub fn query_stats(&self) -> &query_stats::QueryStats {
2628 &self.query_stats
2629 }
2630
2631 pub fn query_stats_mut(&mut self) -> &mut query_stats::QueryStats {
2633 &mut self.query_stats
2634 }
2635
2636 pub const fn statistics(&self) -> &statistics::Statistics {
2640 &self.statistics
2641 }
2642
2643 pub fn tables_needing_analyze(&self) -> Vec<String> {
2656 const MIN_ROWS: u64 = 100;
2657 let mut out = Vec::new();
2658 for name in self.catalog.table_names() {
2659 if is_internal_table_name(&name) {
2660 continue;
2661 }
2662 let Some(table) = self.catalog.get(&name) else {
2663 continue;
2664 };
2665 let row_count = table.rows().len() as u64;
2666 let modified = self.statistics.modified_since_last_analyze(&name);
2667 let base = row_count.max(MIN_ROWS);
2672 let threshold = base.saturating_add(9) / 10;
2673 if modified >= threshold {
2674 out.push(name);
2675 }
2676 }
2677 out
2678 }
2679
2680 fn exec_analyze(&mut self, target: Option<&str>) -> Result<QueryResult, EngineError> {
2691 let names: Vec<String> = if let Some(name) = target {
2692 if self.catalog.get(name).is_none() {
2694 return Err(EngineError::Storage(StorageError::TableNotFound {
2695 name: name.to_string(),
2696 }));
2697 }
2698 alloc::vec![name.to_string()]
2699 } else {
2700 self.catalog
2701 .table_names()
2702 .into_iter()
2703 .filter(|n| !is_internal_table_name(n))
2704 .collect()
2705 };
2706 let mut analysed = 0usize;
2707 for table_name in &names {
2708 self.analyze_one_table(table_name)?;
2709 analysed += 1;
2710 }
2711 if analysed > 0 {
2717 self.statistics.bump_version();
2718 if target.is_some() {
2719 for t in &names {
2720 self.plan_cache.evict_referencing(t);
2721 }
2722 } else {
2723 self.plan_cache.clear();
2724 }
2725 }
2726 Ok(QueryResult::CommandOk {
2727 affected: analysed,
2728 modified_catalog: true,
2729 })
2730 }
2731
2732 fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
2745 let normalised = match value {
2746 spg_sql::ast::SetValue::String(s) => s,
2747 spg_sql::ast::SetValue::Ident(s) => s,
2748 spg_sql::ast::SetValue::Number(s) => s,
2749 spg_sql::ast::SetValue::Default => String::new(),
2750 };
2751 let key = name.to_ascii_lowercase();
2752 let value_off = matches!(
2763 normalised.to_ascii_lowercase().as_str(),
2764 "0" | "off" | "false"
2765 );
2766 let value_on = matches!(
2767 normalised.to_ascii_lowercase().as_str(),
2768 "1" | "on" | "true"
2769 );
2770 if key == "foreign_key_checks"
2771 || key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
2772 {
2773 if value_off || key == "session_replication_role" {
2774 self.foreign_key_checks = false;
2775 } else if value_on
2776 || (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
2777 {
2778 self.foreign_key_checks = true;
2779 let _ = self.drain_pending_foreign_keys();
2783 }
2784 }
2785 let new_escapes = if key == "sql_mode" {
2793 Some(true)
2794 } else if key == "standard_conforming_strings" {
2795 Some(value_off)
2796 } else {
2797 None
2798 };
2799 if let Some(flag) = new_escapes
2800 && flag != self.backslash_escapes
2801 {
2802 self.backslash_escapes = flag;
2803 self.plan_cache.clear();
2804 }
2805 self.session_params.insert(key, normalised);
2806 }
2807
2808 fn drain_pending_foreign_keys(&mut self) -> Result<(), EngineError> {
2815 let pending = core::mem::take(&mut self.pending_foreign_keys);
2816 for (child, fk) in pending {
2817 let cols_snapshot = match self.active_catalog().get(&child) {
2821 Some(t) => t.schema().columns.clone(),
2822 None => continue,
2823 };
2824 let storage_fk =
2825 resolve_foreign_key(&child, &cols_snapshot, fk, self.active_catalog())?;
2826 let table = self
2827 .active_catalog_mut()
2828 .get_mut(&child)
2829 .expect("checked above");
2830 table.schema_mut().foreign_keys.push(storage_fk);
2831 }
2832 Ok(())
2833 }
2834
2835 #[must_use]
2839 pub fn session_param(&self, name: &str) -> Option<&str> {
2840 self.session_params
2841 .get(&name.to_ascii_lowercase())
2842 .map(String::as_str)
2843 }
2844
2845 fn ev_ctx<'a>(
2850 &'a self,
2851 columns: &'a [ColumnSchema],
2852 alias: Option<&'a str>,
2853 ) -> EvalContext<'a> {
2854 EvalContext::new(columns, alias)
2855 .with_default_text_search_config(self.session_param("default_text_search_config"))
2856 }
2857
2858 fn exec_compact_cold_segments(&mut self) -> Result<QueryResult, EngineError> {
2862 let target = COMPACTION_TARGET_DEFAULT_BYTES;
2863 let reports = self.compact_cold_segments_with_target(target)?;
2864 let columns = alloc::vec![
2865 ColumnSchema::new("table_name", DataType::Text, false),
2866 ColumnSchema::new("index_name", DataType::Text, false),
2867 ColumnSchema::new("sources_merged", DataType::BigInt, false),
2868 ColumnSchema::new("merged_segment_id", DataType::BigInt, false),
2869 ColumnSchema::new("merged_rows", DataType::BigInt, false),
2870 ColumnSchema::new("deleted_rows_pruned", DataType::BigInt, false),
2871 ColumnSchema::new("bytes_reclaimed_estimate", DataType::BigInt, false),
2872 ];
2873 let rows: Vec<Row> = reports
2874 .into_iter()
2875 .map(|(tname, iname, report)| {
2876 Row::new(alloc::vec![
2877 Value::Text(tname),
2878 Value::Text(iname),
2879 Value::BigInt(i64::try_from(report.sources.len()).unwrap_or(i64::MAX)),
2880 Value::BigInt(i64::from(report.merged_segment_id.unwrap_or(0))),
2881 Value::BigInt(i64::try_from(report.merged_rows).unwrap_or(i64::MAX)),
2882 Value::BigInt(i64::try_from(report.deleted_rows_pruned).unwrap_or(i64::MAX),),
2883 Value::BigInt(
2884 i64::try_from(report.bytes_reclaimed_estimate).unwrap_or(i64::MAX),
2885 ),
2886 ])
2887 })
2888 .collect();
2889 Ok(QueryResult::Rows { columns, rows })
2890 }
2891
2892 fn analyze_one_table(&mut self, table_name: &str) -> Result<(), EngineError> {
2897 let table = self.catalog.get(table_name).ok_or_else(|| {
2898 EngineError::Storage(StorageError::TableNotFound {
2899 name: table_name.to_string(),
2900 })
2901 })?;
2902 let schema = table.schema().clone();
2903 let row_count = table.rows().len();
2904 self.statistics.clear_table(table_name);
2909 for (col_pos, col_schema) in schema.columns.iter().enumerate() {
2910 if matches!(col_schema.ty, DataType::Vector { .. }) {
2913 continue;
2914 }
2915 let mut non_null_values: Vec<Value> = Vec::with_capacity(row_count);
2916 let mut nulls: u64 = 0;
2917 for row in table.rows() {
2918 match row.values.get(col_pos) {
2919 Some(Value::Null) | None => nulls += 1,
2920 Some(v) => non_null_values.push(v.clone()),
2921 }
2922 }
2923 non_null_values.sort_by(|a, b| sort_values_for_histogram(a, b));
2928 let non_null: Vec<String> = non_null_values.iter().map(canonical_value_repr).collect();
2929 let null_frac = if row_count == 0 {
2930 0.0
2931 } else {
2932 #[allow(clippy::cast_precision_loss)]
2933 let f = nulls as f32 / row_count as f32;
2934 f
2935 };
2936 let n_distinct = statistics::estimate_n_distinct(&non_null);
2937 let histogram_bounds = statistics::build_histogram(&non_null);
2938 self.statistics.set(
2939 table_name.to_string(),
2940 col_schema.name.clone(),
2941 statistics::ColumnStats {
2942 null_frac,
2943 n_distinct,
2944 histogram_bounds,
2945 },
2946 );
2947 }
2948 self.statistics.reset_modified(table_name);
2949 let cold_count = {
2955 let table = self
2956 .active_catalog()
2957 .get(table_name)
2958 .expect("table still present");
2959 table.count_cold_locators()
2960 };
2961 let table_mut = self
2962 .active_catalog_mut()
2963 .get_mut(table_name)
2964 .expect("table still present");
2965 table_mut.set_cold_row_count(cold_count);
2966 Ok(())
2967 }
2968
2969 fn exec_show_publications(&self) -> QueryResult {
2981 let columns = alloc::vec![
2982 ColumnSchema::new("name", DataType::Text, false),
2983 ColumnSchema::new("scope", DataType::Text, false),
2984 ColumnSchema::new("table_count", DataType::Int, true),
2985 ];
2986 let rows: Vec<Row> = self
2987 .publications
2988 .iter()
2989 .map(|(name, scope)| {
2990 let (scope_str, count_val) = match scope {
2991 spg_sql::ast::PublicationScope::AllTables => {
2992 ("FOR ALL TABLES".to_string(), Value::Null)
2993 }
2994 spg_sql::ast::PublicationScope::ForTables(ts) => (
2995 alloc::format!("FOR TABLE {}", ts.join(", ")),
2996 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2997 ),
2998 spg_sql::ast::PublicationScope::AllTablesExcept(ts) => (
2999 alloc::format!("FOR ALL TABLES EXCEPT {}", ts.join(", ")),
3000 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
3001 ),
3002 };
3003 Row::new(alloc::vec![
3004 Value::Text(name.clone()),
3005 Value::Text(scope_str),
3006 count_val,
3007 ])
3008 })
3009 .collect();
3010 QueryResult::Rows { columns, rows }
3011 }
3012
3013 fn exec_show_users(&self) -> QueryResult {
3015 let columns = alloc::vec![
3016 ColumnSchema::new("name", DataType::Text, false),
3017 ColumnSchema::new("role", DataType::Text, false),
3018 ];
3019 let rows: Vec<Row> = self
3020 .users
3021 .iter()
3022 .map(|(name, rec)| {
3023 Row::new(alloc::vec![
3024 Value::Text(name.to_string()),
3025 Value::Text(rec.role.as_str().to_string()),
3026 ])
3027 })
3028 .collect();
3029 QueryResult::Rows { columns, rows }
3030 }
3031
3032 fn exec_create_user(&mut self, s: &CreateUserStatement) -> Result<QueryResult, EngineError> {
3033 if self.in_transaction() {
3034 return Err(EngineError::Unsupported(
3035 "CREATE USER is not allowed inside a transaction".into(),
3036 ));
3037 }
3038 let role = users::Role::parse(&s.role).ok_or_else(|| {
3039 EngineError::Unsupported(alloc::format!("invalid role: {:?}", s.role))
3040 })?;
3041 let salt = self.salt_fn.map_or_else(
3045 || {
3046 let mut s_bytes = [0u8; 16];
3047 let digest = spg_crypto::hash(s.name.as_bytes());
3048 s_bytes.copy_from_slice(&digest[..16]);
3049 s_bytes
3050 },
3051 |f| f(),
3052 );
3053 self.users
3054 .create(&s.name, &s.password, role, salt)
3055 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE USER: {e}")))?;
3056 Ok(QueryResult::CommandOk {
3057 affected: 1,
3058 modified_catalog: true,
3059 })
3060 }
3061
3062 fn exec_drop_user(&mut self, name: &str) -> Result<QueryResult, EngineError> {
3063 if self.in_transaction() {
3064 return Err(EngineError::Unsupported(
3065 "DROP USER is not allowed inside a transaction".into(),
3066 ));
3067 }
3068 self.users
3069 .drop(name)
3070 .map_err(|e| EngineError::Unsupported(alloc::format!("DROP USER: {e}")))?;
3071 Ok(QueryResult::CommandOk {
3072 affected: 1,
3073 modified_catalog: true,
3074 })
3075 }
3076
3077 fn exec_create_function(
3083 &mut self,
3084 s: spg_sql::ast::CreateFunctionStatement,
3085 ) -> Result<QueryResult, EngineError> {
3086 let args_repr = render_function_args(&s.args);
3087 let returns = match &s.returns {
3088 spg_sql::ast::FunctionReturn::Trigger => alloc::string::String::from("TRIGGER"),
3089 spg_sql::ast::FunctionReturn::Void => alloc::string::String::from("VOID"),
3090 spg_sql::ast::FunctionReturn::Type(t) => alloc::format!("{t}"),
3091 spg_sql::ast::FunctionReturn::Other(s) => s.clone(),
3092 };
3093 let body_text = match &s.body {
3094 spg_sql::ast::FunctionBody::PlPgSql(b) => alloc::format!("{b}"),
3095 spg_sql::ast::FunctionBody::Raw(s) => s.clone(),
3096 };
3097 let def = spg_storage::FunctionDef {
3098 name: s.name.clone(),
3099 args_repr,
3100 returns,
3101 language: s.language.clone(),
3102 body: body_text,
3103 };
3104 self.active_catalog_mut()
3105 .create_function(def, s.or_replace)
3106 .map_err(EngineError::Storage)?;
3107 Ok(QueryResult::CommandOk {
3108 affected: 0,
3109 modified_catalog: true,
3110 })
3111 }
3112
3113 fn exec_create_trigger(
3118 &mut self,
3119 s: spg_sql::ast::CreateTriggerStatement,
3120 ) -> Result<QueryResult, EngineError> {
3121 let timing = match s.timing {
3122 spg_sql::ast::TriggerTiming::Before => "BEFORE",
3123 spg_sql::ast::TriggerTiming::After => "AFTER",
3124 spg_sql::ast::TriggerTiming::InsteadOf => "INSTEAD OF",
3125 };
3126 let events: Vec<alloc::string::String> = s
3127 .events
3128 .iter()
3129 .map(|e| match e {
3130 spg_sql::ast::TriggerEvent::Insert => alloc::string::String::from("INSERT"),
3131 spg_sql::ast::TriggerEvent::Update => alloc::string::String::from("UPDATE"),
3132 spg_sql::ast::TriggerEvent::Delete => alloc::string::String::from("DELETE"),
3133 spg_sql::ast::TriggerEvent::Truncate => alloc::string::String::from("TRUNCATE"),
3134 })
3135 .collect();
3136 let for_each = match s.for_each {
3137 spg_sql::ast::TriggerForEach::Row => "ROW",
3138 spg_sql::ast::TriggerForEach::Statement => "STATEMENT",
3139 };
3140 let def = spg_storage::TriggerDef {
3141 name: s.name.clone(),
3142 table: s.table.clone(),
3143 timing: alloc::string::String::from(timing),
3144 events,
3145 for_each: alloc::string::String::from(for_each),
3146 function: s.function.clone(),
3147 update_columns: s.update_columns.clone(),
3148 enabled: true,
3151 };
3152 self.active_catalog_mut()
3153 .create_trigger(def, s.or_replace)
3154 .map_err(EngineError::Storage)?;
3155 Ok(QueryResult::CommandOk {
3156 affected: 0,
3157 modified_catalog: true,
3158 })
3159 }
3160
3161 fn exec_drop_trigger(
3162 &mut self,
3163 name: &str,
3164 table: &str,
3165 if_exists: bool,
3166 ) -> Result<QueryResult, EngineError> {
3167 let removed = self.active_catalog_mut().drop_trigger(name, table);
3168 if !removed && !if_exists {
3169 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3170 alloc::format!("trigger {name:?} on {table:?} does not exist"),
3171 )));
3172 }
3173 Ok(QueryResult::CommandOk {
3174 affected: usize::from(removed),
3175 modified_catalog: removed,
3176 })
3177 }
3178
3179 fn exec_drop_function(
3180 &mut self,
3181 name: &str,
3182 if_exists: bool,
3183 ) -> Result<QueryResult, EngineError> {
3184 let removed = self.active_catalog_mut().drop_function(name);
3185 if !removed && !if_exists {
3186 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3187 alloc::format!("function {name:?} does not exist"),
3188 )));
3189 }
3190 Ok(QueryResult::CommandOk {
3191 affected: usize::from(removed),
3192 modified_catalog: removed,
3193 })
3194 }
3195
3196 fn exec_create_sequence(
3200 &mut self,
3201 s: spg_sql::ast::CreateSequenceStatement,
3202 ) -> Result<QueryResult, EngineError> {
3203 use spg_sql::ast::{SeqBound, SequenceDataType as AstDt};
3204 use spg_storage::{SequenceDataType, SequenceDef};
3205 let dt = match s.data_type {
3206 None => SequenceDataType::BigInt,
3207 Some(AstDt::SmallInt) => SequenceDataType::SmallInt,
3208 Some(AstDt::Int) => SequenceDataType::Int,
3209 Some(AstDt::BigInt) => SequenceDataType::BigInt,
3210 };
3211 let increment = s.options.increment.unwrap_or(1);
3212 if increment == 0 {
3213 return Err(EngineError::Unsupported(
3214 "INCREMENT must not be zero".into(),
3215 ));
3216 }
3217 let (def_min, def_max) = dt.default_bounds(increment > 0);
3218 let min_value = match s.options.min_value {
3219 None | Some(SeqBound::NoBound) => def_min,
3220 Some(SeqBound::Value(n)) => n,
3221 };
3222 let max_value = match s.options.max_value {
3223 None | Some(SeqBound::NoBound) => def_max,
3224 Some(SeqBound::Value(n)) => n,
3225 };
3226 if min_value > max_value {
3227 return Err(EngineError::Unsupported(alloc::format!(
3228 "MINVALUE ({min_value}) must be <= MAXVALUE ({max_value})"
3229 )));
3230 }
3231 let start = s
3232 .options
3233 .start
3234 .unwrap_or(if increment > 0 { min_value } else { max_value });
3235 if start < min_value || start > max_value {
3236 return Err(EngineError::Unsupported(alloc::format!(
3237 "START WITH ({start}) is outside MINVALUE..MAXVALUE ({min_value}..{max_value})"
3238 )));
3239 }
3240 let cache = s.options.cache.unwrap_or(1);
3241 if cache < 1 {
3242 return Err(EngineError::Unsupported("CACHE must be >= 1".into()));
3243 }
3244 let cycle = s.options.cycle.unwrap_or(false);
3245 let owned_by = match s.options.owned_by {
3246 None | Some(spg_sql::ast::SequenceOwnedBy::None) => None,
3247 Some(spg_sql::ast::SequenceOwnedBy::Column { table, column }) => Some((table, column)),
3248 };
3249 let def = SequenceDef {
3250 name: s.name.clone(),
3251 data_type: dt,
3252 start,
3253 increment,
3254 min_value,
3255 max_value,
3256 cache,
3257 cycle,
3258 owned_by,
3259 last_value: start,
3260 is_called: false,
3261 };
3262 self.active_catalog_mut()
3263 .create_sequence(def, s.if_not_exists)
3264 .map_err(EngineError::Storage)?;
3265 Ok(QueryResult::CommandOk {
3266 affected: 0,
3267 modified_catalog: !self.in_transaction(),
3268 })
3269 }
3270
3271 fn exec_alter_sequence(
3274 &mut self,
3275 s: spg_sql::ast::AlterSequenceStatement,
3276 ) -> Result<QueryResult, EngineError> {
3277 use spg_sql::ast::SeqBound;
3278 self.ensure_implicit_sequence(&s.name);
3281 let cat = self.active_catalog_mut();
3282 if !cat.sequences().contains_key(&s.name) {
3283 if s.if_exists {
3284 return Ok(QueryResult::CommandOk {
3285 affected: 0,
3286 modified_catalog: false,
3287 });
3288 }
3289 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3290 alloc::format!("sequence {:?} does not exist", s.name),
3291 )));
3292 }
3293 let min_value = match s.options.min_value {
3294 None => None,
3295 Some(SeqBound::NoBound) => None, Some(SeqBound::Value(n)) => Some(n),
3297 };
3298 let max_value = match s.options.max_value {
3299 None => None,
3300 Some(SeqBound::NoBound) => None,
3301 Some(SeqBound::Value(n)) => Some(n),
3302 };
3303 let owned_by = s.options.owned_by.map(|ob| match ob {
3304 spg_sql::ast::SequenceOwnedBy::None => None,
3305 spg_sql::ast::SequenceOwnedBy::Column { table, column } => Some((table, column)),
3306 });
3307 cat.alter_sequence(
3308 &s.name,
3309 s.options.increment,
3310 min_value,
3311 max_value,
3312 s.options.start,
3313 s.options.restart,
3314 s.options.cache,
3315 s.options.cycle,
3316 owned_by,
3317 )
3318 .map_err(EngineError::Storage)?;
3319 Ok(QueryResult::CommandOk {
3320 affected: 0,
3321 modified_catalog: !self.in_transaction(),
3322 })
3323 }
3324
3325 fn pre_resolve_sequence_calls_in_statement(
3330 &mut self,
3331 stmt: &mut Statement,
3332 ) -> Result<(), EngineError> {
3333 match stmt {
3334 Statement::Select(s) => self.pre_resolve_sequence_calls_in_select(s),
3335 Statement::Insert(s) => {
3336 for tuple in &mut s.rows {
3337 for cell in tuple.iter_mut() {
3338 self.resolve_sequence_calls_in_expr(cell)?;
3339 }
3340 }
3341 Ok(())
3342 }
3343 Statement::Update(s) => {
3344 for (_col, expr) in &mut s.assignments {
3345 self.resolve_sequence_calls_in_expr(expr)?;
3346 }
3347 if let Some(w) = &mut s.where_ {
3348 self.resolve_sequence_calls_in_expr(w)?;
3349 }
3350 Ok(())
3351 }
3352 Statement::Delete(s) => {
3353 if let Some(w) = &mut s.where_ {
3354 self.resolve_sequence_calls_in_expr(w)?;
3355 }
3356 Ok(())
3357 }
3358 _ => Ok(()),
3359 }
3360 }
3361
3362 fn pre_resolve_sequence_calls_in_select(
3363 &mut self,
3364 s: &mut spg_sql::ast::SelectStatement,
3365 ) -> Result<(), EngineError> {
3366 for item in &mut s.items {
3367 match item {
3368 spg_sql::ast::SelectItem::Expr { expr, .. } => {
3369 self.resolve_sequence_calls_in_expr(expr)?;
3370 }
3371 spg_sql::ast::SelectItem::Wildcard => {}
3372 }
3373 }
3374 if let Some(w) = &mut s.where_ {
3375 self.resolve_sequence_calls_in_expr(w)?;
3376 }
3377 Ok(())
3378 }
3379
3380 #[allow(clippy::too_many_lines)]
3388 fn resolve_sequence_calls_in_expr(&mut self, expr: &mut Expr) -> Result<(), EngineError> {
3389 match expr {
3390 Expr::Literal(_) | Expr::Column(_) | Expr::Placeholder(_) => Ok(()),
3391 Expr::FunctionCall { name, args } => {
3392 for a in args.iter_mut() {
3396 self.resolve_sequence_calls_in_expr(a)?;
3397 }
3398 let lc = name.to_ascii_lowercase();
3399 if lc == "nextval" || lc == "currval" || lc == "setval" {
3400 let v = self.eval_sequence_call(&lc, args)?;
3401 *expr = Expr::Literal(value_to_literal(v));
3402 } else if lc == "pg_get_serial_sequence" && args.len() == 2 {
3403 let lit = |e: &Expr| -> Option<String> {
3408 match e {
3409 Expr::Literal(spg_sql::ast::Literal::String(v)) => {
3410 let t = v.strip_prefix("public.").unwrap_or(v).trim_matches('"');
3411 Some(t.to_string())
3412 }
3413 _ => None,
3414 }
3415 };
3416 if let (Some(t), Some(c)) = (lit(&args[0]), lit(&args[1])) {
3417 let is_serial = self.active_catalog().get(&t).is_some_and(|tb| {
3418 tb.schema()
3419 .columns
3420 .iter()
3421 .any(|col| col.name == c && col.auto_increment)
3422 });
3423 *expr = if is_serial {
3424 Expr::Literal(spg_sql::ast::Literal::String(alloc::format!(
3425 "public.{t}_{c}_seq"
3426 )))
3427 } else {
3428 Expr::Literal(spg_sql::ast::Literal::Null)
3429 };
3430 }
3431 }
3432 Ok(())
3433 }
3434 Expr::Binary { lhs, rhs, .. } => {
3435 self.resolve_sequence_calls_in_expr(lhs)?;
3436 self.resolve_sequence_calls_in_expr(rhs)
3437 }
3438 Expr::Unary { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3439 Expr::Cast { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3440 Expr::IsNull { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3441 Expr::Like { expr, pattern, .. } => {
3442 self.resolve_sequence_calls_in_expr(expr)?;
3443 self.resolve_sequence_calls_in_expr(pattern)
3444 }
3445 Expr::Extract { source, .. } => self.resolve_sequence_calls_in_expr(source),
3446 Expr::Array(items) => {
3447 for it in items.iter_mut() {
3448 self.resolve_sequence_calls_in_expr(it)?;
3449 }
3450 Ok(())
3451 }
3452 _ => Ok(()),
3457 }
3458 }
3459
3460 fn ensure_implicit_sequence(&mut self, seq_name: &str) {
3467 if self.active_catalog().sequences().contains_key(seq_name) {
3468 return;
3469 }
3470 let Some(rest) = seq_name.strip_suffix("_seq") else {
3471 return;
3472 };
3473 let mut found: Option<(String, String, i64)> = None;
3474 for tname in self.active_catalog().table_names() {
3475 let Some(table) = self.active_catalog().get(&tname) else {
3476 continue;
3477 };
3478 for (i, col) in table.schema().columns.iter().enumerate() {
3479 if col.auto_increment && alloc::format!("{tname}_{}", col.name) == rest {
3480 let next = table.next_auto_value(i).unwrap_or(1);
3481 found = Some((tname.clone(), col.name.clone(), next - 1));
3482 break;
3483 }
3484 }
3485 if found.is_some() {
3486 break;
3487 }
3488 }
3489 let Some((tname, cname, last)) = found else {
3490 return;
3491 };
3492 let def = spg_storage::SequenceDef {
3493 name: seq_name.to_string(),
3494 data_type: spg_storage::SequenceDataType::BigInt,
3495 start: 1,
3496 increment: 1,
3497 min_value: 1,
3498 max_value: i64::MAX,
3499 cache: 1,
3500 cycle: false,
3501 owned_by: Some((tname, cname)),
3502 last_value: last.max(0),
3503 is_called: last > 0,
3504 };
3505 let _ = self.active_catalog_mut().create_sequence(def, true);
3506 }
3507
3508 fn eval_sequence_call(&mut self, op: &str, args: &[Expr]) -> Result<Value, EngineError> {
3512 if args.is_empty() {
3513 return Err(EngineError::Unsupported(alloc::format!(
3514 "{op}() takes at least one argument"
3515 )));
3516 }
3517 let seq_name = match &args[0] {
3518 Expr::Literal(spg_sql::ast::Literal::String(s)) => {
3519 let trimmed = s
3525 .strip_prefix("public.")
3526 .or_else(|| s.strip_prefix("pg_catalog."))
3527 .unwrap_or(s);
3528 trimmed.to_string()
3529 }
3530 Expr::Cast { expr, .. } => {
3535 if let Expr::Literal(spg_sql::ast::Literal::String(s)) = expr.as_ref() {
3536 let trimmed = s
3537 .strip_prefix("public.")
3538 .or_else(|| s.strip_prefix("pg_catalog."))
3539 .unwrap_or(s);
3540 trimmed.to_string()
3541 } else {
3542 return Err(EngineError::Unsupported(alloc::format!(
3543 "{op}() first argument must be a literal sequence name"
3544 )));
3545 }
3546 }
3547 other => {
3548 return Err(EngineError::Unsupported(alloc::format!(
3549 "{op}() first argument must be a literal sequence name, got {other:?}"
3550 )));
3551 }
3552 };
3553 self.ensure_implicit_sequence(&seq_name);
3554 match op {
3555 "nextval" => {
3556 let v = self
3557 .active_catalog_mut()
3558 .sequence_next_value(&seq_name)
3559 .map_err(EngineError::Storage)?;
3560 Ok(Value::BigInt(v))
3561 }
3562 "currval" => {
3563 let v = self
3564 .active_catalog()
3565 .sequence_current_value(&seq_name)
3566 .map_err(EngineError::Storage)?;
3567 Ok(Value::BigInt(v))
3568 }
3569 "setval" => {
3570 if args.len() < 2 || args.len() > 3 {
3571 return Err(EngineError::Unsupported(alloc::format!(
3572 "setval() takes 2 or 3 arguments, got {}",
3573 args.len()
3574 )));
3575 }
3576 let value = match &args[1] {
3577 Expr::Literal(spg_sql::ast::Literal::Integer(n)) => *n,
3578 other => {
3579 return Err(EngineError::Unsupported(alloc::format!(
3580 "setval() value argument must be a literal integer, got {other:?}"
3581 )));
3582 }
3583 };
3584 let is_called = if args.len() == 3 {
3585 match &args[2] {
3586 Expr::Literal(spg_sql::ast::Literal::Bool(b)) => *b,
3587 other => {
3588 return Err(EngineError::Unsupported(alloc::format!(
3589 "setval() is_called argument must be a literal BOOL, got {other:?}"
3590 )));
3591 }
3592 }
3593 } else {
3594 true
3595 };
3596 let v = self
3597 .active_catalog_mut()
3598 .sequence_set_value(&seq_name, value, is_called)
3599 .map_err(EngineError::Storage)?;
3600 Ok(Value::BigInt(v))
3601 }
3602 other => Err(EngineError::Unsupported(alloc::format!(
3603 "unknown sequence op {other:?}"
3604 ))),
3605 }
3606 }
3607
3608 fn expand_views_in_select(
3617 &self,
3618 stmt: &SelectStatement,
3619 ) -> Result<Option<SelectStatement>, EngineError> {
3620 let cat = self.active_catalog();
3621 let mut referenced: Vec<String> = Vec::new();
3622 if let Some(from) = &stmt.from {
3623 collect_view_refs(&from.primary, cat, &mut referenced);
3624 for j in &from.joins {
3625 collect_view_refs(&j.table, cat, &mut referenced);
3626 }
3627 }
3628 referenced.retain(|n| !stmt.ctes.iter().any(|c| c.name == *n));
3631 if referenced.is_empty() {
3632 return Ok(None);
3633 }
3634 let mut new_ctes: Vec<spg_sql::ast::Cte> = Vec::with_capacity(referenced.len());
3635 for name in &referenced {
3636 let view = cat.views().get(name).ok_or_else(|| {
3637 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3638 "view {name:?} disappeared mid-expansion"
3639 )))
3640 })?;
3641 let parsed = spg_sql::parser::parse_statement(&view.body).map_err(|e| {
3642 EngineError::Unsupported(alloc::format!("view {name:?} body re-parse failed: {e}"))
3643 })?;
3644 let Statement::Select(body) = parsed else {
3645 return Err(EngineError::Unsupported(alloc::format!(
3646 "view {name:?} body is not a SELECT (catalog corruption)"
3647 )));
3648 };
3649 new_ctes.push(spg_sql::ast::Cte {
3650 name: name.clone(),
3651 body,
3652 recursive: false,
3653 column_overrides: view.columns.clone(),
3654 });
3655 }
3656 let mut out = stmt.clone();
3657 new_ctes.extend(out.ctes);
3659 out.ctes = new_ctes;
3660 Ok(Some(out))
3661 }
3662
3663 fn exec_create_view(
3667 &mut self,
3668 s: spg_sql::ast::CreateViewStatement,
3669 ) -> Result<QueryResult, EngineError> {
3670 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body));
3674 let def = spg_storage::ViewDef {
3675 name: s.name.clone(),
3676 columns: s.columns,
3677 body: body_repr,
3678 };
3679 self.active_catalog_mut()
3680 .create_view(def, s.or_replace, s.if_not_exists)
3681 .map_err(EngineError::Storage)?;
3682 Ok(QueryResult::CommandOk {
3683 affected: 0,
3684 modified_catalog: !self.in_transaction(),
3685 })
3686 }
3687
3688 fn exec_create_type(
3693 &mut self,
3694 s: spg_sql::ast::CreateTypeStatement,
3695 ) -> Result<QueryResult, EngineError> {
3696 let cat = self.active_catalog();
3699 if cat.get(&s.name).is_some() {
3700 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3701 alloc::format!("type {:?} would shadow an existing table", s.name),
3702 )));
3703 }
3704 if cat.sequences().contains_key(&s.name) {
3705 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3706 alloc::format!("type {:?} would shadow an existing sequence", s.name),
3707 )));
3708 }
3709 if cat.views().contains_key(&s.name) {
3710 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3711 alloc::format!("type {:?} would shadow an existing view", s.name),
3712 )));
3713 }
3714 let def = match s.kind {
3715 spg_sql::ast::TypeKind::Enum { labels } => {
3716 if labels.is_empty() {
3717 return Err(EngineError::Unsupported(
3718 "CREATE TYPE … AS ENUM requires at least one label".into(),
3719 ));
3720 }
3721 for i in 0..labels.len() {
3723 for j in (i + 1)..labels.len() {
3724 if labels[i] == labels[j] {
3725 return Err(EngineError::Unsupported(alloc::format!(
3726 "CREATE TYPE {:?}: duplicate ENUM label {:?}",
3727 s.name,
3728 labels[i]
3729 )));
3730 }
3731 }
3732 }
3733 spg_storage::EnumDef {
3734 name: s.name.clone(),
3735 labels,
3736 }
3737 }
3738 };
3739 self.active_catalog_mut()
3740 .create_enum_type(def)
3741 .map_err(EngineError::Storage)?;
3742 Ok(QueryResult::CommandOk {
3743 affected: 0,
3744 modified_catalog: !self.in_transaction(),
3745 })
3746 }
3747
3748 fn exec_create_domain(
3753 &mut self,
3754 s: spg_sql::ast::CreateDomainStatement,
3755 ) -> Result<QueryResult, EngineError> {
3756 let cat = self.active_catalog();
3757 if cat.domain_types().contains_key(&s.name) {
3758 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3759 alloc::format!("domain {:?} already exists", s.name),
3760 )));
3761 }
3762 if cat.get(&s.name).is_some()
3763 || cat.sequences().contains_key(&s.name)
3764 || cat.views().contains_key(&s.name)
3765 || cat.enum_types().contains_key(&s.name)
3766 {
3767 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3768 alloc::format!("domain {:?} would shadow an existing object", s.name),
3769 )));
3770 }
3771 let base_type = column_type_to_data_type(s.base_type);
3772 let default = s.default.as_ref().map(|e| alloc::format!("{e}"));
3773 let checks = s
3774 .checks
3775 .iter()
3776 .map(|e| alloc::format!("{e}"))
3777 .collect::<Vec<_>>();
3778 let def = spg_storage::DomainDef {
3779 name: s.name.clone(),
3780 base_type,
3781 nullable: !s.not_null,
3782 default,
3783 checks,
3784 };
3785 self.active_catalog_mut()
3786 .create_domain_type(def)
3787 .map_err(EngineError::Storage)?;
3788 Ok(QueryResult::CommandOk {
3789 affected: 0,
3790 modified_catalog: !self.in_transaction(),
3791 })
3792 }
3793
3794 fn exec_drop_domain(
3796 &mut self,
3797 names: &[String],
3798 if_exists: bool,
3799 ) -> Result<QueryResult, EngineError> {
3800 let mut removed = 0usize;
3801 for name in names {
3802 let was_present = self.active_catalog_mut().drop_domain_type(name);
3803 if was_present {
3804 removed += 1;
3805 } else if !if_exists {
3806 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3807 alloc::format!("domain {name:?} does not exist"),
3808 )));
3809 }
3810 }
3811 Ok(QueryResult::CommandOk {
3812 affected: removed,
3813 modified_catalog: removed > 0 && !self.in_transaction(),
3814 })
3815 }
3816
3817 fn exec_create_schema(
3823 &mut self,
3824 name: String,
3825 if_not_exists: bool,
3826 ) -> Result<QueryResult, EngineError> {
3827 self.active_catalog_mut()
3828 .create_schema(name, if_not_exists)
3829 .map_err(EngineError::Storage)?;
3830 Ok(QueryResult::CommandOk {
3831 affected: 0,
3832 modified_catalog: !self.in_transaction(),
3833 })
3834 }
3835
3836 fn exec_drop_schema(
3840 &mut self,
3841 names: &[String],
3842 if_exists: bool,
3843 ) -> Result<QueryResult, EngineError> {
3844 let mut removed = 0usize;
3845 for name in names {
3846 let was_present = self
3847 .active_catalog_mut()
3848 .drop_schema(name)
3849 .map_err(EngineError::Storage)?;
3850 if was_present {
3851 removed += 1;
3852 } else if !if_exists {
3853 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3854 alloc::format!("schema {name:?} does not exist"),
3855 )));
3856 }
3857 }
3858 Ok(QueryResult::CommandOk {
3859 affected: removed,
3860 modified_catalog: removed > 0 && !self.in_transaction(),
3861 })
3862 }
3863
3864 fn exec_drop_type(
3869 &mut self,
3870 names: &[String],
3871 if_exists: bool,
3872 ) -> Result<QueryResult, EngineError> {
3873 let mut removed = 0usize;
3874 for name in names {
3875 let was_present = self.active_catalog_mut().drop_enum_type(name);
3876 if was_present {
3877 removed += 1;
3878 } else if !if_exists {
3879 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3880 alloc::format!("type {name:?} does not exist"),
3881 )));
3882 }
3883 }
3884 Ok(QueryResult::CommandOk {
3885 affected: removed,
3886 modified_catalog: removed > 0 && !self.in_transaction(),
3887 })
3888 }
3889
3890 fn exec_create_materialized_view(
3895 &mut self,
3896 s: spg_sql::ast::CreateMaterializedViewStatement,
3897 ) -> Result<QueryResult, EngineError> {
3898 let cat = self.active_catalog();
3900 if cat.materialized_views().contains_key(&s.name) || cat.get(&s.name).is_some() {
3901 if s.if_not_exists {
3902 return Ok(QueryResult::CommandOk {
3903 affected: 0,
3904 modified_catalog: false,
3905 });
3906 }
3907 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3908 alloc::format!("materialized view {:?} already exists", s.name),
3909 )));
3910 }
3911 if cat.views().contains_key(&s.name) {
3912 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3913 alloc::format!(
3914 "materialized view {:?} would shadow an existing view",
3915 s.name
3916 ),
3917 )));
3918 }
3919 if cat.sequences().contains_key(&s.name) {
3920 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3921 alloc::format!(
3922 "materialized view {:?} would shadow an existing sequence",
3923 s.name
3924 ),
3925 )));
3926 }
3927 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body.clone()));
3929 let result = self.exec_select_cancel(&s.body, CancelToken::none())?;
3934 let (mut cols, rows) = match result {
3935 QueryResult::Rows { columns, rows } => (columns, rows),
3936 other => {
3937 return Err(EngineError::Unsupported(alloc::format!(
3938 "CREATE MATERIALIZED VIEW body did not return rows: {other:?}"
3939 )));
3940 }
3941 };
3942 if !s.columns.is_empty() {
3944 if s.columns.len() != cols.len() {
3945 return Err(EngineError::Unsupported(alloc::format!(
3946 "CREATE MATERIALIZED VIEW {:?}: column list has {} names but body returns {}",
3947 s.name,
3948 s.columns.len(),
3949 cols.len()
3950 )));
3951 }
3952 for (c, name) in cols.iter_mut().zip(s.columns.iter()) {
3953 c.name.clone_from(name);
3954 }
3955 }
3956 cols = infer_column_types(&cols, &rows);
3959 let schema = spg_storage::TableSchema::new(s.name.clone(), cols);
3960 let cat = self.active_catalog_mut();
3961 cat.create_table(schema).map_err(EngineError::Storage)?;
3962 if s.with_data {
3963 let table = cat
3964 .get_mut(&s.name)
3965 .expect("just-created materialized-view backing table must exist");
3966 for row in rows {
3967 table.insert(row).map_err(EngineError::Storage)?;
3968 }
3969 }
3970 cat.register_materialized_view(s.name.clone(), body_repr);
3971 Ok(QueryResult::CommandOk {
3972 affected: 0,
3973 modified_catalog: !self.in_transaction(),
3974 })
3975 }
3976
3977 fn exec_refresh_materialized_view(
3981 &mut self,
3982 name: &str,
3983 with_data: bool,
3984 ) -> Result<QueryResult, EngineError> {
3985 let source = self
3986 .active_catalog()
3987 .materialized_views()
3988 .get(name)
3989 .cloned()
3990 .ok_or_else(|| {
3991 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3992 "materialized view {name:?} does not exist"
3993 )))
3994 })?;
3995 {
3998 let cat = self.active_catalog_mut();
3999 let table = cat.get_mut(name).ok_or_else(|| {
4000 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
4001 "materialized view {name:?} backing table missing"
4002 )))
4003 })?;
4004 table.truncate();
4005 }
4006 if !with_data {
4007 return Ok(QueryResult::CommandOk {
4008 affected: 0,
4009 modified_catalog: !self.in_transaction(),
4010 });
4011 }
4012 let parsed = spg_sql::parser::parse_statement(&source).map_err(|e| {
4013 EngineError::Unsupported(alloc::format!(
4014 "materialized view {name:?} body re-parse failed: {e}"
4015 ))
4016 })?;
4017 let Statement::Select(body) = parsed else {
4018 return Err(EngineError::Unsupported(alloc::format!(
4019 "materialized view {name:?} body is not a SELECT (catalog corruption)"
4020 )));
4021 };
4022 let rows = match self.exec_select_cancel(&body, CancelToken::none())? {
4023 QueryResult::Rows { rows, .. } => rows,
4024 other => {
4025 return Err(EngineError::Unsupported(alloc::format!(
4026 "REFRESH MATERIALIZED VIEW {name:?} body did not return rows: {other:?}"
4027 )));
4028 }
4029 };
4030 let cat = self.active_catalog_mut();
4031 let table = cat.get_mut(name).expect("backing table verified above");
4032 let affected = rows.len();
4033 for row in rows {
4034 table.insert(row).map_err(EngineError::Storage)?;
4035 }
4036 Ok(QueryResult::CommandOk {
4037 affected,
4038 modified_catalog: !self.in_transaction(),
4039 })
4040 }
4041
4042 fn exec_drop_materialized_view(
4045 &mut self,
4046 names: &[String],
4047 if_exists: bool,
4048 ) -> Result<QueryResult, EngineError> {
4049 let mut removed = 0usize;
4050 for name in names {
4051 let was_present = self
4052 .active_catalog_mut()
4053 .drop_materialized_view_source(name);
4054 if was_present {
4055 self.active_catalog_mut().drop_table(name);
4057 removed += 1;
4058 } else if !if_exists {
4059 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
4060 alloc::format!("materialized view {name:?} does not exist"),
4061 )));
4062 }
4063 }
4064 Ok(QueryResult::CommandOk {
4065 affected: removed,
4066 modified_catalog: removed > 0 && !self.in_transaction(),
4067 })
4068 }
4069
4070 fn exec_drop_view(
4072 &mut self,
4073 names: &[String],
4074 if_exists: bool,
4075 ) -> Result<QueryResult, EngineError> {
4076 let mut removed = 0usize;
4077 for name in names {
4078 let was_present = self.active_catalog_mut().drop_view(name);
4079 if !was_present && !if_exists {
4080 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
4081 alloc::format!("view {name:?} does not exist"),
4082 )));
4083 }
4084 if was_present {
4085 removed += 1;
4086 }
4087 }
4088 Ok(QueryResult::CommandOk {
4089 affected: removed,
4090 modified_catalog: removed > 0 && !self.in_transaction(),
4091 })
4092 }
4093
4094 fn exec_drop_sequence(
4096 &mut self,
4097 names: &[String],
4098 if_exists: bool,
4099 ) -> Result<QueryResult, EngineError> {
4100 let mut removed = 0usize;
4101 for name in names {
4102 let was_present = self.active_catalog_mut().drop_sequence(name);
4103 if !was_present && !if_exists {
4104 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
4105 alloc::format!("sequence {name:?} does not exist"),
4106 )));
4107 }
4108 if was_present {
4109 removed += 1;
4110 }
4111 }
4112 Ok(QueryResult::CommandOk {
4113 affected: removed,
4114 modified_catalog: removed > 0 && !self.in_transaction(),
4115 })
4116 }
4117
4118 fn exec_update_cancel(
4125 &mut self,
4126 stmt: &spg_sql::ast::UpdateStatement,
4127 cancel: CancelToken<'_>,
4128 ) -> Result<QueryResult, EngineError> {
4129 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
4138 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
4139 let trigger_session_cfg: Option<String> = self
4140 .session_params
4141 .get("default_text_search_config")
4142 .cloned();
4143 if let Some(w) = &stmt.where_ {
4151 let schema_cols = self
4152 .active_catalog()
4153 .get(&stmt.table)
4154 .ok_or_else(|| {
4155 EngineError::Storage(StorageError::TableNotFound {
4156 name: stmt.table.clone(),
4157 })
4158 })?
4159 .schema()
4160 .columns
4161 .clone();
4162 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4163 && let Some(idx_name) = self
4164 .active_catalog()
4165 .get(&stmt.table)
4166 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4167 {
4168 let _ = self
4172 .active_catalog_mut()
4173 .promote_cold_row(&stmt.table, &idx_name, &key);
4174 }
4175 }
4176
4177 let ts_cfg: Option<String> = self
4180 .session_param("default_text_search_config")
4181 .map(String::from);
4182 let clock_for_on_update = self.clock;
4186 let table = self
4187 .active_catalog_mut()
4188 .get_mut(&stmt.table)
4189 .ok_or_else(|| {
4190 EngineError::Storage(StorageError::TableNotFound {
4191 name: stmt.table.clone(),
4192 })
4193 })?;
4194 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4195 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
4199 for (col, expr) in &stmt.assignments {
4200 let pos = schema_cols
4201 .iter()
4202 .position(|c| c.name == *col)
4203 .ok_or_else(|| {
4204 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
4205 })?;
4206 targets.push((pos, expr));
4207 }
4208 let mut on_update_overrides: Vec<(usize, String)> = Vec::new();
4216 for (i, col) in schema_cols.iter().enumerate() {
4217 if targets.iter().any(|(p, _)| *p == i) {
4218 continue;
4219 }
4220 if let Some(src) = &col.on_update_runtime {
4221 on_update_overrides.push((i, src.clone()));
4222 }
4223 }
4224 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4225 .with_default_text_search_config(ts_cfg.as_deref());
4226 let seek_positions: Option<Vec<usize>> = stmt
4241 .where_
4242 .as_ref()
4243 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4244 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
4245 let candidate_positions: Vec<usize> = match &seek_positions {
4246 Some(list) => list.clone(),
4247 None => (0..table.row_count()).collect(),
4248 };
4249 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4250 if loop_n.is_multiple_of(256) {
4254 cancel.check()?;
4255 }
4256 let Some(row) = table.rows().get(i) else {
4257 continue;
4258 };
4259 if let Some(w) = &stmt.where_ {
4260 let cond = eval::eval_expr(w, row, &ctx)?;
4261 if !matches!(cond, Value::Bool(true)) {
4262 continue;
4263 }
4264 }
4265 let mut new_vals = row.values.clone();
4266 for (pos, expr) in &targets {
4267 let v = eval::eval_expr(expr, row, &ctx)?;
4268 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
4269 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
4270 new_vals[*pos] = coerced;
4271 }
4272 for (pos, src) in &on_update_overrides {
4275 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4276 new_vals[*pos] = v;
4277 }
4278 planned.push((i, new_vals));
4279 }
4280 planned.sort_by_key(|(i, _)| *i);
4285 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4289 .iter()
4290 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4291 .collect();
4292 let self_fks = table.schema().foreign_keys.clone();
4293 let _ = table;
4298 if !self_fks.is_empty() {
4302 let new_rows: Vec<Vec<Value>> = planned
4303 .iter()
4304 .map(|(_pos, new_vals)| new_vals.clone())
4305 .collect();
4306 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4307 }
4308 {
4312 let new_rows: Vec<Vec<Value>> = planned
4313 .iter()
4314 .map(|(_pos, new_vals)| new_vals.clone())
4315 .collect();
4316 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4317 }
4318 let child_plan =
4322 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4323 for step in &child_plan {
4325 apply_fk_child_step(self.active_catalog_mut(), step)?;
4326 }
4327 let table = self
4329 .active_catalog_mut()
4330 .get_mut(&stmt.table)
4331 .ok_or_else(|| {
4332 EngineError::Storage(StorageError::TableNotFound {
4333 name: stmt.table.clone(),
4334 })
4335 })?;
4336 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4346 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4348 for (pos, new_vals) in &planned {
4349 let old_row = table.rows()[*pos].clone();
4350 let mut new_row = Row::new(new_vals.clone());
4351 let mut skip = false;
4352 for (fd, filter) in &before_update_triggers {
4353 if !filter.is_empty()
4358 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4359 {
4360 continue;
4361 }
4362 let (outcome, deferred) = triggers::fire_row_trigger(
4363 fd,
4364 Some(new_row.clone()),
4365 Some(&old_row),
4366 &stmt.table,
4367 &schema_cols,
4368 &[],
4369 trigger_session_cfg.as_deref(),
4370 false,
4371 )
4372 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4373 deferred_embedded.extend(deferred);
4374 match outcome {
4375 triggers::TriggerOutcome::Row(r) => new_row = r,
4376 triggers::TriggerOutcome::Skip => {
4377 skip = true;
4378 break;
4379 }
4380 }
4381 }
4382 if !skip {
4383 applied_after_before.push((*pos, new_row, old_row));
4384 }
4385 }
4386 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4389 applied_after_before
4390 .iter()
4391 .map(|(_pos, new_row, _old)| new_row.values.clone())
4392 .collect()
4393 } else {
4394 Vec::new()
4395 };
4396 let affected = applied_after_before.len();
4397 for (pos, new_row, old_row) in applied_after_before {
4401 table.update_row(pos, new_row.values.clone())?;
4402 for (fd, filter) in &after_update_triggers {
4403 if !filter.is_empty()
4404 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4405 {
4406 continue;
4407 }
4408 let (_outcome, deferred) = triggers::fire_row_trigger(
4409 fd,
4410 Some(new_row.clone()),
4411 Some(&old_row),
4412 &stmt.table,
4413 &schema_cols,
4414 &[],
4415 trigger_session_cfg.as_deref(),
4416 true,
4417 )
4418 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4419 deferred_embedded.extend(deferred);
4420 }
4421 }
4422 let _ = table;
4423 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4425 if !self.in_transaction() && affected > 0 {
4427 self.statistics
4428 .record_modifications(&stmt.table, affected as u64);
4429 }
4430 if let Some(items) = &stmt.returning {
4432 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4433 }
4434 Ok(QueryResult::CommandOk {
4435 affected,
4436 modified_catalog: !self.in_transaction(),
4437 })
4438 }
4439
4440 fn exec_merge_cancel(
4471 &mut self,
4472 stmt: &spg_sql::ast::MergeStatement,
4473 cancel: CancelToken<'_>,
4474 ) -> Result<QueryResult, EngineError> {
4475 let target_alias = stmt
4476 .target_alias
4477 .clone()
4478 .unwrap_or_else(|| stmt.target.clone());
4479 let source_alias = stmt
4480 .source_alias
4481 .clone()
4482 .unwrap_or_else(|| stmt.source.clone());
4483 let (target_cols, target_rows_snapshot) = {
4484 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4485 EngineError::Storage(StorageError::TableNotFound {
4486 name: stmt.target.clone(),
4487 })
4488 })?;
4489 (
4490 t.schema().columns.clone(),
4491 t.rows().iter().cloned().collect::<Vec<Row>>(),
4492 )
4493 };
4494 let (source_cols, source_rows) = {
4495 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4496 EngineError::Storage(StorageError::TableNotFound {
4497 name: stmt.source.clone(),
4498 })
4499 })?;
4500 (
4501 s.schema().columns.clone(),
4502 s.rows().iter().cloned().collect::<Vec<Row>>(),
4503 )
4504 };
4505 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4507 for col in &target_cols {
4508 combined_schema.push(ColumnSchema::new(
4509 alloc::format!("{target_alias}.{}", col.name),
4510 col.ty,
4511 col.nullable,
4512 ));
4513 }
4514 for col in &source_cols {
4515 combined_schema.push(ColumnSchema::new(
4516 alloc::format!("{source_alias}.{}", col.name),
4517 col.ty,
4518 col.nullable,
4519 ));
4520 }
4521 let combined_ctx = EvalContext::new(&combined_schema, None);
4522 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4526 for col in &target_cols {
4527 source_only_schema.push(ColumnSchema::new(
4528 alloc::format!("{target_alias}.{}", col.name),
4529 col.ty,
4530 col.nullable,
4531 ));
4532 }
4533 for col in &source_cols {
4534 source_only_schema.push(ColumnSchema::new(
4535 alloc::format!("{source_alias}.{}", col.name),
4536 col.ty,
4537 col.nullable,
4538 ));
4539 }
4540 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4541 let target_arity = target_cols.len();
4542 let source_arity = source_cols.len();
4543
4544 let mut delete_indices: Vec<usize> = Vec::new();
4547 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4548 let mut inserts: Vec<Vec<Value>> = Vec::new();
4549 let mut affected: usize = 0;
4550
4551 for (src_idx, src_row) in source_rows.iter().enumerate() {
4552 if src_idx.is_multiple_of(256) {
4553 cancel.check()?;
4554 }
4555 let mut matched_targets: Vec<usize> = Vec::new();
4557 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4558 let mut combined_vals = t_row.values.clone();
4559 combined_vals.extend(src_row.values.iter().cloned());
4560 let combined_row = Row::new(combined_vals);
4561 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4562 if matches!(cond, Value::Bool(true)) {
4563 matched_targets.push(t_idx);
4564 }
4565 }
4566 let is_matched = !matched_targets.is_empty();
4567 let fired_clause = stmt.clauses.iter().find(|c| {
4573 let kind_ok = match c.matched {
4574 spg_sql::ast::MergeMatched::Matched => is_matched,
4575 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4576 };
4577 if !kind_ok {
4578 return false;
4579 }
4580 let Some(cond_expr) = &c.condition else {
4581 return true;
4582 };
4583 let row = if is_matched {
4584 let t = &target_rows_snapshot[matched_targets[0]];
4585 let mut vals = t.values.clone();
4586 vals.extend(src_row.values.iter().cloned());
4587 Row::new(vals)
4588 } else {
4589 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4590 vals.extend(src_row.values.iter().cloned());
4591 Row::new(vals)
4592 };
4593 let ctx_ref = if is_matched {
4594 &combined_ctx
4595 } else {
4596 &source_only_ctx
4597 };
4598 matches!(
4599 eval::eval_expr(cond_expr, &row, ctx_ref),
4600 Ok(Value::Bool(true))
4601 )
4602 });
4603 let Some(clause) = fired_clause else { continue };
4604 match &clause.action {
4605 spg_sql::ast::MergeAction::DoNothing => {}
4606 spg_sql::ast::MergeAction::Delete => {
4607 for &t_idx in &matched_targets {
4608 if !delete_indices.contains(&t_idx) {
4609 delete_indices.push(t_idx);
4610 affected += 1;
4611 }
4612 }
4613 }
4614 spg_sql::ast::MergeAction::Update { assignments } => {
4615 let mut planned_sets: Vec<(usize, &Expr)> =
4617 Vec::with_capacity(assignments.len());
4618 for (col, expr) in assignments {
4619 let pos =
4620 target_cols
4621 .iter()
4622 .position(|c| c.name == *col)
4623 .ok_or_else(|| {
4624 EngineError::Eval(EvalError::ColumnNotFound {
4625 name: col.clone(),
4626 })
4627 })?;
4628 planned_sets.push((pos, expr));
4629 }
4630 for &t_idx in &matched_targets {
4631 let t_row = &target_rows_snapshot[t_idx];
4632 let mut new_values = t_row.values.clone();
4633 let mut combined_vals = t_row.values.clone();
4634 combined_vals.extend(src_row.values.iter().cloned());
4635 let combined_row = Row::new(combined_vals);
4636 for (pos, expr) in &planned_sets {
4637 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4638 let coerced = coerce_value(
4639 raw,
4640 target_cols[*pos].ty,
4641 &target_cols[*pos].name,
4642 *pos,
4643 )?;
4644 new_values[*pos] = coerced;
4645 }
4646 updates.push((t_idx, new_values));
4647 affected += 1;
4648 }
4649 }
4650 spg_sql::ast::MergeAction::Insert { columns, values } => {
4651 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4653 vals.extend(src_row.values.iter().cloned());
4654 let synth_row = Row::new(vals);
4655 let mut new_row_values: Vec<Value> =
4656 (0..target_arity).map(|_| Value::Null).collect();
4657 for (col, expr) in columns.iter().zip(values.iter()) {
4658 let pos =
4659 target_cols
4660 .iter()
4661 .position(|c| c.name == *col)
4662 .ok_or_else(|| {
4663 EngineError::Eval(EvalError::ColumnNotFound {
4664 name: col.clone(),
4665 })
4666 })?;
4667 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4668 let coerced =
4669 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4670 new_row_values[pos] = coerced;
4671 }
4672 inserts.push(new_row_values);
4673 affected += 1;
4674 }
4675 }
4676 }
4677 let _ = source_arity; let table = self
4681 .active_catalog_mut()
4682 .get_mut(&stmt.target)
4683 .ok_or_else(|| {
4684 EngineError::Storage(StorageError::TableNotFound {
4685 name: stmt.target.clone(),
4686 })
4687 })?;
4688 for (idx, new_vals) in &updates {
4692 table
4693 .update_row(*idx, new_vals.clone())
4694 .map_err(EngineError::Storage)?;
4695 }
4696 if !delete_indices.is_empty() {
4697 table.delete_rows(&delete_indices);
4698 }
4699 for vals in inserts {
4700 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4701 }
4702 Ok(QueryResult::CommandOk {
4703 affected,
4704 modified_catalog: affected > 0,
4705 })
4706 }
4707
4708 fn exec_delete_cancel(
4709 &mut self,
4710 stmt: &spg_sql::ast::DeleteStatement,
4711 cancel: CancelToken<'_>,
4712 ) -> Result<QueryResult, EngineError> {
4713 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4717 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4718 let trigger_session_cfg: Option<String> = self
4719 .session_params
4720 .get("default_text_search_config")
4721 .cloned();
4722 let mut cold_shadow_count: usize = 0;
4730 if let Some(w) = &stmt.where_ {
4731 let schema_cols = self
4732 .active_catalog()
4733 .get(&stmt.table)
4734 .ok_or_else(|| {
4735 EngineError::Storage(StorageError::TableNotFound {
4736 name: stmt.table.clone(),
4737 })
4738 })?
4739 .schema()
4740 .columns
4741 .clone();
4742 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4743 && let Some(idx_name) = self
4744 .active_catalog()
4745 .get(&stmt.table)
4746 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4747 {
4748 cold_shadow_count = self
4749 .active_catalog_mut()
4750 .shadow_cold_row(&stmt.table, &idx_name, &key)
4751 .unwrap_or(0);
4752 }
4753 }
4754
4755 let ts_cfg: Option<String> = self
4761 .session_param("default_text_search_config")
4762 .map(String::from);
4763 let table = self
4764 .active_catalog_mut()
4765 .get_mut(&stmt.table)
4766 .ok_or_else(|| {
4767 EngineError::Storage(StorageError::TableNotFound {
4768 name: stmt.table.clone(),
4769 })
4770 })?;
4771 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4772 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4773 .with_default_text_search_config(ts_cfg.as_deref());
4774 let mut positions: Vec<usize> = Vec::new();
4775 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4779 let seek_positions: Option<Vec<usize>> = stmt
4785 .where_
4786 .as_ref()
4787 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4788 let candidate_positions: Vec<usize> = match seek_positions {
4789 Some(mut list) => {
4790 list.sort_unstable();
4791 list
4792 }
4793 None => (0..table.row_count()).collect(),
4794 };
4795 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4796 if loop_n.is_multiple_of(256) {
4797 cancel.check()?;
4798 }
4799 let Some(row) = table.rows().get(i) else {
4800 continue;
4801 };
4802 let keep = if let Some(w) = &stmt.where_ {
4803 let cond = eval::eval_expr(w, row, &ctx)?;
4804 !matches!(cond, Value::Bool(true))
4805 } else {
4806 false
4807 };
4808 if !keep {
4809 positions.push(i);
4810 to_delete_rows.push(row.values.clone());
4811 }
4812 }
4813 let _ = table;
4820 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4828 if !before_delete_triggers.is_empty() {
4829 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4830 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4831 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4832 let old_row = Row::new(old_vals.clone());
4833 let mut cancel_this = false;
4834 for fd in &before_delete_triggers {
4835 let (outcome, deferred) = triggers::fire_row_trigger(
4836 fd,
4837 None,
4838 Some(&old_row),
4839 &stmt.table,
4840 &schema_cols,
4841 &[],
4842 trigger_session_cfg.as_deref(),
4843 false,
4844 )
4845 .map_err(|e| {
4846 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4847 })?;
4848 deferred_embedded.extend(deferred);
4849 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4850 cancel_this = true;
4851 break;
4852 }
4853 }
4854 if !cancel_this {
4855 filtered_positions.push(*pos);
4856 filtered_old_rows.push(old_vals.clone());
4857 }
4858 }
4859 positions = filtered_positions;
4860 to_delete_rows = filtered_old_rows;
4861 }
4862 let cascade_plan = plan_fk_parent_deletions(
4863 self.active_catalog(),
4864 &stmt.table,
4865 &positions,
4866 &to_delete_rows,
4867 )?;
4868 for step in &cascade_plan {
4875 apply_fk_child_step(self.active_catalog_mut(), step)?;
4876 }
4877 let table = self
4879 .active_catalog_mut()
4880 .get_mut(&stmt.table)
4881 .ok_or_else(|| {
4882 EngineError::Storage(StorageError::TableNotFound {
4883 name: stmt.table.clone(),
4884 })
4885 })?;
4886 let affected = table.delete_rows(&positions) + cold_shadow_count;
4887 let _ = table;
4888 if !after_delete_triggers.is_empty() {
4893 for old_vals in &to_delete_rows {
4894 let old_row = Row::new(old_vals.clone());
4895 for fd in &after_delete_triggers {
4896 let (_outcome, deferred) = triggers::fire_row_trigger(
4897 fd,
4898 None,
4899 Some(&old_row),
4900 &stmt.table,
4901 &schema_cols,
4902 &[],
4903 trigger_session_cfg.as_deref(),
4904 true,
4905 )
4906 .map_err(|e| {
4907 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4908 })?;
4909 deferred_embedded.extend(deferred);
4910 }
4911 }
4912 }
4913 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4915 if !self.in_transaction() && affected > 0 {
4917 self.statistics
4918 .record_modifications(&stmt.table, affected as u64);
4919 }
4920 if let Some(items) = &stmt.returning {
4926 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4927 }
4928 Ok(QueryResult::CommandOk {
4929 affected,
4930 modified_catalog: !self.in_transaction(),
4931 })
4932 }
4933
4934 #[allow(clippy::format_push_string)]
4944 fn exec_explain(
4945 &self,
4946 e: &spg_sql::ast::ExplainStatement,
4947 cancel: CancelToken<'_>,
4948 ) -> Result<QueryResult, EngineError> {
4949 let mut lines = Vec::<String>::new();
4950 explain_select(&e.inner, self, 0, &mut lines);
4951 if e.suggest {
4952 let suggestions = build_index_suggestions(&e.inner, self);
4961 for s in suggestions {
4962 lines.push(s);
4963 }
4964 } else if e.analyze {
4965 let started = self.clock.map(|f| f());
4982 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4983 let elapsed_micros = match (self.clock, started) {
4984 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4985 _ => None,
4986 };
4987 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4988 rows.len()
4989 } else {
4990 0
4991 };
4992 annotate_explain_lines(&mut lines, row_count, self);
4993 let mut total = alloc::format!("Total: rows={row_count}");
4994 if let Some(us) = elapsed_micros {
4995 total.push_str(&alloc::format!(" elapsed={us}us"));
4996 }
4997 lines.push(total);
4998 }
4999 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
5000 let rows: Vec<Row> = lines
5001 .into_iter()
5002 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
5003 .collect();
5004 Ok(QueryResult::Rows { columns, rows })
5005 }
5006
5007 fn exec_show_tables(&self) -> QueryResult {
5008 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
5009 let rows: Vec<Row> = self
5010 .active_catalog()
5011 .table_names()
5012 .into_iter()
5013 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
5014 .collect();
5015 QueryResult::Rows { columns, rows }
5016 }
5017
5018 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
5023 let t = self.active_catalog().get(name).ok_or_else(|| {
5024 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
5025 })?;
5026 let cols: Vec<String> = t
5027 .schema()
5028 .columns
5029 .iter()
5030 .map(|c| {
5031 let ty = render_data_type(c.ty);
5032 let nullable = if c.nullable { "" } else { " NOT NULL" };
5033 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
5034 })
5035 .collect();
5036 let mut body = cols.join(",\n");
5037 for uc in &t.schema().uniqueness_constraints {
5039 let col_names: Vec<String> = uc
5040 .columns
5041 .iter()
5042 .map(|&p| {
5043 t.schema().columns.get(p).map_or_else(
5044 || alloc::format!("col{p}"),
5045 |c| alloc::format!("`{}`", c.name),
5046 )
5047 })
5048 .collect();
5049 let kw = if uc.is_primary_key {
5050 "PRIMARY KEY"
5051 } else {
5052 "UNIQUE KEY"
5053 };
5054 body.push_str(",\n ");
5055 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
5056 }
5057 for fk in &t.schema().foreign_keys {
5059 let local: Vec<String> = fk
5060 .local_columns
5061 .iter()
5062 .map(|&p| {
5063 t.schema().columns.get(p).map_or_else(
5064 || alloc::format!("col{p}"),
5065 |c| alloc::format!("`{}`", c.name),
5066 )
5067 })
5068 .collect();
5069 let parent_cols: Vec<String> =
5070 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
5071 fk.parent_columns
5072 .iter()
5073 .map(|&p| {
5074 parent.schema().columns.get(p).map_or_else(
5075 || alloc::format!("col{p}"),
5076 |c| alloc::format!("`{}`", c.name),
5077 )
5078 })
5079 .collect()
5080 } else {
5081 fk.parent_columns
5082 .iter()
5083 .map(|p| alloc::format!("col{p}"))
5084 .collect()
5085 };
5086 body.push_str(",\n ");
5087 body.push_str(&alloc::format!(
5088 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
5089 local.join(", "),
5090 fk.parent_table,
5091 parent_cols.join(", ")
5092 ));
5093 }
5094 let ddl = alloc::format!(
5095 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
5096 name,
5097 body
5098 );
5099 let columns = alloc::vec![
5100 ColumnSchema::new("Table", DataType::Text, false),
5101 ColumnSchema::new("Create Table", DataType::Text, false),
5102 ];
5103 let rows = alloc::vec![Row::new(alloc::vec![
5104 Value::Text(name.into()),
5105 Value::Text(ddl),
5106 ])];
5107 Ok(QueryResult::Rows { columns, rows })
5108 }
5109
5110 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
5116 let t = self.active_catalog().get(name).ok_or_else(|| {
5117 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
5118 })?;
5119 let columns = alloc::vec![
5120 ColumnSchema::new("Table", DataType::Text, false),
5121 ColumnSchema::new("Non_unique", DataType::Int, false),
5122 ColumnSchema::new("Key_name", DataType::Text, false),
5123 ColumnSchema::new("Seq_in_index", DataType::Int, false),
5124 ColumnSchema::new("Column_name", DataType::Text, false),
5125 ColumnSchema::new("Null", DataType::Text, false),
5126 ColumnSchema::new("Index_type", DataType::Text, false),
5127 ];
5128 let mut rows: Vec<Row> = Vec::new();
5129 for idx in t.indices() {
5130 let col = t
5131 .schema()
5132 .columns
5133 .get(idx.column_position)
5134 .map_or("?".into(), |c| c.name.clone());
5135 let nullable = t
5136 .schema()
5137 .columns
5138 .get(idx.column_position)
5139 .map_or(true, |c| c.nullable);
5140 rows.push(Row::new(alloc::vec![
5141 Value::Text(name.into()),
5142 Value::Int(i32::from(!idx.is_unique)),
5143 Value::Text(idx.name.clone()),
5144 Value::Int(1),
5145 Value::Text(col),
5146 Value::Text(if nullable {
5147 "YES".into()
5148 } else {
5149 String::new()
5150 }),
5151 Value::Text("BTREE".into()),
5152 ]));
5153 }
5154 Ok(QueryResult::Rows { columns, rows })
5155 }
5156
5157 fn exec_show_status(&self) -> QueryResult {
5161 let columns = alloc::vec![
5162 ColumnSchema::new("Variable_name", DataType::Text, false),
5163 ColumnSchema::new("Value", DataType::Text, false),
5164 ];
5165 let pairs: &[(&str, &str)] = &[
5166 ("Uptime", "0"),
5167 ("Threads_connected", "1"),
5168 ("Threads_running", "1"),
5169 ("Questions", "0"),
5170 ("Slow_queries", "0"),
5171 ("Opened_tables", "0"),
5172 ("Innodb_buffer_pool_pages_total", "0"),
5173 ];
5174 let rows: Vec<Row> = pairs
5175 .iter()
5176 .map(|(k, v)| {
5177 Row::new(alloc::vec![
5178 Value::Text((*k).into()),
5179 Value::Text((*v).into())
5180 ])
5181 })
5182 .collect();
5183 QueryResult::Rows { columns, rows }
5184 }
5185
5186 fn exec_show_variables(&self) -> QueryResult {
5189 let columns = alloc::vec![
5190 ColumnSchema::new("Variable_name", DataType::Text, false),
5191 ColumnSchema::new("Value", DataType::Text, false),
5192 ];
5193 let mut rows: Vec<Row> = Vec::new();
5194 let canonical: &[(&str, &str)] = &[
5195 ("version", "8.0.35-spg"),
5196 ("version_comment", "SPG dual-stack engine"),
5197 ("character_set_server", "utf8mb4"),
5198 ("collation_server", "utf8mb4_0900_ai_ci"),
5199 ("max_allowed_packet", "67108864"),
5200 ("autocommit", "ON"),
5201 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
5202 ("time_zone", "SYSTEM"),
5203 ("transaction_isolation", "REPEATABLE-READ"),
5204 ];
5205 for &(k, v) in canonical {
5206 rows.push(Row::new(alloc::vec![
5207 Value::Text(k.into()),
5208 Value::Text(v.into()),
5209 ]));
5210 }
5211 for (k, v) in &self.session_params {
5213 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
5214 rows.push(Row::new(alloc::vec![
5215 Value::Text(k.clone()),
5216 Value::Text(v.clone()),
5217 ]));
5218 }
5219 }
5220 QueryResult::Rows { columns, rows }
5221 }
5222
5223 fn exec_show_processlist(&self) -> QueryResult {
5228 let columns = alloc::vec![
5229 ColumnSchema::new("Id", DataType::Int, false),
5230 ColumnSchema::new("User", DataType::Text, false),
5231 ColumnSchema::new("Host", DataType::Text, false),
5232 ColumnSchema::new("db", DataType::Text, true),
5233 ColumnSchema::new("Command", DataType::Text, false),
5234 ColumnSchema::new("Time", DataType::Int, false),
5235 ColumnSchema::new("State", DataType::Text, true),
5236 ColumnSchema::new("Info", DataType::Text, true),
5237 ];
5238 let rows = alloc::vec![Row::new(alloc::vec![
5239 Value::Int(1),
5240 Value::Text("postgres".into()),
5241 Value::Text("localhost".into()),
5242 Value::Text("postgres".into()),
5243 Value::Text("Query".into()),
5244 Value::Int(0),
5245 Value::Text("executing".into()),
5246 Value::Text("SHOW PROCESSLIST".into()),
5247 ])];
5248 QueryResult::Rows { columns, rows }
5249 }
5250
5251 fn exec_show_databases(&self) -> QueryResult {
5258 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
5259 let names = [
5260 "information_schema",
5261 "mysql",
5262 "performance_schema",
5263 "sys",
5264 "postgres",
5265 ];
5266 let rows: Vec<Row> = names
5267 .iter()
5268 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
5269 .collect();
5270 QueryResult::Rows { columns, rows }
5271 }
5272
5273 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
5276 let table =
5277 self.active_catalog()
5278 .get(table_name)
5279 .ok_or_else(|| StorageError::TableNotFound {
5280 name: table_name.into(),
5281 })?;
5282 let columns = alloc::vec![
5283 ColumnSchema::new("name", DataType::Text, false),
5284 ColumnSchema::new("type", DataType::Text, false),
5285 ColumnSchema::new("nullable", DataType::Bool, false),
5286 ];
5287 let rows: Vec<Row> = table
5288 .schema()
5289 .columns
5290 .iter()
5291 .map(|c| {
5292 Row::new(alloc::vec![
5293 Value::Text(c.name.clone()),
5294 Value::Text(alloc::format!("{}", c.ty)),
5295 Value::Bool(c.nullable),
5296 ])
5297 })
5298 .collect();
5299 Ok(QueryResult::Rows { columns, rows })
5300 }
5301
5302 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5303 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5304 if self.tx_catalogs.contains_key(&tx_id) {
5305 return Err(EngineError::TransactionAlreadyOpen);
5306 }
5307 self.tx_catalogs.insert(
5308 tx_id,
5309 TxState {
5310 catalog: self.catalog.clone(),
5311 savepoints: Vec::new(),
5312 },
5313 );
5314 Ok(QueryResult::CommandOk {
5315 affected: 0,
5316 modified_catalog: false,
5317 })
5318 }
5319
5320 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5321 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5322 let state = self
5323 .tx_catalogs
5324 .remove(&tx_id)
5325 .ok_or(EngineError::NoActiveTransaction)?;
5326 self.catalog = state.catalog;
5327 Ok(QueryResult::CommandOk {
5331 affected: 0,
5332 modified_catalog: true,
5333 })
5334 }
5335
5336 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5337 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5338 if self.tx_catalogs.remove(&tx_id).is_none() {
5339 return Err(EngineError::NoActiveTransaction);
5340 }
5341 Ok(QueryResult::CommandOk {
5343 affected: 0,
5344 modified_catalog: false,
5345 })
5346 }
5347
5348 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5349 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5350 let state = self
5351 .tx_catalogs
5352 .get_mut(&tx_id)
5353 .ok_or(EngineError::NoActiveTransaction)?;
5354 state.savepoints.retain(|(n, _)| n != &name);
5358 let snapshot = state.catalog.clone();
5359 state.savepoints.push((name, snapshot));
5360 Ok(QueryResult::CommandOk {
5361 affected: 0,
5362 modified_catalog: false,
5363 })
5364 }
5365
5366 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5367 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5368 let state = self
5369 .tx_catalogs
5370 .get_mut(&tx_id)
5371 .ok_or(EngineError::NoActiveTransaction)?;
5372 let pos = state
5373 .savepoints
5374 .iter()
5375 .rposition(|(n, _)| n == name)
5376 .ok_or_else(|| {
5377 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5378 })?;
5379 let snapshot = state.savepoints[pos].1.clone();
5383 state.savepoints.truncate(pos + 1);
5384 state.catalog = snapshot;
5385 Ok(QueryResult::CommandOk {
5386 affected: 0,
5387 modified_catalog: false,
5388 })
5389 }
5390
5391 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5392 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5393 let state = self
5394 .tx_catalogs
5395 .get_mut(&tx_id)
5396 .ok_or(EngineError::NoActiveTransaction)?;
5397 let pos = state
5398 .savepoints
5399 .iter()
5400 .rposition(|(n, _)| n == name)
5401 .ok_or_else(|| {
5402 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5403 })?;
5404 state.savepoints.truncate(pos);
5407 Ok(QueryResult::CommandOk {
5408 affected: 0,
5409 modified_catalog: false,
5410 })
5411 }
5412
5413 fn exec_alter_table(
5424 &mut self,
5425 s: spg_sql::ast::AlterTableStatement,
5426 ) -> Result<QueryResult, EngineError> {
5427 let table_name = s.name.clone();
5432 for target in s.targets {
5433 self.exec_alter_table_subaction(&table_name, target)?;
5434 }
5435 Ok(QueryResult::CommandOk {
5436 affected: 0,
5437 modified_catalog: !self.in_transaction(),
5438 })
5439 }
5440
5441 fn exec_alter_table_subaction(
5442 &mut self,
5443 table_name_outer: &str,
5444 target: spg_sql::ast::AlterTableTarget,
5445 ) -> Result<(), EngineError> {
5446 struct S<'a> {
5449 name: &'a str,
5450 }
5451 let s = S {
5452 name: table_name_outer,
5453 };
5454 match target {
5455 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5456 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5457 EngineError::Storage(StorageError::TableNotFound {
5458 name: s.name.into(),
5459 })
5460 })?;
5461 table.schema_mut().hot_tier_bytes = Some(n);
5462 }
5463 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5464 let cols_snapshot = self
5469 .active_catalog()
5470 .get(s.name)
5471 .ok_or_else(|| {
5472 EngineError::Storage(StorageError::TableNotFound {
5473 name: s.name.into(),
5474 })
5475 })?
5476 .schema()
5477 .columns
5478 .clone();
5479 let storage_fk =
5480 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5481 let existing_rows: Vec<Vec<Value>> = self
5484 .active_catalog()
5485 .get(s.name)
5486 .expect("checked above")
5487 .rows()
5488 .iter()
5489 .map(|r| r.values.clone())
5490 .collect();
5491 enforce_fk_inserts(
5492 self.active_catalog(),
5493 s.name,
5494 core::slice::from_ref(&storage_fk),
5495 &existing_rows,
5496 )?;
5497 let table = self
5499 .active_catalog_mut()
5500 .get_mut(s.name)
5501 .expect("checked above");
5502 if let Some(name) = &storage_fk.name
5503 && table
5504 .schema()
5505 .foreign_keys
5506 .iter()
5507 .any(|f| f.name.as_ref() == Some(name))
5508 {
5509 return Err(EngineError::Unsupported(alloc::format!(
5510 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5511 )));
5512 }
5513 table.schema_mut().foreign_keys.push(storage_fk);
5514 }
5515 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5516 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5517 EngineError::Storage(StorageError::TableNotFound {
5518 name: s.name.into(),
5519 })
5520 })?;
5521 let fks = &mut table.schema_mut().foreign_keys;
5522 let before = fks.len();
5523 fks.retain(|f| f.name.as_ref() != Some(&name));
5524 if fks.len() == before && !if_exists {
5525 return Err(EngineError::Unsupported(alloc::format!(
5526 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5527 s.name
5528 )));
5529 }
5530 }
5532 spg_sql::ast::AlterTableTarget::AddColumn {
5533 column,
5534 if_not_exists,
5535 } => {
5536 let clock = self.clock;
5541 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5542 EngineError::Storage(StorageError::TableNotFound {
5543 name: s.name.into(),
5544 })
5545 })?;
5546 if table
5547 .schema()
5548 .columns
5549 .iter()
5550 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5551 {
5552 if if_not_exists {
5553 return Ok(());
5554 }
5555 return Err(EngineError::Unsupported(alloc::format!(
5556 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5557 column.name,
5558 s.name
5559 )));
5560 }
5561 let col_name = column.name.clone();
5562 let nullable = column.nullable;
5563 let has_default = column.default.is_some() || column.auto_increment;
5564 let col_schema = column_def_to_schema(column)?;
5565 let row_count = table.row_count();
5566 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5573 resolve_column_default_free(&col_schema, clock)?
5574 } else if nullable || row_count == 0 {
5575 Value::Null
5576 } else {
5577 return Err(EngineError::Unsupported(alloc::format!(
5578 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5579 when the table has existing rows"
5580 )));
5581 };
5582 table.add_column(col_schema, fill_value);
5583 }
5584 spg_sql::ast::AlterTableTarget::AlterColumnType {
5585 column,
5586 new_type,
5587 using,
5588 } => {
5589 let new_data_type = column_type_to_data_type(new_type);
5595 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5596 EngineError::Storage(StorageError::TableNotFound {
5597 name: s.name.into(),
5598 })
5599 })?;
5600 let col_pos = table
5601 .schema()
5602 .columns
5603 .iter()
5604 .position(|c| c.name.eq_ignore_ascii_case(&column))
5605 .ok_or_else(|| {
5606 EngineError::Unsupported(alloc::format!(
5607 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5608 s.name
5609 ))
5610 })?;
5611 let schema_cols = table.schema().columns.clone();
5612 let ctx = eval::EvalContext::new(&schema_cols, None);
5613 let mut new_values: alloc::vec::Vec<Value> =
5614 alloc::vec::Vec::with_capacity(table.row_count());
5615 for row in table.rows().iter() {
5616 let raw = match &using {
5617 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5618 EngineError::Unsupported(alloc::format!(
5619 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5620 ))
5621 })?,
5622 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5623 };
5624 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5625 new_values.push(coerced);
5626 }
5627 table.schema_mut().columns[col_pos].ty = new_data_type;
5628 for (i, v) in new_values.into_iter().enumerate() {
5629 let mut row_values = table
5630 .rows()
5631 .get(i)
5632 .expect("bounds-checked above")
5633 .values
5634 .clone();
5635 row_values[col_pos] = v;
5636 table.update_row(i, row_values)?;
5637 }
5638 }
5639 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5640 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5646 EngineError::Storage(StorageError::TableNotFound {
5647 name: s.name.into(),
5648 })
5649 })?;
5650 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5651 let nnd = matches!(
5656 tc,
5657 spg_sql::ast::TableConstraint::Unique {
5658 nulls_not_distinct: true,
5659 ..
5660 }
5661 );
5662 match tc {
5663 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5664 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5665 let positions: Vec<usize> = columns
5666 .iter()
5667 .map(|c| {
5668 table
5669 .schema()
5670 .columns
5671 .iter()
5672 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5673 .ok_or_else(|| {
5674 EngineError::Unsupported(alloc::format!(
5675 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5676 s.name
5677 ))
5678 })
5679 })
5680 .collect::<Result<Vec<_>, _>>()?;
5681 let already = table
5685 .schema()
5686 .uniqueness_constraints
5687 .iter()
5688 .any(|u| u.columns == positions);
5689 if !already {
5690 table.schema_mut().uniqueness_constraints.push(
5691 spg_storage::UniquenessConstraint {
5692 is_primary_key: is_pk,
5693 columns: positions.clone(),
5694 nulls_not_distinct: nnd,
5695 },
5696 );
5697 if is_pk {
5699 for p in &positions {
5700 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5701 c.nullable = false;
5702 }
5703 }
5704 }
5705 let leading = &columns[0];
5708 let already_idx = table.indices().iter().any(|idx| {
5709 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5710 && table.schema().columns[idx.column_position].name == *leading
5711 });
5712 if !already_idx {
5713 let suffix = if is_pk { "pkey" } else { "key" };
5714 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5715 let _ = table.add_index(idx_name, leading);
5716 }
5717 }
5718 }
5719 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5720 table.schema_mut().checks.push(alloc::format!("{expr}"));
5721 }
5722 spg_sql::ast::TableConstraint::Index { name, columns } => {
5723 let leading = &columns[0];
5729 let already_idx = table.indices().iter().any(|idx| {
5730 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5731 && table.schema().columns[idx.column_position].name == *leading
5732 });
5733 if !already_idx {
5734 let idx_name = name
5735 .clone()
5736 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5737 let _ = table.add_index(idx_name, leading);
5738 }
5739 }
5740 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5741 for (k, col) in columns.iter().enumerate() {
5749 let already_idx = table.indices().iter().any(|idx| {
5750 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5751 && table.schema().columns[idx.column_position].name == *col
5752 });
5753 if already_idx {
5754 continue;
5755 }
5756 let idx_name = match (&name, columns.len(), k) {
5757 (Some(n), 1, _) => n.clone(),
5758 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5759 (None, _, _) => {
5760 alloc::format!("{}_{col}_ftidx", s.name)
5761 }
5762 };
5763 let _ = table.add_gin_fulltext_index(idx_name, col);
5764 }
5765 }
5766 }
5767 }
5768 spg_sql::ast::AlterTableTarget::DropColumn {
5769 column,
5770 if_exists,
5771 cascade,
5772 } => {
5773 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5780 EngineError::Storage(StorageError::TableNotFound {
5781 name: s.name.into(),
5782 })
5783 })?;
5784 let col_pos = match table
5785 .schema()
5786 .columns
5787 .iter()
5788 .position(|c| c.name.eq_ignore_ascii_case(&column))
5789 {
5790 Some(p) => p,
5791 None => {
5792 if if_exists {
5793 return Ok(());
5794 }
5795 return Err(EngineError::Unsupported(alloc::format!(
5796 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5797 s.name
5798 )));
5799 }
5800 };
5801 let dependent_fks: Vec<usize> = table
5804 .schema()
5805 .foreign_keys
5806 .iter()
5807 .enumerate()
5808 .filter_map(|(i, fk)| {
5809 if fk.local_columns.contains(&col_pos) {
5810 Some(i)
5811 } else {
5812 None
5813 }
5814 })
5815 .collect();
5816 if !dependent_fks.is_empty() && !cascade {
5817 return Err(EngineError::Unsupported(alloc::format!(
5818 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5819 use DROP COLUMN ... CASCADE to remove them"
5820 )));
5821 }
5822 if cascade {
5824 let mut sorted = dependent_fks.clone();
5826 sorted.sort();
5827 sorted.reverse();
5828 let fks = &mut table.schema_mut().foreign_keys;
5829 for i in sorted {
5830 fks.remove(i);
5831 }
5832 }
5833 table.drop_column(col_pos);
5836 }
5837 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5838 let table_name = s.name.to_string();
5846 let trigs = self.active_catalog_mut().triggers_mut();
5847 let mut touched = false;
5848 for t in trigs.iter_mut() {
5849 if !t.table.eq_ignore_ascii_case(&table_name) {
5850 continue;
5851 }
5852 match &which {
5853 spg_sql::ast::TriggerSelector::All => {
5854 t.enabled = enabled;
5855 touched = true;
5856 }
5857 spg_sql::ast::TriggerSelector::Named(name) => {
5858 if t.name.eq_ignore_ascii_case(name) {
5859 t.enabled = enabled;
5860 touched = true;
5861 }
5862 }
5863 }
5864 }
5865 if !touched {
5871 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5872 return Err(EngineError::Unsupported(alloc::format!(
5873 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5874 if enabled { "ENABLE" } else { "DISABLE" },
5875 )));
5876 }
5877 }
5878 }
5879 spg_sql::ast::AlterTableTarget::SetColumnAutoIncrement { column, seq_name } => {
5880 if let Some(seq) = seq_name {
5886 let _ = self.exec_create_sequence(spg_sql::ast::CreateSequenceStatement {
5887 name: seq,
5888 if_not_exists: true,
5889 temporary: false,
5890 data_type: None,
5891 options: spg_sql::ast::SequenceOptions::default(),
5892 })?;
5893 }
5894 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5902 EngineError::Storage(StorageError::TableNotFound {
5903 name: s.name.into(),
5904 })
5905 })?;
5906 let pos = table
5907 .schema()
5908 .columns
5909 .iter()
5910 .position(|c| c.name.eq_ignore_ascii_case(&column))
5911 .ok_or_else(|| {
5912 EngineError::Unsupported(alloc::format!(
5913 "ALTER COLUMN {column:?}: no such column on {:?}",
5914 s.name
5915 ))
5916 })?;
5917 let col = &table.schema().columns[pos];
5918 if !matches!(
5919 col.ty,
5920 spg_storage::DataType::SmallInt
5921 | spg_storage::DataType::Int
5922 | spg_storage::DataType::BigInt
5923 ) {
5924 return Err(EngineError::Unsupported(alloc::format!(
5925 "auto-increment applies to integer columns only ({column:?} is {:?})",
5926 col.ty
5927 )));
5928 }
5929 table.schema_mut().columns[pos].auto_increment = true;
5930 }
5931 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5932 let old = s.name.to_string();
5939 self.active_catalog_mut()
5940 .rename_table(&old, &new)
5941 .map_err(EngineError::Storage)?;
5942 }
5943 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5944 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5958 EngineError::Storage(StorageError::TableNotFound {
5959 name: s.name.into(),
5960 })
5961 })?;
5962 let col_pos = table
5963 .schema()
5964 .columns
5965 .iter()
5966 .position(|c| c.name.eq_ignore_ascii_case(&old))
5967 .ok_or_else(|| {
5968 EngineError::Unsupported(alloc::format!(
5969 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5970 s.name
5971 ))
5972 })?;
5973 if table
5975 .schema()
5976 .columns
5977 .iter()
5978 .enumerate()
5979 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5980 {
5981 return Err(EngineError::Unsupported(alloc::format!(
5982 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5983 s.name
5984 )));
5985 }
5986 if old.eq_ignore_ascii_case(&new) {
5990 return Ok(());
5991 }
5992 table.rename_column(col_pos, &new);
5993 let n_cols = table.schema().columns.len();
5999 for i in 0..n_cols {
6000 let rt = table.schema().columns[i].runtime_default.clone();
6001 if let Some(src) = rt {
6002 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
6003 table.schema_mut().columns[i].runtime_default = Some(rewritten);
6004 }
6005 }
6006 let checks = table.schema().checks.clone();
6008 let mut new_checks = Vec::with_capacity(checks.len());
6009 for chk in checks {
6010 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
6011 }
6012 table.schema_mut().checks = new_checks;
6013 let n_idx = table.indices().len();
6015 for i in 0..n_idx {
6016 let pred = table.indices()[i].partial_predicate.clone();
6017 if let Some(src) = pred {
6018 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
6019 table.set_partial_predicate(i, Some(rewritten));
6023 }
6024 }
6025 let table_name = s.name.to_string();
6028 for trig in self.active_catalog_mut().triggers_mut() {
6029 if !trig.table.eq_ignore_ascii_case(&table_name) {
6030 continue;
6031 }
6032 for c in &mut trig.update_columns {
6033 if c.eq_ignore_ascii_case(&old) {
6034 *c = new.clone();
6035 }
6036 }
6037 }
6038 }
6039 }
6040 Ok(())
6041 }
6042
6043 fn exec_alter_index(
6044 &mut self,
6045 stmt: spg_sql::ast::AlterIndexStatement,
6046 ) -> Result<QueryResult, EngineError> {
6047 let spg_sql::ast::AlterIndexStatement {
6051 name: idx_name,
6052 target,
6053 } = stmt;
6054 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
6058 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
6059 return match renamed {
6060 Ok(()) => Ok(QueryResult::CommandOk {
6061 affected: 0,
6062 modified_catalog: !self.in_transaction(),
6063 }),
6064 Err(StorageError::IndexNotFound { .. }) if if_exists => {
6065 Ok(QueryResult::CommandOk {
6066 affected: 0,
6067 modified_catalog: false,
6068 })
6069 }
6070 Err(e) => Err(EngineError::Storage(e)),
6071 };
6072 }
6073 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
6074 unreachable!("Rename branch returned above");
6075 };
6076 let target = encoding.map(|e| match e {
6077 SqlVecEncoding::F32 => VecEncoding::F32,
6078 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
6079 SqlVecEncoding::F16 => VecEncoding::F16,
6080 });
6081 let table_name = {
6086 let cat = self.active_catalog();
6087 let mut found: Option<String> = None;
6088 for tname in cat.table_names() {
6089 if let Some(t) = cat.get(&tname)
6090 && t.indices().iter().any(|i| i.name == idx_name)
6091 {
6092 found = Some(tname);
6093 break;
6094 }
6095 }
6096 found.ok_or_else(|| {
6097 EngineError::Storage(StorageError::IndexNotFound {
6098 name: idx_name.clone(),
6099 })
6100 })?
6101 };
6102 let table = self
6103 .active_catalog_mut()
6104 .get_mut(&table_name)
6105 .expect("table found above");
6106 table.rebuild_nsw_index(&idx_name, target)?;
6107 self.plan_cache.evict_referencing(&table_name);
6110 Ok(QueryResult::CommandOk {
6111 affected: 0,
6112 modified_catalog: !self.in_transaction(),
6113 })
6114 }
6115
6116 fn exec_create_index(
6117 &mut self,
6118 stmt: CreateIndexStatement,
6119 ) -> Result<QueryResult, EngineError> {
6120 let table = self
6121 .active_catalog_mut()
6122 .get_mut(&stmt.table)
6123 .ok_or_else(|| {
6124 EngineError::Storage(StorageError::TableNotFound {
6125 name: stmt.table.clone(),
6126 })
6127 })?;
6128 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
6130 return Ok(QueryResult::CommandOk {
6131 affected: 0,
6132 modified_catalog: false,
6133 });
6134 }
6135 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
6142 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
6146 Vec::new()
6147 } else {
6148 let schema = table.schema();
6149 stmt.included_columns
6150 .iter()
6151 .map(|c| {
6152 schema.column_position(c).ok_or_else(|| {
6153 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
6154 })
6155 })
6156 .collect::<Result<Vec<_>, _>>()?
6157 };
6158 match stmt.method {
6159 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
6160 IndexMethod::Hnsw => {
6161 if !included_positions.is_empty() {
6162 return Err(EngineError::Unsupported(
6163 "INCLUDE columns are not supported on HNSW indexes".into(),
6164 ));
6165 }
6166 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
6167 }
6168 IndexMethod::Brin => {
6170 if !included_positions.is_empty() {
6171 return Err(EngineError::Unsupported(
6172 "INCLUDE columns are not supported on BRIN indexes".into(),
6173 ));
6174 }
6175 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
6176 }
6177 IndexMethod::Gin => {
6185 if !included_positions.is_empty() {
6186 return Err(EngineError::Unsupported(
6187 "INCLUDE columns are not supported on GIN indexes".into(),
6188 ));
6189 }
6190 let col_pos = table
6191 .schema()
6192 .column_position(&stmt.column)
6193 .ok_or_else(|| {
6194 EngineError::Storage(StorageError::ColumnNotFound {
6195 column: stmt.column.clone(),
6196 })
6197 })?;
6198 let col_ty = table.schema().columns[col_pos].ty;
6199 let is_trgm = stmt
6205 .opclass
6206 .as_deref()
6207 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
6208 if is_trgm
6209 && matches!(
6210 col_ty,
6211 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
6212 )
6213 {
6214 table
6215 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
6216 .map_err(EngineError::Storage)?;
6217 } else if col_ty == spg_storage::DataType::TsVector {
6218 table
6219 .add_gin_index(stmt.name.clone(), &stmt.column)
6220 .map_err(EngineError::Storage)?;
6221 } else {
6222 table.add_index(stmt.name.clone(), &stmt.column)?;
6228 }
6229 }
6230 }
6231 if !included_positions.is_empty()
6232 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
6233 {
6234 idx.included_columns = included_positions;
6235 }
6236 if let Some(pred_expr) = &stmt.partial_predicate {
6244 let canonical = pred_expr.to_string();
6245 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6257 idx.partial_predicate = Some(canonical);
6258 }
6259 }
6260 if let Some(key_expr) = &stmt.expression {
6268 if matches!(
6269 stmt.method,
6270 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
6271 ) {
6272 return Err(EngineError::Unsupported(
6273 "Expression keys are not supported on HNSW or BRIN indexes".into(),
6274 ));
6275 }
6276 let canonical = key_expr.to_string();
6277 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6278 idx.expression = Some(canonical);
6279 }
6280 }
6281 if stmt.is_unique {
6290 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
6291 for col_name in &stmt.extra_columns {
6292 let pos = table
6293 .schema()
6294 .columns
6295 .iter()
6296 .position(|c| c.name.eq_ignore_ascii_case(col_name))
6297 .ok_or_else(|| {
6298 EngineError::Unsupported(alloc::format!(
6299 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
6300 stmt.name,
6301 stmt.table
6302 ))
6303 })?;
6304 extra_positions.push(pos);
6305 }
6306 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6307 idx.is_unique = true;
6308 idx.extra_column_positions = extra_positions;
6309 }
6310 let snapshot_indices = table.indices().to_vec();
6315 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
6316 table.rows().iter().cloned().collect();
6317 let snapshot_schema = table.schema().clone();
6318 let idx_ref = snapshot_indices
6319 .iter()
6320 .find(|i| i.name == stmt.name)
6321 .expect("just-added index");
6322 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
6323 }
6324 self.plan_cache.evict_referencing(&table_name);
6327 Ok(QueryResult::CommandOk {
6328 affected: 0,
6329 modified_catalog: !self.in_transaction(),
6330 })
6331 }
6332
6333 fn reconcile_table_if_not_exists(
6342 &mut self,
6343 stmt: CreateTableStatement,
6344 ) -> Result<QueryResult, EngineError> {
6345 let table_name = stmt.name.clone();
6346 let clock = self.clock;
6347 let existing_col_names: alloc::collections::BTreeSet<String> = self
6348 .active_catalog()
6349 .get(&table_name)
6350 .expect("checked above")
6351 .schema()
6352 .columns
6353 .iter()
6354 .map(|c| c.name.to_ascii_lowercase())
6355 .collect();
6356 let row_count = self
6357 .active_catalog()
6358 .get(&table_name)
6359 .expect("checked above")
6360 .row_count();
6361 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6363 .columns
6364 .iter()
6365 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6366 .cloned()
6367 .collect();
6368 for col_def in new_columns {
6369 let col_name = col_def.name.clone();
6370 let nullable = col_def.nullable;
6371 let has_default = col_def.default.is_some() || col_def.auto_increment;
6372 let col_schema = column_def_to_schema(col_def)?;
6373 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6374 resolve_column_default_free(&col_schema, clock)?
6375 } else if nullable || row_count == 0 {
6376 Value::Null
6377 } else {
6378 return Err(EngineError::Unsupported(alloc::format!(
6379 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6380 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6381 )));
6382 };
6383 let table = self
6384 .active_catalog_mut()
6385 .get_mut(&table_name)
6386 .expect("checked above");
6387 table.add_column(col_schema, fill_value);
6388 }
6389 let table_cols_now = self
6393 .active_catalog()
6394 .get(&table_name)
6395 .expect("checked above")
6396 .schema()
6397 .columns
6398 .clone();
6399 for fk in stmt.foreign_keys {
6400 let all_resolved = fk.columns.iter().all(|c| {
6404 table_cols_now
6405 .iter()
6406 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6407 });
6408 if !all_resolved {
6409 continue;
6410 }
6411 let already_present = {
6412 let table = self
6413 .active_catalog()
6414 .get(&table_name)
6415 .expect("checked above");
6416 table.schema().foreign_keys.iter().any(|f| {
6417 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6418 && f.local_columns.len() == fk.columns.len()
6419 })
6420 };
6421 if already_present {
6422 continue;
6423 }
6424 let storage_fk =
6425 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6426 let table = self
6427 .active_catalog_mut()
6428 .get_mut(&table_name)
6429 .expect("checked above");
6430 table.schema_mut().foreign_keys.push(storage_fk);
6431 }
6432 Ok(QueryResult::CommandOk {
6433 affected: 0,
6434 modified_catalog: !self.in_transaction(),
6435 })
6436 }
6437
6438 fn exec_drop_table(
6440 &mut self,
6441 names: Vec<String>,
6442 if_exists: bool,
6443 ) -> Result<QueryResult, EngineError> {
6444 for name in names {
6445 let dropped = self.active_catalog_mut().drop_table(&name);
6446 if !dropped && !if_exists {
6447 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6448 }
6449 }
6450 Ok(QueryResult::CommandOk {
6451 affected: 0,
6452 modified_catalog: !self.in_transaction(),
6453 })
6454 }
6455
6456 fn exec_drop_index(
6458 &mut self,
6459 name: String,
6460 if_exists: bool,
6461 ) -> Result<QueryResult, EngineError> {
6462 let dropped = self.active_catalog_mut().drop_named_index(&name);
6463 if !dropped && !if_exists {
6464 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6465 }
6466 Ok(QueryResult::CommandOk {
6467 affected: 0,
6468 modified_catalog: !self.in_transaction(),
6469 })
6470 }
6471
6472 fn exec_create_table(
6473 &mut self,
6474 stmt: CreateTableStatement,
6475 ) -> Result<QueryResult, EngineError> {
6476 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6477 return Ok(QueryResult::CommandOk {
6496 affected: 0,
6497 modified_catalog: false,
6498 });
6499 }
6500 let table_name = stmt.name.clone();
6501 let inline_pk_columns: Vec<String> = stmt
6505 .columns
6506 .iter()
6507 .filter(|c| c.is_primary_key)
6508 .map(|c| c.name.clone())
6509 .collect();
6510 let cols = stmt
6516 .columns
6517 .into_iter()
6518 .map(column_def_to_schema)
6519 .collect::<Result<Vec<_>, _>>()?;
6520 let mut cols = cols;
6529 for col in cols.iter_mut() {
6530 let Some(name) = col.user_enum_type.take() else {
6531 continue;
6532 };
6533 let cat = self.active_catalog();
6534 if cat.enum_types().contains_key(&name) {
6535 col.user_enum_type = Some(name);
6536 continue;
6537 }
6538 if let Some(dom) = cat.domain_types().get(&name) {
6539 col.ty = dom.base_type;
6540 col.user_domain_type = Some(name);
6541 if !dom.nullable {
6542 col.nullable = false;
6543 }
6544 continue;
6545 }
6546 return Err(EngineError::Unsupported(alloc::format!(
6547 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6548 col.name,
6549 name
6550 )));
6551 }
6552 for tc in &stmt.table_constraints {
6553 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6554 for col_name in columns {
6555 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6556 col.nullable = false;
6557 }
6558 }
6559 }
6560 }
6561 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6568 Vec::with_capacity(stmt.foreign_keys.len());
6569 for fk in stmt.foreign_keys {
6570 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6577 if !self.foreign_key_checks
6578 && needs_parent
6579 && self.active_catalog().get(&fk.parent_table).is_none()
6580 {
6581 self.pending_foreign_keys.push((table_name.clone(), fk));
6582 continue;
6583 }
6584 fks.push(resolve_foreign_key(
6585 &table_name,
6586 &cols,
6587 fk,
6588 self.active_catalog(),
6589 )?);
6590 }
6591 let mut schema = TableSchema::new(table_name.clone(), cols);
6592 schema.foreign_keys = fks;
6593 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6597 let mut check_exprs: Vec<String> = Vec::new();
6598 for tc in &stmt.table_constraints {
6599 let (is_pk, names, nnd) = match tc {
6600 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6601 (true, columns.clone(), false)
6602 }
6603 spg_sql::ast::TableConstraint::Unique {
6604 columns,
6605 nulls_not_distinct,
6606 ..
6607 } => (false, columns.clone(), *nulls_not_distinct),
6608 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6609 check_exprs.push(alloc::format!("{expr}"));
6612 continue;
6613 }
6614 spg_sql::ast::TableConstraint::Index { .. } => continue,
6620 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6624 };
6625 let mut positions = Vec::with_capacity(names.len());
6626 for n in &names {
6627 let pos = schema
6628 .columns
6629 .iter()
6630 .position(|c| c.name == *n)
6631 .ok_or_else(|| {
6632 EngineError::Unsupported(alloc::format!(
6633 "table constraint references unknown column {n:?}"
6634 ))
6635 })?;
6636 positions.push(pos);
6637 }
6638 uc_storage.push(spg_storage::UniquenessConstraint {
6639 is_primary_key: is_pk,
6640 columns: positions,
6641 nulls_not_distinct: nnd,
6642 });
6643 }
6644 if !inline_pk_columns.is_empty() {
6651 let mut positions = Vec::with_capacity(inline_pk_columns.len());
6652 for n in &inline_pk_columns {
6653 if let Some(pos) = schema.columns.iter().position(|c| c.name == *n) {
6654 positions.push(pos);
6655 }
6656 }
6657 if !uc_storage
6658 .iter()
6659 .any(|uc| uc.is_primary_key || uc.columns == positions)
6660 {
6661 uc_storage.push(spg_storage::UniquenessConstraint {
6662 is_primary_key: true,
6663 columns: positions,
6664 nulls_not_distinct: false,
6665 });
6666 }
6667 }
6668 schema.uniqueness_constraints = uc_storage.clone();
6669 schema.checks = check_exprs;
6670 self.active_catalog_mut().create_table(schema)?;
6671 let table = self
6675 .active_catalog_mut()
6676 .get_mut(&table_name)
6677 .expect("just created");
6678 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6679 let idx_name = if inline_pk_columns.len() == 1 {
6680 alloc::format!("{table_name}_pkey")
6681 } else {
6682 alloc::format!("{table_name}_pkey_{i}")
6683 };
6684 if let Err(e) = table.add_index(idx_name, col_name) {
6685 return Err(EngineError::Storage(e));
6686 }
6687 }
6688 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6689 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6694 for (k, col) in columns.iter().enumerate() {
6695 let already = table.indices().iter().any(|idx| {
6696 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6697 && table.schema().columns[idx.column_position].name == *col
6698 });
6699 if already {
6700 continue;
6701 }
6702 let idx_name = match (name.as_ref(), columns.len(), k) {
6703 (Some(n), 1, _) => n.clone(),
6704 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6705 (None, _, _) => {
6706 alloc::format!("{table_name}_{col}_ftidx")
6707 }
6708 };
6709 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6710 return Err(EngineError::Storage(e));
6711 }
6712 }
6713 continue;
6714 }
6715 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6719 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6720 ("pkey", columns, None)
6721 }
6722 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6723 spg_sql::ast::TableConstraint::Index { name, columns } => {
6724 ("idx", columns, name.as_ref())
6725 }
6726 spg_sql::ast::TableConstraint::Check { .. } => continue,
6727 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6729 };
6730 let leading = &names[0];
6731 let already = table.indices().iter().any(|idx| {
6734 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6735 && table.schema().columns[idx.column_position].name == *leading
6736 });
6737 if already {
6738 continue;
6739 }
6740 let idx_name = if let Some(n) = explicit_name {
6741 n.clone()
6742 } else if names.len() == 1 {
6743 alloc::format!("{table_name}_{leading}_{suffix}")
6744 } else {
6745 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6746 };
6747 if let Err(e) = table.add_index(idx_name, leading) {
6748 return Err(EngineError::Storage(e));
6749 }
6750 }
6751 Ok(QueryResult::CommandOk {
6752 affected: 0,
6753 modified_catalog: !self.in_transaction(),
6754 })
6755 }
6756
6757 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6758 for tuple in &mut stmt.rows {
6766 for cell in tuple.iter_mut() {
6767 self.resolve_sequence_calls_in_expr(cell)?;
6768 }
6769 }
6770 if let Some(select) = stmt.select_source.clone() {
6775 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6776 let rows = match select_result {
6777 QueryResult::Rows { rows, .. } => rows,
6778 other => {
6779 return Err(EngineError::Unsupported(alloc::format!(
6780 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6781 )));
6782 }
6783 };
6784 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6785 for row in rows {
6786 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6787 for v in row.values {
6788 tuple.push(value_to_literal_expr_permissive(v)?);
6789 }
6790 materialised.push(tuple);
6791 }
6792 let recurse = InsertStatement {
6793 table: stmt.table,
6794 columns: stmt.columns,
6795 rows: materialised,
6796 select_source: None,
6797 on_conflict: stmt.on_conflict,
6798 returning: stmt.returning,
6799 };
6800 return self.exec_insert(recurse);
6801 }
6802 let clock = self.clock;
6806 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6812 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6813 let trigger_session_cfg: Option<alloc::string::String> = self
6814 .session_params
6815 .get("default_text_search_config")
6816 .cloned();
6817 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6823 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6824 EngineError::Storage(StorageError::TableNotFound {
6825 name: stmt.table.clone(),
6826 })
6827 })?;
6828 preview_table.schema().columns.clone()
6829 };
6830 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6831 pre_borrow_column_meta
6832 .iter()
6833 .enumerate()
6834 .filter_map(|(i, col)| {
6835 if let Some(inline) = &col.inline_enum_variants {
6840 return Some((i, inline.clone()));
6841 }
6842 col.user_enum_type.as_ref().and_then(|ename| {
6843 self.active_catalog()
6844 .enum_types()
6845 .get(ename)
6846 .map(|e| (i, e.labels.clone()))
6847 })
6848 })
6849 .collect();
6850 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6855 pre_borrow_column_meta
6856 .iter()
6857 .enumerate()
6858 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6859 .collect();
6860 let mut seq_floors: alloc::collections::BTreeMap<usize, i64> =
6868 alloc::collections::BTreeMap::new();
6869 for (i, col) in pre_borrow_column_meta.iter().enumerate() {
6870 if col.auto_increment
6871 && let Some(sd) = self.active_catalog().sequences().get(&alloc::format!(
6872 "{}_{}_seq",
6873 stmt.table,
6874 col.name
6875 ))
6876 {
6877 let floor = if sd.is_called {
6880 sd.last_value + 1
6881 } else {
6882 sd.last_value
6883 };
6884 seq_floors.insert(i, floor);
6885 }
6886 }
6887 let table = self
6888 .active_catalog_mut()
6889 .get_mut(&stmt.table)
6890 .ok_or_else(|| {
6891 EngineError::Storage(StorageError::TableNotFound {
6892 name: stmt.table.clone(),
6893 })
6894 })?;
6895 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6901 let schema_cols_len = column_meta.len();
6902 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6906 None => None, Some(cols) => {
6908 let mut map = alloc::vec![None; schema_cols_len];
6909 for (j, name) in cols.iter().enumerate() {
6910 let idx = column_meta
6911 .iter()
6912 .position(|c| c.name == *name)
6913 .ok_or_else(|| {
6914 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6915 })?;
6916 if map[idx].is_some() {
6917 return Err(EngineError::Storage(StorageError::ArityMismatch {
6918 expected: schema_cols_len,
6919 actual: cols.len(),
6920 }));
6921 }
6922 map[idx] = Some(j);
6923 }
6924 for (i, col) in column_meta.iter().enumerate() {
6928 if map[i].is_none()
6929 && !col.nullable
6930 && col.default.is_none()
6931 && col.runtime_default.is_none()
6932 && !col.auto_increment
6933 {
6934 return Err(EngineError::Storage(StorageError::NullInNotNull {
6935 column: col.name.clone(),
6936 }));
6937 }
6938 }
6939 Some(map)
6940 }
6941 };
6942 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6943 let fks = table.schema().foreign_keys.clone();
6949 let mut affected = 0usize;
6950 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6953 let mut auto_cursors: alloc::collections::BTreeMap<usize, i64> =
6961 alloc::collections::BTreeMap::new();
6962 for tuple in stmt.rows {
6963 if tuple.len() != expected_tuple_len {
6964 return Err(EngineError::Storage(StorageError::ArityMismatch {
6965 expected: expected_tuple_len,
6966 actual: tuple.len(),
6967 }));
6968 }
6969 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6973 let raw_tuple: Vec<Value> = tuple
6975 .into_iter()
6976 .map(literal_expr_to_value)
6977 .collect::<Result<_, _>>()?;
6978 let mut out = Vec::with_capacity(schema_cols_len);
6979 for (i, col) in column_meta.iter().enumerate() {
6980 let mut raw = match map[i] {
6981 Some(j) => raw_tuple[j].clone(),
6982 None => resolve_column_default_free(col, clock)?,
6983 };
6984 if col.auto_increment && raw.is_null() {
6985 let next = match auto_cursors.get(&i) {
6986 Some(n) => *n,
6987 None => {
6988 let base = table.next_auto_value(i).ok_or_else(|| {
6989 EngineError::Unsupported(alloc::format!(
6990 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6991 col.name
6992 ))
6993 })?;
6994 base.max(seq_floors.get(&i).copied().unwrap_or(i64::MIN))
6995 }
6996 };
6997 auto_cursors.insert(i, next + 1);
6998 raw = Value::BigInt(next);
6999 }
7000 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
7001 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
7002 let coerced =
7003 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
7004 check_unsigned_range(&coerced, col, i)?;
7005 out.push(coerced);
7006 }
7007 out
7008 } else {
7009 let mut out = Vec::with_capacity(schema_cols_len);
7011 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
7012 let mut raw = literal_expr_to_value(expr)?;
7013 if col.auto_increment && raw.is_null() {
7014 let next = match auto_cursors.get(&i) {
7015 Some(n) => *n,
7016 None => {
7017 let base = table.next_auto_value(i).ok_or_else(|| {
7018 EngineError::Unsupported(alloc::format!(
7019 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
7020 col.name
7021 ))
7022 })?;
7023 base.max(seq_floors.get(&i).copied().unwrap_or(i64::MIN))
7024 }
7025 };
7026 auto_cursors.insert(i, next + 1);
7027 raw = Value::BigInt(next);
7028 }
7029 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
7030 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
7031 let coerced =
7032 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
7033 check_unsigned_range(&coerced, col, i)?;
7034 out.push(coerced);
7035 }
7036 out
7037 };
7038 all_values.push(values);
7039 }
7040 let uniqueness = table.schema().uniqueness_constraints.clone();
7045 let _ = table;
7046 if !fks.is_empty() {
7047 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
7048 }
7049 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
7051 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
7065 let mut skipped_count = 0usize;
7066 if let Some(clause) = &stmt.on_conflict {
7067 let (conflict_cols, conflict_nnd) = resolve_on_conflict_columns(
7068 self.active_catalog(),
7069 &stmt.table,
7070 clause.target_columns.as_slice(),
7071 )?;
7072 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
7073 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
7074 for values in all_values {
7075 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
7076 let has_null_key =
7082 !conflict_nnd && key_tuple.iter().any(|v| matches!(v, Value::Null));
7083 let collides_with_table = !has_null_key
7084 && on_conflict_keys_exist(
7085 self.active_catalog(),
7086 &stmt.table,
7087 &conflict_cols,
7088 &key_tuple,
7089 );
7090 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
7091 let collides_with_batch =
7092 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
7093 let collides = collides_with_table || collides_with_batch;
7094 match (&clause.action, collides) {
7095 (_, false) => {
7096 seen_keys.push(key_tuple_owned);
7097 kept.push(values);
7098 }
7099 (spg_sql::ast::OnConflictAction::Nothing, true) => {
7100 skipped_count += 1;
7101 }
7102 (
7103 spg_sql::ast::OnConflictAction::Update {
7104 assignments,
7105 where_,
7106 },
7107 true,
7108 ) => {
7109 if !collides_with_table {
7110 skipped_count += 1;
7111 continue;
7112 }
7113 let target_pos = lookup_row_position_by_keys(
7114 self.active_catalog(),
7115 &stmt.table,
7116 &conflict_cols,
7117 &key_tuple,
7118 )
7119 .ok_or_else(|| {
7120 EngineError::Unsupported(
7121 "ON CONFLICT DO UPDATE: conflict detected but row \
7122 position could not be resolved (cold-tier row?)"
7123 .into(),
7124 )
7125 })?;
7126 let updated = apply_on_conflict_assignments(
7127 self.active_catalog(),
7128 &stmt.table,
7129 target_pos,
7130 &values,
7131 assignments,
7132 where_.as_ref(),
7133 )?;
7134 if let Some(new_row) = updated {
7135 pending_updates.push((target_pos, new_row));
7136 } else {
7137 skipped_count += 1;
7138 }
7139 }
7140 }
7141 }
7142 all_values = kept;
7143 }
7144 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
7150 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
7151 let table = self
7153 .active_catalog_mut()
7154 .get_mut(&stmt.table)
7155 .ok_or_else(|| {
7156 EngineError::Storage(StorageError::TableNotFound {
7157 name: stmt.table.clone(),
7158 })
7159 })?;
7160 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
7164 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
7168 'rowloop: for values in all_values {
7169 let mut row = Row::new(values);
7170 for fd in &before_insert_triggers {
7175 let (outcome, deferred) = triggers::fire_row_trigger(
7176 fd,
7177 Some(row.clone()),
7178 None,
7179 &stmt.table,
7180 &column_meta,
7181 &[],
7182 trigger_session_cfg.as_deref(),
7183 false,
7184 )
7185 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
7186 deferred_embedded.extend(deferred);
7187 match outcome {
7188 triggers::TriggerOutcome::Row(r) => row = r,
7189 triggers::TriggerOutcome::Skip => continue 'rowloop,
7190 }
7191 }
7192 if stmt.returning.is_some() {
7193 returning_rows.push(row.values.clone());
7194 }
7195 let inserted = row.clone();
7198 table.insert(row)?;
7199 affected += 1;
7200 for fd in &after_insert_triggers {
7204 let (_outcome, deferred) = triggers::fire_row_trigger(
7205 fd,
7206 Some(inserted.clone()),
7207 None,
7208 &stmt.table,
7209 &column_meta,
7210 &[],
7211 trigger_session_cfg.as_deref(),
7212 true,
7213 )
7214 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
7215 deferred_embedded.extend(deferred);
7216 }
7217 }
7218 for (pos, new_row) in pending_updates {
7222 if stmt.returning.is_some() {
7223 returning_rows.push(new_row.clone());
7224 }
7225 table.update_row(pos, new_row)?;
7226 affected += 1;
7227 }
7228 let _ = skipped_count;
7229 let _ = table;
7235 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
7236 if let Some(items) = &stmt.returning {
7240 return self.build_returning_rows(&stmt.table, items, returning_rows);
7241 }
7242 if !self.in_transaction() && affected > 0 {
7247 self.statistics
7248 .record_modifications(&stmt.table, affected as u64);
7249 }
7250 Ok(QueryResult::CommandOk {
7251 affected,
7252 modified_catalog: !self.in_transaction(),
7253 })
7254 }
7255
7256 fn exec_select_as_of_segment(
7269 &self,
7270 stmt: &SelectStatement,
7271 from: &spg_sql::ast::FromClause,
7272 segment_id: u32,
7273 ) -> Result<QueryResult, EngineError> {
7274 if !from.joins.is_empty()
7277 || stmt.group_by.is_some()
7278 || stmt.having.is_some()
7279 || !stmt.unions.is_empty()
7280 || !stmt.order_by.is_empty()
7281 || stmt.offset.is_some()
7282 || stmt.distinct
7283 || aggregate::uses_aggregate(stmt)
7284 {
7285 return Err(EngineError::Unsupported(
7286 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
7287 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
7288 .into(),
7289 ));
7290 }
7291 let table = self
7292 .active_catalog()
7293 .get(&from.primary.name)
7294 .ok_or_else(|| StorageError::TableNotFound {
7295 name: from.primary.name.clone(),
7296 })?;
7297 let schema = table.schema().clone();
7298 let schema_cols = &schema.columns;
7299 let alias = from
7300 .primary
7301 .alias
7302 .as_deref()
7303 .unwrap_or(from.primary.name.as_str());
7304 let ctx = EvalContext::new(schema_cols, Some(alias));
7305 let seg = self
7306 .active_catalog()
7307 .cold_segment(segment_id)
7308 .ok_or_else(|| {
7309 EngineError::Unsupported(alloc::format!(
7310 "AS OF SEGMENT: cold segment {segment_id} not registered"
7311 ))
7312 })?;
7313 let mut out_rows: Vec<Row> = Vec::new();
7314 let mut limit_remaining: Option<usize> =
7315 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
7316 for (_key, body) in seg.scan() {
7317 let (row, _consumed) =
7318 spg_storage::decode_row_body_dense(&body, &schema, seg.codec_version())
7319 .map_err(EngineError::Storage)?;
7320 if let Some(where_expr) = &stmt.where_ {
7321 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
7322 if !matches!(cond, Value::Bool(true)) {
7323 continue;
7324 }
7325 }
7326 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7328 out_rows.push(projected);
7329 if let Some(rem) = limit_remaining.as_mut() {
7330 if *rem == 0 {
7331 out_rows.pop();
7332 break;
7333 }
7334 *rem -= 1;
7335 }
7336 }
7337 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7339 Ok(QueryResult::Rows {
7340 columns,
7341 rows: out_rows,
7342 })
7343 }
7344
7345 fn eval_expr_simple(
7350 &self,
7351 expr: &Expr,
7352 row: &Row,
7353 ctx: &EvalContext,
7354 ) -> Result<Value, EngineError> {
7355 let cancel = CancelToken::none();
7356 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7357 }
7358
7359 fn build_returning_rows(
7366 &self,
7367 table_name: &str,
7368 items: &[SelectItem],
7369 mutated_rows: Vec<Vec<Value>>,
7370 ) -> Result<QueryResult, EngineError> {
7371 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7372 EngineError::Storage(StorageError::TableNotFound {
7373 name: table_name.into(),
7374 })
7375 })?;
7376 let schema_cols = table.schema().columns.clone();
7377 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7378 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7379 for values in mutated_rows {
7380 let row = Row::new(values);
7381 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7382 out_rows.push(projected);
7383 }
7384 Ok(QueryResult::Rows {
7385 columns,
7386 rows: out_rows,
7387 })
7388 }
7389
7390 fn project_row_simple(
7394 &self,
7395 row: &Row,
7396 items: &[SelectItem],
7397 schema_cols: &[ColumnSchema],
7398 alias: &str,
7399 ) -> Result<Row, EngineError> {
7400 let ctx = EvalContext::new(schema_cols, Some(alias));
7401 let cancel = CancelToken::none();
7402 let mut out_vals = Vec::new();
7403 for item in items {
7404 match item {
7405 SelectItem::Wildcard => {
7406 out_vals.extend(row.values.iter().cloned());
7407 }
7408 SelectItem::Expr { expr, .. } => {
7409 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7410 out_vals.push(v);
7411 }
7412 }
7413 }
7414 Ok(Row::new(out_vals))
7415 }
7416
7417 fn derive_output_columns(
7422 &self,
7423 items: &[SelectItem],
7424 schema_cols: &[ColumnSchema],
7425 _alias: &str,
7426 ) -> Vec<ColumnSchema> {
7427 let mut out = Vec::new();
7428 for item in items {
7429 match item {
7430 SelectItem::Wildcard => {
7431 out.extend(schema_cols.iter().cloned());
7432 }
7433 SelectItem::Expr { expr, alias } => {
7434 if let Expr::Column(col) = expr
7440 && let Some(sc) = schema_cols.iter().find(|c| c.name == col.name)
7441 {
7442 let name = alias.clone().unwrap_or_else(|| sc.name.clone());
7443 out.push(ColumnSchema::new(name, sc.ty, sc.nullable));
7444 continue;
7445 }
7446 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7447 out.push(ColumnSchema::new(name, DataType::Text, true));
7450 }
7451 }
7452 }
7453 out
7454 }
7455
7456 fn exec_select_cancel(
7457 &self,
7458 stmt: &SelectStatement,
7459 cancel: CancelToken<'_>,
7460 ) -> Result<QueryResult, EngineError> {
7461 cancel.check()?;
7462 if !self.active_catalog().views().is_empty() {
7469 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7470 return self.exec_select_cancel(&rewritten, cancel);
7471 }
7472 }
7473 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7482 return self.exec_select_with_meta_views(stmt, cancel);
7483 }
7484 if let Some(from) = &stmt.from
7493 && let Some(seg_id) = from.primary.as_of_segment
7494 {
7495 return self.exec_select_as_of_segment(stmt, from, seg_id);
7496 }
7497 if let Some(from) = &stmt.from
7501 && from.joins.is_empty()
7502 && stmt.where_.is_none()
7503 && stmt.group_by.is_none()
7504 && stmt.having.is_none()
7505 && stmt.unions.is_empty()
7506 && stmt.order_by.is_empty()
7507 && stmt.limit.is_none()
7508 && stmt.offset.is_none()
7509 && !stmt.distinct
7510 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7511 {
7512 let lower = from.primary.name.to_ascii_lowercase();
7513 match lower.as_str() {
7514 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7515 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7517 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7518 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7519 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7520 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7521 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7522 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7523 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7524 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7525 _ => {}
7526 }
7527 }
7528 if !stmt.ctes.is_empty() {
7536 return self.exec_with_ctes(stmt, cancel);
7537 }
7538 let mut stmt_owned;
7545 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7546 stmt_owned = stmt.clone();
7547 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7548 &stmt_owned
7549 } else {
7550 stmt
7551 };
7552 if stmt_ref.unions.is_empty() {
7553 return self.exec_bare_select_cancel(stmt_ref, cancel);
7554 }
7555 let mut head = stmt_ref.clone();
7560 head.unions = Vec::new();
7561 head.order_by = Vec::new();
7562 head.limit = None;
7563 let QueryResult::Rows { columns, mut rows } =
7564 self.exec_bare_select_cancel(&head, cancel)?
7565 else {
7566 unreachable!("bare SELECT cannot return CommandOk")
7567 };
7568 for (kind, peer) in &stmt_ref.unions {
7569 let QueryResult::Rows {
7570 columns: peer_cols,
7571 rows: peer_rows,
7572 } = self.exec_bare_select_cancel(peer, cancel)?
7573 else {
7574 unreachable!("bare SELECT cannot return CommandOk")
7575 };
7576 if peer_cols.len() != columns.len() {
7577 return Err(EngineError::Unsupported(alloc::format!(
7578 "UNION arity mismatch: head has {} columns, peer has {}",
7579 columns.len(),
7580 peer_cols.len()
7581 )));
7582 }
7583 rows.extend(peer_rows);
7584 if matches!(kind, UnionKind::Distinct) {
7585 rows = dedup_rows(rows);
7586 }
7587 }
7588 if !stmt.order_by.is_empty() {
7591 let synth_ctx = EvalContext::new(&columns, None);
7592 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7593 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7594 for r in rows {
7595 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7596 tagged.push((keys, r));
7597 }
7598 sort_by_keys(&mut tagged, &descs);
7599 rows = tagged.into_iter().map(|(_, r)| r).collect();
7600 }
7601 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7602 Ok(QueryResult::Rows { columns, rows })
7603 }
7604
7605 #[allow(clippy::too_many_lines)]
7606 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7614 &self,
7615 stmt: &SelectStatement,
7616 primary: &TableRef,
7617 cancel: CancelToken<'_>,
7618 ) -> Result<QueryResult, EngineError> {
7619 let expr = primary
7620 .unnest_expr
7621 .as_deref()
7622 .expect("caller guards unnest_expr.is_some()");
7623 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7626 let ctx = EvalContext::new(&empty_schema, None);
7627 let dummy_row = Row::new(alloc::vec::Vec::new());
7628 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7631 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7632 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7633 Value::TextArray(items) => {
7634 let rows = items
7635 .into_iter()
7636 .map(|item| {
7637 Row::new(alloc::vec![match item {
7638 Some(s) => Value::Text(s),
7639 None => Value::Null,
7640 }])
7641 })
7642 .collect();
7643 (DataType::Text, rows)
7644 }
7645 Value::IntArray(items) => {
7646 let rows = items
7647 .into_iter()
7648 .map(|item| {
7649 Row::new(alloc::vec![match item {
7650 Some(n) => Value::Int(n),
7651 None => Value::Null,
7652 }])
7653 })
7654 .collect();
7655 (DataType::Int, rows)
7656 }
7657 Value::BigIntArray(items) => {
7658 let rows = items
7659 .into_iter()
7660 .map(|item| {
7661 Row::new(alloc::vec![match item {
7662 Some(n) => Value::BigInt(n),
7663 None => Value::Null,
7664 }])
7665 })
7666 .collect();
7667 (DataType::BigInt, rows)
7668 }
7669 other => {
7670 return Err(EngineError::Unsupported(alloc::format!(
7671 "unnest() expects an array argument, got {:?}",
7672 other.data_type()
7673 )));
7674 }
7675 };
7676 let alias = primary
7677 .alias
7678 .clone()
7679 .unwrap_or_else(|| "unnest".to_string());
7680 let col_name = primary
7686 .unnest_column_aliases
7687 .first()
7688 .cloned()
7689 .unwrap_or_else(|| alias.clone());
7690 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7691 let schema_cols = alloc::vec![col_schema.clone()];
7692 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7693 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7695 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7696 for row in rows {
7697 cancel.check()?;
7698 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7699 if matches!(v, Value::Bool(true)) {
7700 out.push(row);
7701 }
7702 }
7703 out
7704 } else {
7705 rows
7706 };
7707 if aggregate::uses_aggregate(stmt) {
7713 let agg_memo = core::cell::RefCell::new(memoize::MemoizeCache::default());
7717 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
7718 self.eval_expr_with_correlated(e, r, c, cancel, Some(&mut agg_memo.borrow_mut()))
7719 .map_err(|err| match err {
7720 EngineError::Eval(ev) => ev,
7721 other => eval::EvalError::TypeMismatch {
7722 detail: alloc::format!("{other}"),
7723 },
7724 })
7725 };
7726 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7727 let mut agg = aggregate::run(
7728 stmt,
7729 &filtered_refs,
7730 &schema_cols,
7731 Some(&alias),
7732 Some(&agg_correlated),
7733 )?;
7734 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7735 return Ok(QueryResult::Rows {
7736 columns: agg.columns,
7737 rows: agg.rows,
7738 });
7739 }
7740 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7742 let mut projected_rows: alloc::vec::Vec<Row> =
7743 alloc::vec::Vec::with_capacity(filtered.len());
7744 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7753 if let Some(srf_idx) = srf_position {
7754 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7755 .expect("checked by is_top_level_unnest above");
7756 for row in &filtered {
7757 let arr_val =
7758 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7759 let elements = array_value_to_elements(&arr_val)?;
7760 for elem in elements {
7764 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7765 for (i, p) in projection.iter().enumerate() {
7766 if i == srf_idx {
7767 vals.push(elem.clone());
7768 } else {
7769 vals.push(
7770 eval::eval_expr(&p.expr, row, &scan_ctx)
7771 .map_err(EngineError::Eval)?,
7772 );
7773 }
7774 }
7775 projected_rows.push(Row::new(vals));
7776 }
7777 }
7778 } else {
7779 let mut proj_memo = memoize::MemoizeCache::default();
7783 for row in &filtered {
7784 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7785 for p in &projection {
7786 vals.push(self.eval_expr_with_correlated(
7787 &p.expr,
7788 row,
7789 &scan_ctx,
7790 cancel,
7791 Some(&mut proj_memo),
7792 )?);
7793 }
7794 projected_rows.push(Row::new(vals));
7795 }
7796 }
7797 let columns: alloc::vec::Vec<ColumnSchema> = projection
7800 .iter()
7801 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7802 .collect();
7803 if !stmt.order_by.is_empty() {
7806 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7807 .iter()
7808 .enumerate()
7809 .map(|(i, r)| -> Result<_, EngineError> {
7810 let keys: Result<Vec<Value>, EngineError> = stmt
7811 .order_by
7812 .iter()
7813 .map(|ob| {
7814 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7815 })
7816 .collect();
7817 Ok((i, keys?))
7818 })
7819 .collect::<Result<_, _>>()?;
7820 indexed.sort_by(|a, b| {
7821 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7822 let o = &stmt.order_by[idx];
7823 let cmp = order_by_value_cmp(o.desc, o.nulls_first, ka, kb);
7824 if cmp != core::cmp::Ordering::Equal {
7825 return cmp;
7826 }
7827 }
7828 core::cmp::Ordering::Equal
7829 });
7830 projected_rows = indexed
7831 .into_iter()
7832 .map(|(i, _)| projected_rows[i].clone())
7833 .collect();
7834 }
7835 if let Some(offset) = stmt.offset_literal() {
7837 let off = (offset as usize).min(projected_rows.len());
7838 projected_rows.drain(..off);
7839 }
7840 if let Some(limit) = stmt.limit_literal() {
7841 projected_rows.truncate(limit as usize);
7842 }
7843 Ok(QueryResult::Rows {
7844 columns,
7845 rows: projected_rows,
7846 })
7847 }
7848
7849 fn exec_select_generate_series(
7860 &self,
7861 stmt: &SelectStatement,
7862 primary: &TableRef,
7863 cancel: CancelToken<'_>,
7864 ) -> Result<QueryResult, EngineError> {
7865 let args = primary
7866 .generate_series_args
7867 .as_ref()
7868 .expect("caller guards generate_series_args.is_some()");
7869 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7870 let ctx = EvalContext::new(&empty_schema, None);
7871 let dummy_row = Row::new(alloc::vec::Vec::new());
7872 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7873 for a in args {
7874 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7875 }
7876 let (elem_dtype, rows) = match arg_values.as_slice() {
7880 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7881 let interval_step = match step {
7882 Value::Interval { .. } => step.clone(),
7883 other => {
7884 return Err(EngineError::Unsupported(alloc::format!(
7885 "generate_series(timestamp, timestamp, …): \
7886 step must be INTERVAL, got {:?}",
7887 other.data_type()
7888 )));
7889 }
7890 };
7891 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7892 (DataType::Timestamp, rows)
7893 }
7894 [start, stop, step]
7895 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7896 {
7897 let s = value_to_i64(start);
7898 let e = value_to_i64(stop);
7899 let st = value_to_i64(step);
7900 let rows = generate_series_integers(s, e, st, &cancel)?;
7901 (DataType::BigInt, rows)
7902 }
7903 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7904 let s = value_to_i64(start);
7905 let e = value_to_i64(stop);
7906 let rows = generate_series_integers(s, e, 1, &cancel)?;
7907 (DataType::BigInt, rows)
7908 }
7909 _ => {
7910 return Err(EngineError::Unsupported(alloc::format!(
7911 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7912 argument shapes; got {:?}",
7913 arg_values
7914 .iter()
7915 .map(|v| v.data_type())
7916 .collect::<alloc::vec::Vec<_>>()
7917 )));
7918 }
7919 };
7920 let alias = primary
7921 .alias
7922 .clone()
7923 .unwrap_or_else(|| "generate_series".to_string());
7924 let col_name = alias.clone();
7925 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7926 let schema_cols = alloc::vec![col_schema.clone()];
7927 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7928 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7930 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7931 for row in rows {
7932 cancel.check()?;
7933 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7934 if matches!(v, Value::Bool(true)) {
7935 out.push(row);
7936 }
7937 }
7938 out
7939 } else {
7940 rows
7941 };
7942 if aggregate::uses_aggregate(stmt) {
7952 let agg_memo = core::cell::RefCell::new(memoize::MemoizeCache::default());
7956 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
7957 self.eval_expr_with_correlated(e, r, c, cancel, Some(&mut agg_memo.borrow_mut()))
7958 .map_err(|err| match err {
7959 EngineError::Eval(ev) => ev,
7960 other => eval::EvalError::TypeMismatch {
7961 detail: alloc::format!("{other}"),
7962 },
7963 })
7964 };
7965 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7966 let mut agg = aggregate::run(
7967 stmt,
7968 &filtered_refs,
7969 &schema_cols,
7970 Some(&alias),
7971 Some(&agg_correlated),
7972 )?;
7973 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7974 return Ok(QueryResult::Rows {
7975 columns: agg.columns,
7976 rows: agg.rows,
7977 });
7978 }
7979 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7981 let mut projected_rows: alloc::vec::Vec<Row> =
7982 alloc::vec::Vec::with_capacity(filtered.len());
7983 let mut proj_memo = memoize::MemoizeCache::default();
7984 for row in &filtered {
7985 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7986 for p in &projection {
7987 vals.push(self.eval_expr_with_correlated(
7989 &p.expr,
7990 row,
7991 &scan_ctx,
7992 cancel,
7993 Some(&mut proj_memo),
7994 )?);
7995 }
7996 projected_rows.push(Row::new(vals));
7997 }
7998 let columns: alloc::vec::Vec<ColumnSchema> = projection
7999 .iter()
8000 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
8001 .collect();
8002 if !stmt.order_by.is_empty() {
8004 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
8005 .iter()
8006 .enumerate()
8007 .map(|(i, r)| -> Result<_, EngineError> {
8008 let keys: Result<Vec<Value>, EngineError> = stmt
8009 .order_by
8010 .iter()
8011 .map(|ob| {
8012 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
8013 })
8014 .collect();
8015 Ok((i, keys?))
8016 })
8017 .collect::<Result<_, _>>()?;
8018 indexed.sort_by(|a, b| {
8019 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
8020 let o = &stmt.order_by[idx];
8021 let cmp = order_by_value_cmp(o.desc, o.nulls_first, ka, kb);
8022 if cmp != core::cmp::Ordering::Equal {
8023 return cmp;
8024 }
8025 }
8026 core::cmp::Ordering::Equal
8027 });
8028 projected_rows = indexed
8029 .into_iter()
8030 .map(|(i, _)| projected_rows[i].clone())
8031 .collect();
8032 }
8033 if let Some(offset) = stmt.offset_literal() {
8034 let off = (offset as usize).min(projected_rows.len());
8035 projected_rows.drain(..off);
8036 }
8037 if let Some(limit) = stmt.limit_literal() {
8038 projected_rows.truncate(limit as usize);
8039 }
8040 Ok(QueryResult::Rows {
8041 columns,
8042 rows: projected_rows,
8043 })
8044 }
8045
8046 fn exec_bare_select_cancel(
8047 &self,
8048 stmt: &SelectStatement,
8049 cancel: CancelToken<'_>,
8050 ) -> Result<QueryResult, EngineError> {
8051 check_with_ties_requires_order_by(stmt)?;
8056 if !self.meta_views_materialised && select_references_meta_view(stmt) {
8064 return self.exec_select_with_meta_views(stmt, cancel);
8065 }
8066 if select_has_window(stmt) {
8071 return self.exec_select_with_window(stmt, cancel);
8072 }
8073 let Some(from) = &stmt.from else {
8078 let empty_schema: Vec<ColumnSchema> = Vec::new();
8079 let ctx = self.ev_ctx(&empty_schema, None);
8080 let projection = build_projection(&stmt.items, &empty_schema, "")?;
8081 let dummy_row = Row::new(Vec::new());
8082 let mut values = Vec::with_capacity(projection.len());
8083 for p in &projection {
8084 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
8085 }
8086 let columns: Vec<ColumnSchema> = projection
8087 .into_iter()
8088 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8089 .collect();
8090 return Ok(QueryResult::Rows {
8091 columns,
8092 rows: alloc::vec![Row::new(values)],
8093 });
8094 };
8095 if !from.joins.is_empty() {
8099 return self.exec_joined_select(stmt, from, cancel);
8100 }
8101 if from.primary.unnest_expr.is_some() {
8108 return self.exec_select_unnest(stmt, &from.primary, cancel);
8109 }
8110 if from.primary.generate_series_args.is_some() {
8116 return self.exec_select_generate_series(stmt, &from.primary, cancel);
8117 }
8118 let primary = &from.primary;
8119 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
8120 StorageError::TableNotFound {
8121 name: primary.name.clone(),
8122 }
8123 })?;
8124 let schema_cols = &table.schema().columns;
8125 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
8128 let ctx = self.ev_ctx(schema_cols, Some(alias));
8129
8130 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
8135 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
8136 }
8137
8138 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
8146 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
8149 .or_else(|| {
8150 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
8156 })
8157 .or_else(|| {
8158 try_trgm_seek(w, schema_cols, table, alias)
8164 })
8165 });
8166
8167 if aggregate::uses_aggregate(stmt) {
8170 let mut filtered: Vec<&Row> = Vec::new();
8171 let mut memo = memoize::MemoizeCache::new();
8175 if let Some(rows) = &indexed_rows {
8176 for cow in rows {
8177 let row = cow.as_ref();
8178 if let Some(where_expr) = &stmt.where_ {
8179 let cond = self.eval_expr_with_correlated(
8180 where_expr,
8181 row,
8182 &ctx,
8183 cancel,
8184 Some(&mut memo),
8185 )?;
8186 if !matches!(cond, Value::Bool(true)) {
8187 continue;
8188 }
8189 }
8190 filtered.push(row);
8191 }
8192 } else {
8193 for i in 0..table.row_count() {
8194 let row = &table.rows()[i];
8195 if let Some(where_expr) = &stmt.where_ {
8196 let cond = self.eval_expr_with_correlated(
8197 where_expr,
8198 row,
8199 &ctx,
8200 cancel,
8201 Some(&mut memo),
8202 )?;
8203 if !matches!(cond, Value::Bool(true)) {
8204 continue;
8205 }
8206 }
8207 filtered.push(row);
8208 }
8209 }
8210 let agg_memo = core::cell::RefCell::new(memoize::MemoizeCache::default());
8214 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
8215 self.eval_expr_with_correlated(e, r, c, cancel, Some(&mut agg_memo.borrow_mut()))
8216 .map_err(|err| match err {
8217 EngineError::Eval(ev) => ev,
8218 other => eval::EvalError::TypeMismatch {
8219 detail: alloc::format!("{other}"),
8220 },
8221 })
8222 };
8223 let mut agg = aggregate::run(
8224 stmt,
8225 &filtered,
8226 schema_cols,
8227 Some(alias),
8228 Some(&agg_correlated),
8229 )?;
8230 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8231 return Ok(QueryResult::Rows {
8232 columns: agg.columns,
8233 rows: agg.rows,
8234 });
8235 }
8236
8237 let projection = build_projection(&stmt.items, schema_cols, alias)?;
8238 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
8246
8247 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8250 let mut memo = memoize::MemoizeCache::new();
8252 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
8255 if loop_idx.is_multiple_of(256) {
8256 cancel.check()?;
8257 }
8258 if let Some(where_expr) = &stmt.where_ {
8259 let cond =
8260 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
8261 if !matches!(cond, Value::Bool(true)) {
8262 return Ok(());
8263 }
8264 }
8265 let order_keys = if stmt.order_by.is_empty() {
8266 Vec::new()
8267 } else {
8268 build_order_keys(&stmt.order_by, row, &ctx)?
8269 };
8270 if let Some(srf_idx) = srf_position {
8271 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
8272 .expect("checked by is_top_level_unnest above");
8273 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
8274 let elements = array_value_to_elements(&arr_val)?;
8275 for elem in elements {
8276 let mut values = Vec::with_capacity(projection.len());
8277 for (i, p) in projection.iter().enumerate() {
8278 if i == srf_idx {
8279 values.push(elem.clone());
8280 } else {
8281 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8282 }
8283 }
8284 tagged.push((order_keys.clone(), Row::new(values)));
8285 }
8286 } else {
8287 let mut values = Vec::with_capacity(projection.len());
8288 for p in &projection {
8289 values.push(self.eval_expr_with_correlated(&p.expr, row, &ctx, cancel, None)?);
8291 }
8292 tagged.push((order_keys, Row::new(values)));
8293 }
8294 Ok(())
8295 };
8296 if let Some(rows) = &indexed_rows {
8297 for (loop_idx, cow) in rows.iter().enumerate() {
8298 process_row(cow.as_ref(), loop_idx)?;
8299 }
8300 } else {
8301 for i in 0..table.row_count() {
8302 process_row(&table.rows()[i], i)?;
8303 }
8304 }
8305
8306 if !stmt.order_by.is_empty() {
8307 let keep = if stmt.distinct || stmt.limit_with_ties {
8315 None
8316 } else {
8317 stmt.limit_literal()
8318 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8319 };
8320 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8321 partial_sort_tagged(&mut tagged, keep, &descs);
8322 }
8323
8324 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
8334 apply_offset_and_limit_tagged(
8335 &mut tagged,
8336 stmt.offset_literal(),
8337 stmt.limit_literal(),
8338 true,
8339 );
8340 tagged.into_iter().map(|(_, r)| r).collect()
8341 } else {
8342 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8343 if stmt.distinct {
8344 output_rows = dedup_rows(output_rows);
8345 }
8346 apply_offset_and_limit(
8347 &mut output_rows,
8348 stmt.offset_literal(),
8349 stmt.limit_literal(),
8350 );
8351 output_rows
8352 };
8353
8354 let columns: Vec<ColumnSchema> = projection
8355 .into_iter()
8356 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8357 .collect();
8358
8359 Ok(QueryResult::Rows {
8360 columns,
8361 rows: output_rows,
8362 })
8363 }
8364
8365 #[allow(clippy::too_many_lines)]
8372 fn materialise_table_ref(
8380 &self,
8381 tref: &TableRef,
8382 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8383 if let Some(expr) = tref.unnest_expr.as_deref() {
8384 let empty_schema: Vec<ColumnSchema> = Vec::new();
8385 let ctx = EvalContext::new(&empty_schema, None);
8386 let dummy_row = Row::new(Vec::new());
8387 let (elem_dtype, rows) =
8388 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8389 Value::Null => (DataType::Text, Vec::new()),
8390 Value::TextArray(items) => (
8391 DataType::Text,
8392 items
8393 .into_iter()
8394 .map(|item| {
8395 Row::new(alloc::vec![match item {
8396 Some(s) => Value::Text(s),
8397 None => Value::Null,
8398 }])
8399 })
8400 .collect(),
8401 ),
8402 Value::IntArray(items) => (
8403 DataType::Int,
8404 items
8405 .into_iter()
8406 .map(|item| {
8407 Row::new(alloc::vec![match item {
8408 Some(n) => Value::Int(n),
8409 None => Value::Null,
8410 }])
8411 })
8412 .collect(),
8413 ),
8414 Value::BigIntArray(items) => (
8415 DataType::BigInt,
8416 items
8417 .into_iter()
8418 .map(|item| {
8419 Row::new(alloc::vec![match item {
8420 Some(n) => Value::BigInt(n),
8421 None => Value::Null,
8422 }])
8423 })
8424 .collect(),
8425 ),
8426 other => {
8427 return Err(EngineError::Unsupported(alloc::format!(
8428 "unnest() expects an array argument, got {:?}",
8429 other.data_type()
8430 )));
8431 }
8432 };
8433 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8434 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8435 return Ok((
8436 rows,
8437 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8438 ));
8439 }
8440 let table =
8441 self.active_catalog()
8442 .get(&tref.name)
8443 .ok_or_else(|| StorageError::TableNotFound {
8444 name: tref.name.clone(),
8445 })?;
8446 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8447 let cols = table.schema().columns.clone();
8448 Ok((rows, cols))
8449 }
8450
8451 fn materialise_table_ref_filtered(
8460 &self,
8461 tref: &TableRef,
8462 preds: &[&Expr],
8463 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8464 if preds.is_empty()
8465 || tref.unnest_expr.is_some()
8466 || tref.lateral_subquery.is_some()
8467 || tref.as_of_segment.is_some()
8468 {
8469 return self.materialise_table_ref(tref);
8470 }
8471 let Some(table) = self.active_catalog().get(&tref.name) else {
8472 return self.materialise_table_ref(tref);
8473 };
8474 let cols = table.schema().columns.clone();
8475 let alias = tref.alias.as_deref().unwrap_or(tref.name.as_str());
8476 let mut seeded: Option<Vec<usize>> = None;
8479 for p in preds {
8480 if let Expr::Binary {
8481 lhs,
8482 op: spg_sql::ast::BinOp::Eq,
8483 rhs,
8484 } = p
8485 {
8486 let pair = match (lhs.as_ref(), rhs.as_ref()) {
8487 (Expr::Column(c), Expr::Literal(l)) | (Expr::Literal(l), Expr::Column(c)) => {
8488 Some((c, l))
8489 }
8490 _ => None,
8491 };
8492 if let Some((c, l)) = pair
8493 && c.qualifier
8494 .as_deref()
8495 .is_none_or(|q| q.eq_ignore_ascii_case(alias))
8496 && let Some(pos) = cols.iter().position(|s| s.name == c.name)
8497 && let Some(idx) = table.index_on(pos)
8498 && let Some(key) = spg_storage::IndexKey::from_value(&eval::literal_to_value(l))
8499 {
8500 let mut ids = Vec::new();
8501 let mut all_hot = true;
8502 for loc in idx.lookup_eq(&key) {
8503 match *loc {
8504 spg_storage::RowLocator::Hot(i) => ids.push(i),
8505 spg_storage::RowLocator::Cold { .. } => {
8506 all_hot = false;
8507 break;
8508 }
8509 }
8510 }
8511 if all_hot {
8512 seeded = Some(ids);
8513 break;
8514 }
8515 }
8516 }
8517 }
8518 let ctx = EvalContext::new(&cols, Some(alias));
8519 let mut out: Vec<Row> = Vec::new();
8520 let push_if = |row: &Row, out: &mut Vec<Row>| -> Result<(), EngineError> {
8521 for p in preds {
8522 let v = eval::eval_expr(p, row, &ctx).map_err(EngineError::Eval)?;
8523 if !matches!(v, Value::Bool(true)) {
8524 return Ok(());
8525 }
8526 }
8527 out.push(row.clone());
8528 Ok(())
8529 };
8530 match seeded {
8531 Some(ids) => {
8532 for i in ids {
8533 if let Some(row) = table.rows().get(i) {
8534 push_if(row, &mut out)?;
8535 }
8536 }
8537 }
8538 None => {
8539 for row in table.rows().iter() {
8540 push_if(row, &mut out)?;
8541 }
8542 }
8543 }
8544 Ok((out, cols))
8545 }
8546
8547 fn composite_col_pos(schema: &[ColumnSchema], c: &spg_sql::ast::ColumnName) -> Option<usize> {
8562 if let Some(q) = &c.qualifier {
8563 let composite = alloc::format!("{q}.{}", c.name);
8564 return schema.iter().position(|s| s.name == composite);
8565 }
8566 let suffix = alloc::format!(".{}", c.name);
8567 let mut hits = schema
8568 .iter()
8569 .enumerate()
8570 .filter(|(_, s)| s.name.ends_with(&suffix) || s.name == c.name);
8571 let first = hits.next();
8572 if hits.next().is_some() {
8573 return None; }
8575 first.map(|(i, _)| i)
8576 }
8577
8578 fn peer_col_pos(
8581 peer_alias: &str,
8582 peer_cols: &[ColumnSchema],
8583 c: &spg_sql::ast::ColumnName,
8584 ) -> Option<usize> {
8585 if let Some(q) = &c.qualifier
8586 && !q.eq_ignore_ascii_case(peer_alias)
8587 {
8588 return None;
8589 }
8590 peer_cols.iter().position(|s| s.name == c.name)
8591 }
8592
8593 fn null_out_unreferenced(
8598 rows: &mut [Row],
8599 cols: &[ColumnSchema],
8600 alias: &str,
8601 needed: &alloc::collections::BTreeSet<(String, String)>,
8602 ) {
8603 let keep: Vec<bool> = cols
8604 .iter()
8605 .map(|c| needed.contains(&(alias.to_string(), c.name.clone())))
8606 .collect();
8607 if keep.iter().all(|k| *k) {
8608 return;
8609 }
8610 for row in rows.iter_mut() {
8611 for (i, k) in keep.iter().enumerate() {
8612 if !*k && i < row.values.len() {
8613 row.values[i] = Value::Null;
8614 }
8615 }
8616 }
8617 }
8618
8619 fn build_joined_filtered_rows(
8620 &self,
8621 from: &FromClause,
8622 where_: Option<&Expr>,
8623 cancel: CancelToken<'_>,
8624 needed: Option<&alloc::collections::BTreeSet<(String, String)>>,
8625 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8626 let primary_alias = from
8627 .primary
8628 .alias
8629 .as_deref()
8630 .unwrap_or(from.primary.name.as_str())
8631 .to_string();
8632 let mut primary_preds: Vec<&Expr> = Vec::new();
8641 let mut peer_preds: Vec<Vec<&Expr>> = alloc::vec![Vec::new(); from.joins.len()];
8642 if let Some(w) = where_ {
8643 for sub in reorder::split_and_conjunctions(w) {
8644 if expr_has_subquery(sub) || aggregate::contains_aggregate(sub) {
8645 continue;
8646 }
8647 let mut quals: Vec<&str> = Vec::new();
8648 let mut all_qualified = true;
8649 collect_column_qualifiers(sub, &mut quals, &mut all_qualified);
8650 if !all_qualified || quals.is_empty() {
8651 continue;
8652 }
8653 let q0 = quals[0];
8654 if !quals.iter().all(|q| q.eq_ignore_ascii_case(q0)) {
8655 continue;
8656 }
8657 if q0.eq_ignore_ascii_case(&primary_alias) {
8658 primary_preds.push(sub);
8659 continue;
8660 }
8661 for (i, j) in from.joins.iter().enumerate() {
8662 if matches!(j.kind, JoinKind::Inner)
8663 && j.table.lateral_subquery.is_none()
8664 && q0.eq_ignore_ascii_case(
8665 j.table.alias.as_deref().unwrap_or(j.table.name.as_str()),
8666 )
8667 {
8668 peer_preds[i].push(sub);
8669 break;
8670 }
8671 }
8672 }
8673 }
8674 let mut from_owned;
8683 let mut from = from;
8684 if primary_preds.is_empty()
8691 && let Some(j0) = from.joins.first()
8692 && matches!(j0.kind, JoinKind::Inner)
8693 && j0.table.lateral_subquery.is_none()
8694 && !peer_preds[0].is_empty()
8695 {
8696 let peer_alias = j0.table.alias.as_deref().unwrap_or(j0.table.name.as_str());
8697 let on_safe = j0.on.as_ref().is_some_and(|on| {
8698 let mut quals: Vec<&str> = Vec::new();
8699 let mut all_q = true;
8700 collect_column_qualifiers(on, &mut quals, &mut all_q);
8701 all_q
8702 && quals.iter().all(|q| {
8703 q.eq_ignore_ascii_case(&primary_alias) || q.eq_ignore_ascii_case(peer_alias)
8704 })
8705 });
8706 if on_safe {
8707 from_owned = from.clone();
8708 core::mem::swap(&mut from_owned.primary, &mut from_owned.joins[0].table);
8709 primary_preds = peer_preds[0].drain(..).collect();
8710 from = &from_owned;
8711 }
8712 }
8713 let primary_alias = from
8714 .primary
8715 .alias
8716 .as_deref()
8717 .unwrap_or(from.primary.name.as_str())
8718 .to_string();
8719 let (mut primary_rows, primary_cols) =
8720 self.materialise_table_ref_filtered(&from.primary, &primary_preds)?;
8721 if let Some(needed) = needed {
8722 Self::null_out_unreferenced(&mut primary_rows, &primary_cols, &primary_alias, needed);
8723 }
8724 #[allow(clippy::type_complexity)]
8731 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8732 for j in &from.joins {
8733 let a = j
8734 .table
8735 .alias
8736 .as_deref()
8737 .unwrap_or(j.table.name.as_str())
8738 .to_string();
8739 if let Some(inner_box) = &j.table.lateral_subquery {
8740 let schema = self.lateral_probe_schema(inner_box)?;
8745 joined.push(JoinedPeer {
8746 eager_rows: None,
8747 cols: schema,
8748 alias: a,
8749 kind: j.kind,
8750 on: j.on.as_ref(),
8751 lateral: Some(inner_box.as_ref()),
8752 join_table: None,
8753 });
8754 } else {
8755 let pidx = from
8756 .joins
8757 .iter()
8758 .position(|jj| core::ptr::eq(jj, j))
8759 .unwrap_or(0);
8760 let plain = j.table.unnest_expr.is_none() && j.table.as_of_segment.is_none();
8764 if plain
8765 && peer_preds[pidx].is_empty()
8766 && let Some(t) = self.active_catalog().get(&j.table.name)
8767 {
8768 joined.push(JoinedPeer {
8769 eager_rows: None,
8770 cols: t.schema().columns.clone(),
8771 alias: a,
8772 kind: j.kind,
8773 on: j.on.as_ref(),
8774 lateral: None,
8775 join_table: Some(j.table.name.clone()),
8776 });
8777 continue;
8778 }
8779 let (mut rows, cols) =
8780 self.materialise_table_ref_filtered(&j.table, &peer_preds[pidx])?;
8781 if let Some(needed) = needed {
8782 Self::null_out_unreferenced(&mut rows, &cols, &a, needed);
8783 }
8784 joined.push(JoinedPeer {
8785 eager_rows: Some(rows),
8786 cols,
8787 alias: a,
8788 kind: j.kind,
8789 on: j.on.as_ref(),
8790 lateral: None,
8791 join_table: Some(j.table.name.clone()),
8792 });
8793 }
8794 }
8795 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8796 for col in &primary_cols {
8797 combined_schema.push(ColumnSchema::new(
8798 alloc::format!("{primary_alias}.{}", col.name),
8799 col.ty,
8800 col.nullable,
8801 ));
8802 }
8803 for peer in &joined {
8804 for col in &peer.cols {
8805 combined_schema.push(ColumnSchema::new(
8806 alloc::format!("{}.{}", peer.alias, col.name),
8807 col.ty,
8808 col.nullable,
8809 ));
8810 }
8811 }
8812 let ctx = EvalContext::new(&combined_schema, None);
8813 const MAX_JOIN_INTERMEDIATE_ROWS: usize = 4_000_000;
8818 let mut working: Vec<Row> = primary_rows;
8819 let mut consumed_cols = primary_cols.len();
8822 for peer in &joined {
8823 if working.len() > MAX_JOIN_INTERMEDIATE_ROWS {
8824 return Err(EngineError::Unsupported(alloc::format!(
8825 "join intermediate result exceeds {MAX_JOIN_INTERMEDIATE_ROWS} rows ({} so far) - add join predicates",
8826 working.len()
8827 )));
8828 }
8829 let right_arity = peer.cols.len();
8830 let mut next: Vec<Row> = Vec::new();
8831 let mut eq_pairs: Vec<(usize, usize)> = Vec::new(); let mut residual: Vec<&Expr> = Vec::new();
8842 if let (Some(on_expr), None) = (peer.on, peer.lateral) {
8843 for sub in reorder::split_and_conjunctions(on_expr) {
8844 let mut matched = None;
8845 if let Expr::Binary {
8846 lhs,
8847 op: spg_sql::ast::BinOp::Eq,
8848 rhs,
8849 } = sub
8850 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
8851 {
8852 let left_slice = &combined_schema[..consumed_cols];
8853 if let (Some(l), Some(r)) = (
8854 Self::composite_col_pos(left_slice, a),
8855 Self::peer_col_pos(&peer.alias, &peer.cols, b),
8856 ) {
8857 matched = Some((l, r));
8858 } else if let (Some(l), Some(r)) = (
8859 Self::composite_col_pos(left_slice, b),
8860 Self::peer_col_pos(&peer.alias, &peer.cols, a),
8861 ) {
8862 matched = Some((l, r));
8863 }
8864 }
8865 match matched {
8866 Some(pair) => eq_pairs.push(pair),
8867 None => residual.push(sub),
8868 }
8869 }
8870 }
8871 const INL_MAX_LEFT: usize = 1024;
8877 if let Some(tname) = &peer.join_table
8878 && peer.eager_rows.is_none()
8879 && !eq_pairs.is_empty()
8880 && working.len() <= INL_MAX_LEFT
8881 && let Some(table) = self.active_catalog().get(tname)
8882 && let Some(idx) = peer
8883 .cols
8884 .iter()
8885 .position(|c| c.name == peer.cols[eq_pairs[0].1].name)
8886 .and_then(|pos| table.index_on(pos))
8887 {
8888 let (lpos0, _) = eq_pairs[0];
8889 for left in &working {
8890 cancel.check()?;
8891 let mut left_matched = false;
8892 let key_v = left.values.get(lpos0).cloned().unwrap_or(Value::Null);
8893 if !matches!(key_v, Value::Null)
8894 && let Some(key) = spg_storage::IndexKey::from_value(&key_v)
8895 {
8896 for loc in idx.lookup_eq(&key) {
8897 let right = match *loc {
8898 spg_storage::RowLocator::Hot(i) => match table.rows().get(i) {
8899 Some(r) => r,
8900 None => continue,
8901 },
8902 spg_storage::RowLocator::Cold { .. } => continue,
8903 };
8904 let mut ok = true;
8907 for (lp, rp) in eq_pairs.iter().skip(1) {
8908 let lv = left.values.get(*lp);
8909 let rv = right.values.get(*rp);
8910 let eq = match (lv, rv) {
8911 (Some(a), Some(b)) => {
8912 !matches!(a, Value::Null)
8913 && !matches!(b, Value::Null)
8914 && value_cmp(a, b) == core::cmp::Ordering::Equal
8915 }
8916 _ => false,
8917 };
8918 if !eq {
8919 ok = false;
8920 break;
8921 }
8922 }
8923 if !ok {
8924 continue;
8925 }
8926 let mut combined_vals = left.values.clone();
8927 combined_vals.extend(right.values.iter().cloned());
8928 let combined = Row::new(combined_vals);
8929 let keep = if residual.is_empty() {
8930 true
8931 } else {
8932 let mut k = true;
8933 for r in &residual {
8934 let cond = self.eval_expr_with_correlated(
8935 r, &combined, &ctx, cancel, None,
8936 )?;
8937 if !matches!(cond, Value::Bool(true)) {
8938 k = false;
8939 break;
8940 }
8941 }
8942 k
8943 };
8944 if keep {
8945 next.push(combined);
8946 left_matched = true;
8947 }
8948 }
8949 }
8950 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8951 let mut combined_vals = left.values.clone();
8952 for _ in 0..right_arity {
8953 combined_vals.push(Value::Null);
8954 }
8955 next.push(Row::new(combined_vals));
8956 }
8957 }
8958 working = next;
8959 consumed_cols += right_arity;
8960 continue;
8961 }
8962 let lazy_rows: Option<Vec<Row>> = if peer.eager_rows.is_none() && peer.lateral.is_none()
8965 {
8966 let tname = peer.join_table.as_deref().unwrap_or("");
8967 let mut rows: Vec<Row> = self
8968 .active_catalog()
8969 .get(tname)
8970 .map(|t| t.rows().iter().cloned().collect())
8971 .unwrap_or_default();
8972 if let Some(needed) = needed {
8973 Self::null_out_unreferenced(&mut rows, &peer.cols, &peer.alias, needed);
8974 }
8975 Some(rows)
8976 } else {
8977 None
8978 };
8979 let eager_view: Option<&Vec<Row>> = peer.eager_rows.as_ref().or(lazy_rows.as_ref());
8980 if !eq_pairs.is_empty() && peer.lateral.is_none() {
8981 let rights = eager_view.expect("non-lateral peer eager");
8982 let mut table: hashbrown::HashMap<String, Vec<usize>> =
8986 hashbrown::HashMap::with_capacity(rights.len());
8987 let mut keybuf: Vec<Value> = Vec::with_capacity(eq_pairs.len());
8988 'build: for (ri, right) in rights.iter().enumerate() {
8989 keybuf.clear();
8990 for (_, rpos) in &eq_pairs {
8991 let v = right.values.get(*rpos).cloned().unwrap_or(Value::Null);
8992 if matches!(v, Value::Null) {
8993 continue 'build;
8994 }
8995 keybuf.push(v);
8996 }
8997 table
8998 .entry(aggregate::encode_key(&keybuf))
8999 .or_default()
9000 .push(ri);
9001 }
9002 for left in &working {
9003 cancel.check()?;
9004 let mut left_matched = false;
9005 keybuf.clear();
9006 let mut left_has_null = false;
9007 for (lpos, _) in &eq_pairs {
9008 let v = left.values.get(*lpos).cloned().unwrap_or(Value::Null);
9009 if matches!(v, Value::Null) {
9010 left_has_null = true;
9011 break;
9012 }
9013 keybuf.push(v);
9014 }
9015 if !left_has_null
9016 && let Some(cands) = table.get(&aggregate::encode_key(&keybuf))
9017 {
9018 for &ri in cands {
9019 let right = &rights[ri];
9020 let mut combined_vals = left.values.clone();
9021 combined_vals.extend(right.values.iter().cloned());
9022 let combined = Row::new(combined_vals);
9023 let keep = if residual.is_empty() {
9024 true
9025 } else {
9026 let mut ok = true;
9027 for r in &residual {
9028 let cond = self.eval_expr_with_correlated(
9029 r, &combined, &ctx, cancel, None,
9030 )?;
9031 if !matches!(cond, Value::Bool(true)) {
9032 ok = false;
9033 break;
9034 }
9035 }
9036 ok
9037 };
9038 if keep {
9039 next.push(combined);
9040 left_matched = true;
9041 }
9042 }
9043 }
9044 if !left_matched && matches!(peer.kind, JoinKind::Left) {
9045 let mut combined_vals = left.values.clone();
9046 for _ in 0..right_arity {
9047 combined_vals.push(Value::Null);
9048 }
9049 next.push(Row::new(combined_vals));
9050 }
9051 }
9052 working = next;
9053 consumed_cols += right_arity;
9054 debug_assert!(consumed_cols <= combined_schema.len());
9055 continue;
9056 }
9057 for left in &working {
9059 cancel.check()?;
9060 let mut left_matched = false;
9061 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
9062 Some(inner) => {
9063 let outer_schema = &combined_schema[..consumed_cols];
9067 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
9068 alloc::borrow::Cow::Owned(rows)
9069 }
9070 None => {
9071 let r = eager_view.expect("non-lateral peer eager");
9072 alloc::borrow::Cow::Borrowed(r.as_slice())
9073 }
9074 };
9075 for right in per_left_rrows.as_ref() {
9076 let mut combined_vals = left.values.clone();
9077 combined_vals.extend(right.values.iter().cloned());
9078 let combined = Row::new(combined_vals);
9079 let keep = if let Some(on_expr) = peer.on {
9080 let cond =
9083 self.eval_expr_with_correlated(on_expr, &combined, &ctx, cancel, None)?;
9084 matches!(cond, Value::Bool(true))
9085 } else {
9086 true
9087 };
9088 if keep {
9089 next.push(combined);
9090 left_matched = true;
9091 }
9092 }
9093 if !left_matched && matches!(peer.kind, JoinKind::Left) {
9094 let mut combined_vals = left.values.clone();
9095 for _ in 0..right_arity {
9096 combined_vals.push(Value::Null);
9097 }
9098 next.push(Row::new(combined_vals));
9099 }
9100 }
9101 working = next;
9102 if working.len() > MAX_JOIN_INTERMEDIATE_ROWS {
9103 return Err(EngineError::Unsupported(alloc::format!(
9104 "join intermediate result exceeds {MAX_JOIN_INTERMEDIATE_ROWS} rows ({} so far) - add join predicates",
9105 working.len()
9106 )));
9107 }
9108 consumed_cols += right_arity;
9109 debug_assert!(consumed_cols <= combined_schema.len());
9110 }
9111 let mut filtered: Vec<Row> = Vec::new();
9112 let mut memo = memoize::MemoizeCache::default();
9118 for row in working {
9119 if let Some(where_expr) = where_ {
9120 let cond = self.eval_expr_with_correlated(
9121 where_expr,
9122 &row,
9123 &ctx,
9124 cancel,
9125 Some(&mut memo),
9126 )?;
9127 if !matches!(cond, Value::Bool(true)) {
9128 continue;
9129 }
9130 }
9131 filtered.push(row);
9132 }
9133 Ok((combined_schema, filtered))
9134 }
9135
9136 fn lateral_probe_schema(
9142 &self,
9143 inner: &SelectStatement,
9144 ) -> Result<Vec<ColumnSchema>, EngineError> {
9145 match self.execute_readonly_select_for_lateral_probe(inner) {
9155 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
9156 _ => {
9162 let mut out: Vec<ColumnSchema> = Vec::new();
9163 for (i, item) in inner.items.iter().enumerate() {
9164 let name = match item {
9165 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
9166 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
9167 SelectItem::Wildcard => alloc::format!("col{i}"),
9168 };
9169 out.push(ColumnSchema::new(name, DataType::Text, true));
9170 }
9171 Ok(out)
9172 }
9173 }
9174 }
9175
9176 fn execute_readonly_select_for_lateral_probe(
9182 &self,
9183 inner: &SelectStatement,
9184 ) -> Result<QueryResult, EngineError> {
9185 self.exec_bare_select_cancel(inner, CancelToken::none())
9186 }
9187
9188 fn materialise_lateral_for_outer(
9194 &self,
9195 inner: &SelectStatement,
9196 outer_schema: &[ColumnSchema],
9197 outer_row: &Row,
9198 ) -> Result<Vec<Row>, EngineError> {
9199 let mut substituted = inner.clone();
9200 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
9201 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
9202 match result {
9203 QueryResult::Rows { rows, .. } => Ok(rows),
9204 _ => Err(EngineError::Unsupported(
9205 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
9206 )),
9207 }
9208 }
9209
9210 fn exec_joined_select(
9211 &self,
9212 stmt: &SelectStatement,
9213 from: &FromClause,
9214 cancel: CancelToken<'_>,
9215 ) -> Result<QueryResult, EngineError> {
9216 let (combined_schema, filtered) = {
9224 let mut needed = alloc::collections::BTreeSet::new();
9225 let prunable = collect_qualified_refs(stmt, &mut needed).is_some();
9226 self.build_joined_filtered_rows(
9227 from,
9228 stmt.where_.as_ref(),
9229 cancel,
9230 if prunable { Some(&needed) } else { None },
9231 )?
9232 };
9233 let ctx = EvalContext::new(&combined_schema, None);
9234 if aggregate::uses_aggregate(stmt) {
9237 let refs: Vec<&Row> = filtered.iter().collect();
9238 let agg_memo = core::cell::RefCell::new(memoize::MemoizeCache::default());
9242 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
9243 self.eval_expr_with_correlated(e, r, c, cancel, Some(&mut agg_memo.borrow_mut()))
9244 .map_err(|err| match err {
9245 EngineError::Eval(ev) => ev,
9246 other => eval::EvalError::TypeMismatch {
9247 detail: alloc::format!("{other}"),
9248 },
9249 })
9250 };
9251 let mut agg =
9252 aggregate::run(stmt, &refs, &combined_schema, None, Some(&agg_correlated))?;
9253 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
9254 return Ok(QueryResult::Rows {
9255 columns: agg.columns,
9256 rows: agg.rows,
9257 });
9258 }
9259
9260 let projection = build_projection(&stmt.items, &combined_schema, "")?;
9261 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
9262 let mut proj_memo = memoize::MemoizeCache::default();
9263 for row in &filtered {
9264 let mut values = Vec::with_capacity(projection.len());
9265 for p in &projection {
9266 values.push(self.eval_expr_with_correlated(
9269 &p.expr,
9270 row,
9271 &ctx,
9272 cancel,
9273 Some(&mut proj_memo),
9274 )?);
9275 }
9276 let order_keys = if stmt.order_by.is_empty() {
9277 Vec::new()
9278 } else {
9279 build_order_keys(&stmt.order_by, row, &ctx)?
9280 };
9281 tagged.push((order_keys, Row::new(values)));
9282 }
9283 if !stmt.order_by.is_empty() {
9284 let keep = if stmt.distinct {
9285 None
9286 } else {
9287 stmt.limit_literal()
9288 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
9289 };
9290 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9291 partial_sort_tagged(&mut tagged, keep, &descs);
9292 }
9293 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9294 if stmt.distinct {
9295 output_rows = dedup_rows(output_rows);
9296 }
9297 apply_offset_and_limit(
9298 &mut output_rows,
9299 stmt.offset_literal(),
9300 stmt.limit_literal(),
9301 );
9302 let columns: Vec<ColumnSchema> = projection
9303 .into_iter()
9304 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9305 .collect();
9306 Ok(QueryResult::Rows {
9307 columns,
9308 rows: output_rows,
9309 })
9310 }
9311}
9312
9313#[derive(Debug, Clone)]
9316struct ProjectedItem {
9317 expr: Expr,
9318 output_name: String,
9319 ty: DataType,
9320 nullable: bool,
9321}
9322
9323fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
9329 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
9330 for r in rows {
9331 if !out.iter().any(|seen| seen == &r) {
9332 out.push(r);
9333 }
9334 }
9335 out
9336}
9337
9338fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
9342 match v {
9343 Value::Null => Ok(f64::INFINITY),
9344 Value::SmallInt(n) => Ok(f64::from(*n)),
9345 Value::Int(n) => Ok(f64::from(*n)),
9346 Value::Date(d) => Ok(f64::from(*d)),
9347 #[allow(clippy::cast_precision_loss)]
9348 Value::Timestamp(t) => Ok(*t as f64),
9349 #[allow(clippy::cast_precision_loss)]
9352 Value::Time(us) => Ok(*us as f64),
9353 Value::Year(y) => Ok(f64::from(*y)),
9357 #[allow(clippy::cast_precision_loss)]
9362 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
9363 #[allow(clippy::cast_precision_loss)]
9365 Value::Money(c) => Ok(*c as f64),
9366 Value::Range { .. } => Err(EngineError::Unsupported(
9369 "ORDER BY of a range value is not supported in v7.17.0".into(),
9370 )),
9371 Value::Hstore(_) => Err(EngineError::Unsupported(
9373 "ORDER BY of a hstore value is not supported".into(),
9374 )),
9375 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
9377 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
9378 ),
9379 #[allow(clippy::cast_precision_loss)]
9380 Value::Numeric { scaled, scale } => {
9381 let mut divisor = 1.0_f64;
9387 for _ in 0..*scale {
9388 divisor *= 10.0;
9389 }
9390 Ok((*scaled as f64) / divisor)
9391 }
9392 #[allow(clippy::cast_precision_loss)]
9393 Value::BigInt(n) => Ok(*n as f64),
9394 Value::Float(x) => Ok(*x),
9395 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
9396 Value::Text(s) => {
9397 let mut key: u64 = 0;
9401 for &b in s.as_bytes().iter().take(8) {
9402 key = (key << 8) | u64::from(b);
9403 }
9404 #[allow(clippy::cast_precision_loss)]
9405 Ok(key as f64)
9406 }
9407 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
9408 Err(EngineError::Unsupported(
9409 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
9410 ))
9411 }
9412 Value::Interval { .. } => Err(EngineError::Unsupported(
9413 "ORDER BY of an INTERVAL is not supported in v2.11 \
9414 (months vs micros has no single canonical ordering)"
9415 .into(),
9416 )),
9417 Value::Json(_) => Err(EngineError::Unsupported(
9418 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
9419 )),
9420 _ => Err(EngineError::Unsupported(
9424 "ORDER BY of this value type is not supported".into(),
9425 )),
9426 }
9427}
9428
9429fn try_nsw_knn(
9443 stmt: &SelectStatement,
9444 table: &Table,
9445 schema_cols: &[ColumnSchema],
9446 table_alias: &str,
9447) -> Option<Vec<usize>> {
9448 if stmt.distinct {
9449 return None;
9450 }
9451 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
9452 if limit == 0 {
9453 return None;
9454 }
9455 if stmt.order_by.len() != 1 {
9459 return None;
9460 }
9461 let order = &stmt.order_by[0];
9462 if order.desc {
9466 return None;
9467 }
9468 let Expr::Binary { lhs, op, rhs } = &order.expr else {
9469 return None;
9470 };
9471 let metric = match op {
9472 BinOp::L2Distance => spg_storage::NswMetric::L2,
9473 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
9474 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
9475 _ => return None,
9476 };
9477 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
9479 (lhs.as_ref(), rhs.as_ref())
9480 else {
9481 return None;
9482 };
9483 if let Some(q) = &col.qualifier
9484 && q != table_alias
9485 {
9486 return None;
9487 }
9488 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
9489 let query = literal_to_vector(literal)?;
9490 let idx = spg_storage::nsw_index_on(table, col_pos)?;
9491 if let Some(where_expr) = &stmt.where_ {
9492 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
9496 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
9497 let ctx = EvalContext::new(schema_cols, Some(table_alias));
9498 let mut kept: Vec<usize> = Vec::with_capacity(limit);
9499 for i in candidates {
9500 let row = &table.rows()[i];
9501 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
9502 if matches!(cond, Value::Bool(true)) {
9503 kept.push(i);
9504 if kept.len() >= limit {
9505 break;
9506 }
9507 }
9508 }
9509 Some(kept)
9510 } else {
9511 Some(spg_storage::nsw_query(
9512 table, &idx.name, &query, limit, metric,
9513 ))
9514 }
9515}
9516
9517const NSW_OVER_FETCH_FLOOR: usize = 32;
9521
9522fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
9525 match e {
9526 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
9527 Expr::Cast { expr, .. } => literal_to_vector(expr),
9528 _ => None,
9529 }
9530}
9531
9532fn materialise_in_order(
9536 stmt: &SelectStatement,
9537 table: &Table,
9538 schema_cols: &[ColumnSchema],
9539 table_alias: &str,
9540 ordered_rows: &[usize],
9541) -> Result<QueryResult, EngineError> {
9542 let ctx = EvalContext::new(schema_cols, Some(table_alias));
9543 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
9544 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
9545 for &i in ordered_rows {
9546 let row = &table.rows()[i];
9547 let mut values = Vec::with_capacity(projection.len());
9548 for p in &projection {
9549 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
9550 }
9551 output_rows.push(Row::new(values));
9552 }
9553 apply_offset_and_limit(
9554 &mut output_rows,
9555 stmt.offset_literal(),
9556 stmt.limit_literal(),
9557 );
9558 let columns: Vec<ColumnSchema> = projection
9559 .into_iter()
9560 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9561 .collect();
9562 Ok(QueryResult::Rows {
9563 columns,
9564 rows: output_rows,
9565 })
9566}
9567
9568fn try_index_seek_positions(
9581 where_expr: &Expr,
9582 schema_cols: &[ColumnSchema],
9583 table: &Table,
9584 table_alias: &str,
9585) -> Option<Vec<usize>> {
9586 if let Expr::Binary {
9587 lhs,
9588 op: BinOp::And,
9589 rhs,
9590 } = where_expr
9591 {
9592 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
9593 return Some(p);
9594 }
9595 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
9596 }
9597 let Expr::Binary {
9598 lhs,
9599 op: BinOp::Eq,
9600 rhs,
9601 } = where_expr
9602 else {
9603 return None;
9604 };
9605 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9606 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9607 let idx = table.index_on(col_pos)?;
9608 let key = IndexKey::from_value(&value)?;
9609 let locators = idx.lookup_eq(&key);
9610 let mut out = Vec::with_capacity(locators.len());
9611 for loc in locators {
9612 match *loc {
9613 spg_storage::RowLocator::Hot(i) => out.push(i),
9614 spg_storage::RowLocator::Cold { .. } => return None,
9615 }
9616 }
9617 Some(out)
9618}
9619
9620fn try_index_seek<'a>(
9621 where_expr: &Expr,
9622 schema_cols: &[ColumnSchema],
9623 catalog: &'a Catalog,
9624 table: &'a Table,
9625 table_alias: &str,
9626) -> Option<Vec<Cow<'a, Row>>> {
9627 if let Expr::Binary {
9634 lhs,
9635 op: BinOp::And,
9636 rhs,
9637 } = where_expr
9638 {
9639 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
9642 return Some(rows);
9643 }
9644 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
9645 }
9646 let Expr::Binary {
9647 lhs,
9648 op: BinOp::Eq,
9649 rhs,
9650 } = where_expr
9651 else {
9652 return None;
9653 };
9654 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9655 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9656 let idx = table.index_on(col_pos)?;
9657 let key = IndexKey::from_value(&value)?;
9658 let locators = idx.lookup_eq(&key);
9659 let table_name = table.schema().name.as_str();
9660 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
9668 for loc in locators {
9669 match *loc {
9670 spg_storage::RowLocator::Hot(i) => {
9671 if let Some(row) = table.rows().get(i) {
9672 out.push(Cow::Borrowed(row));
9673 }
9674 }
9675 spg_storage::RowLocator::Cold { segment_id, .. } => {
9676 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
9677 out.push(Cow::Owned(row));
9678 }
9679 }
9680 }
9681 }
9682 Some(out)
9683}
9684
9685fn try_gin_seek<'a>(
9704 where_expr: &Expr,
9705 schema_cols: &[ColumnSchema],
9706 catalog: &'a Catalog,
9707 table: &'a Table,
9708 table_alias: &str,
9709 ctx: &eval::EvalContext<'_>,
9710) -> Option<Vec<Cow<'a, Row>>> {
9711 if let Expr::Binary {
9712 lhs,
9713 op: BinOp::And,
9714 rhs,
9715 } = where_expr
9716 {
9717 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
9718 return Some(rows);
9719 }
9720 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
9721 }
9722 if let Expr::Binary {
9731 lhs,
9732 op: BinOp::Or,
9733 rhs,
9734 } = where_expr
9735 {
9736 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
9737 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
9738 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
9739 out.extend(left);
9740 out.extend(right);
9741 return Some(out);
9742 }
9743 let Expr::Binary {
9744 lhs,
9745 op: BinOp::TsMatch,
9746 rhs,
9747 } = where_expr
9748 else {
9749 return None;
9750 };
9751 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
9756 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
9757 let idx = table
9764 .indices()
9765 .iter()
9766 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
9767 let candidates = gin_query_candidates(idx, &query)?;
9768 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
9770 for loc in candidates {
9771 match loc {
9772 spg_storage::RowLocator::Hot(i) => {
9773 if let Some(row) = table.rows().get(i) {
9774 out.push(Cow::Borrowed(row));
9775 }
9776 }
9777 spg_storage::RowLocator::Cold { .. } => {}
9784 }
9785 }
9786 Some(out)
9787}
9788
9789fn try_trgm_seek<'a>(
9805 where_expr: &Expr,
9806 schema_cols: &[ColumnSchema],
9807 table: &'a Table,
9808 table_alias: &str,
9809) -> Option<Vec<Cow<'a, Row>>> {
9810 if let Expr::Binary {
9811 lhs,
9812 op: BinOp::And,
9813 rhs,
9814 } = where_expr
9815 {
9816 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9817 return Some(rows);
9818 }
9819 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9820 }
9821 let Expr::Like { expr, pattern, .. } = where_expr else {
9827 return None;
9828 };
9829 let Expr::Column(c) = expr.as_ref() else {
9831 return None;
9832 };
9833 if let Some(q) = &c.qualifier
9834 && q != table_alias
9835 {
9836 return None;
9837 }
9838 let col_pos = schema_cols
9839 .iter()
9840 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9841 let idx = table
9843 .indices()
9844 .iter()
9845 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9846 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9850 return None;
9851 };
9852 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9853 let mut iter = trigrams.iter();
9856 let first = iter.next()?;
9857 let mut acc: Vec<spg_storage::RowLocator> = {
9858 let mut v = idx.gin_trgm_lookup(first).to_vec();
9859 v.sort_by_key(locator_sort_key);
9860 v.dedup_by_key(|l| locator_sort_key(l));
9861 v
9862 };
9863 for tri in iter {
9864 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9865 next.sort_by_key(locator_sort_key);
9866 next.dedup_by_key(|l| locator_sort_key(l));
9867 let mut merged: Vec<spg_storage::RowLocator> =
9869 Vec::with_capacity(acc.len().min(next.len()));
9870 let (mut i, mut j) = (0usize, 0usize);
9871 while i < acc.len() && j < next.len() {
9872 let lk = locator_sort_key(&acc[i]);
9873 let rk = locator_sort_key(&next[j]);
9874 match lk.cmp(&rk) {
9875 core::cmp::Ordering::Less => i += 1,
9876 core::cmp::Ordering::Greater => j += 1,
9877 core::cmp::Ordering::Equal => {
9878 merged.push(acc[i]);
9879 i += 1;
9880 j += 1;
9881 }
9882 }
9883 }
9884 acc = merged;
9885 if acc.is_empty() {
9886 break;
9887 }
9888 }
9889 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9890 for loc in acc {
9891 if let spg_storage::RowLocator::Hot(i) = loc
9892 && let Some(row) = table.rows().get(i)
9893 {
9894 out.push(Cow::Borrowed(row));
9895 }
9896 }
9898 Some(out)
9899}
9900
9901fn resolve_gin_col_query(
9907 col_side: &Expr,
9908 query_side: &Expr,
9909 schema_cols: &[ColumnSchema],
9910 table_alias: &str,
9911 ctx: &eval::EvalContext<'_>,
9912) -> Option<(usize, spg_storage::TsQueryAst)> {
9913 let column = match col_side {
9918 Expr::Column(c) => c,
9919 Expr::FunctionCall { name, args }
9920 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9921 {
9922 if let Expr::Column(c) = args.last().unwrap() {
9926 c
9927 } else {
9928 return None;
9929 }
9930 }
9931 _ => return None,
9932 };
9933 let c = column;
9934 if let Some(q) = &c.qualifier
9935 && q != table_alias
9936 {
9937 return None;
9938 }
9939 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9940 let empty_row = Row::new(Vec::new());
9944 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9945 let Value::TsQuery(q) = v else { return None };
9946 Some((pos, q))
9947}
9948
9949fn gin_query_candidates(
9960 idx: &spg_storage::Index,
9961 query: &spg_storage::TsQueryAst,
9962) -> Option<Vec<spg_storage::RowLocator>> {
9963 use spg_storage::TsQueryAst;
9964 match query {
9965 TsQueryAst::Term { word, .. } => {
9966 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9967 v.sort_by_key(locator_sort_key);
9968 v.dedup_by_key(|l| locator_sort_key(l));
9969 Some(v)
9970 }
9971 TsQueryAst::And(l, r) => {
9972 let mut left = gin_query_candidates(idx, l)?;
9973 let mut right = gin_query_candidates(idx, r)?;
9974 left.sort_by_key(locator_sort_key);
9975 right.sort_by_key(locator_sort_key);
9976 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9978 let (mut i, mut j) = (0usize, 0usize);
9979 while i < left.len() && j < right.len() {
9980 let lk = locator_sort_key(&left[i]);
9981 let rk = locator_sort_key(&right[j]);
9982 match lk.cmp(&rk) {
9983 core::cmp::Ordering::Less => i += 1,
9984 core::cmp::Ordering::Greater => j += 1,
9985 core::cmp::Ordering::Equal => {
9986 out.push(left[i]);
9987 i += 1;
9988 j += 1;
9989 }
9990 }
9991 }
9992 Some(out)
9993 }
9994 TsQueryAst::Or(l, r) => {
9995 let mut out = gin_query_candidates(idx, l)?;
9996 out.extend(gin_query_candidates(idx, r)?);
9997 out.sort_by_key(locator_sort_key);
9998 out.dedup_by_key(|l| locator_sort_key(l));
9999 Some(out)
10000 }
10001 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
10006 }
10007}
10008
10009fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
10014 match *l {
10015 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
10016 spg_storage::RowLocator::Cold {
10017 segment_id,
10018 page_offset,
10019 } => (1, u64::from(segment_id), u64::from(page_offset)),
10020 }
10021}
10022
10023fn try_pk_predicate(
10035 where_expr: &Expr,
10036 schema_cols: &[ColumnSchema],
10037 table_alias: &str,
10038) -> Option<(usize, IndexKey)> {
10039 let Expr::Binary {
10040 lhs,
10041 op: BinOp::Eq,
10042 rhs,
10043 } = where_expr
10044 else {
10045 return None;
10046 };
10047 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
10048 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
10049 let key = IndexKey::from_value(&value)?;
10050 Some((col_pos, key))
10051}
10052
10053fn resolve_col_literal_pair(
10054 col_side: &Expr,
10055 lit_side: &Expr,
10056 schema_cols: &[ColumnSchema],
10057 table_alias: &str,
10058) -> Option<(usize, Value)> {
10059 let Expr::Column(c) = col_side else {
10060 return None;
10061 };
10062 if let Some(q) = &c.qualifier
10063 && q != table_alias
10064 {
10065 return None;
10066 }
10067 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
10068 let Expr::Literal(l) = lit_side else {
10069 return None;
10070 };
10071 let v = match l {
10072 Literal::Integer(n) => {
10073 if let Ok(small) = i32::try_from(*n) {
10074 Value::Int(small)
10075 } else {
10076 Value::BigInt(*n)
10077 }
10078 }
10079 Literal::Float(x) => Value::Float(*x),
10080 Literal::String(s) => Value::Text(s.clone()),
10081 Literal::Bool(b) => Value::Bool(*b),
10082 Literal::Null => Value::Null,
10083 Literal::Vector(_)
10086 | Literal::Interval { .. }
10087 | Literal::TextArray(_)
10088 | Literal::IntArray(_)
10089 | Literal::BigIntArray(_) => return None,
10090 };
10091 Some((pos, v))
10092}
10093
10094fn resolve_projection_column<'a>(
10099 c: &ColumnName,
10100 schema_cols: &'a [ColumnSchema],
10101 table_alias: &str,
10102) -> Result<&'a ColumnSchema, EngineError> {
10103 if let Some(q) = &c.qualifier {
10104 let composite = alloc::format!("{q}.{name}", name = c.name);
10105 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
10106 return Ok(s);
10107 }
10108 if q == table_alias
10111 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
10112 {
10113 return Ok(s);
10114 }
10115 let prefix = alloc::format!("{q}.");
10119 let qualifier_known =
10120 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
10121 if !qualifier_known {
10122 return Err(EngineError::Eval(EvalError::UnknownQualifier {
10123 qualifier: q.clone(),
10124 }));
10125 }
10126 return Err(EngineError::Eval(EvalError::ColumnNotFound {
10127 name: c.name.clone(),
10128 }));
10129 }
10130 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
10131 return Ok(s);
10132 }
10133 let suffix = alloc::format!(".{name}", name = c.name);
10134 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
10135 let first = matches.next();
10136 let extra = matches.next();
10137 match (first, extra) {
10138 (Some(s), None) => Ok(s),
10139 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
10140 detail: alloc::format!("ambiguous column reference: {}", c.name),
10141 })),
10142 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
10143 name: c.name.clone(),
10144 })),
10145 }
10146}
10147
10148fn build_projection(
10149 items: &[SelectItem],
10150 schema_cols: &[ColumnSchema],
10151 table_alias: &str,
10152) -> Result<Vec<ProjectedItem>, EngineError> {
10153 let mut out = Vec::new();
10154 for item in items {
10155 match item {
10156 SelectItem::Wildcard => {
10157 for col in schema_cols {
10158 out.push(ProjectedItem {
10159 expr: Expr::Column(ColumnName {
10160 qualifier: None,
10161 name: col.name.clone(),
10162 }),
10163 output_name: col.name.clone(),
10164 ty: col.ty,
10165 nullable: col.nullable,
10166 });
10167 }
10168 }
10169 SelectItem::Expr { expr, alias } => {
10170 if let Expr::Column(c) = expr {
10177 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
10178 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
10179 out.push(ProjectedItem {
10180 expr: expr.clone(),
10181 output_name,
10182 ty: sch.ty,
10183 nullable: sch.nullable,
10184 });
10185 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
10186 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
10187 out.push(ProjectedItem {
10188 expr: expr.clone(),
10189 output_name,
10190 ty: shape.ty,
10191 nullable: shape.nullable,
10192 });
10193 } else {
10194 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
10195 out.push(ProjectedItem {
10196 expr: expr.clone(),
10197 output_name,
10198 ty: DataType::Text,
10199 nullable: true,
10200 });
10201 }
10202 }
10203 }
10204 }
10205 Ok(out)
10206}
10207
10208fn numeric_from_integer(
10212 n: i128,
10213 precision: u8,
10214 scale: u8,
10215 col_name: &str,
10216) -> Result<Value, EngineError> {
10217 let factor = pow10_i128(scale);
10218 let scaled = n.checked_mul(factor).ok_or_else(|| {
10219 EngineError::Unsupported(alloc::format!(
10220 "integer overflow scaling value for column `{col_name}` to scale {scale}"
10221 ))
10222 })?;
10223 check_precision(scaled, precision, col_name)?;
10224 Ok(Value::Numeric { scaled, scale })
10225}
10226
10227#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
10230fn numeric_from_float(
10231 x: f64,
10232 precision: u8,
10233 scale: u8,
10234 col_name: &str,
10235) -> Result<Value, EngineError> {
10236 if !x.is_finite() {
10237 return Err(EngineError::Unsupported(alloc::format!(
10238 "cannot store non-finite float in NUMERIC column `{col_name}`"
10239 )));
10240 }
10241 let mut factor = 1.0_f64;
10242 for _ in 0..scale {
10243 factor *= 10.0;
10244 }
10245 let shifted = x * factor;
10250 let biased = if shifted >= 0.0 {
10251 shifted + 0.5
10252 } else {
10253 shifted - 0.5
10254 };
10255 if !(-1e38..=1e38).contains(&biased) {
10258 return Err(EngineError::Unsupported(alloc::format!(
10259 "value {x} overflows NUMERIC range for column `{col_name}`"
10260 )));
10261 }
10262 let scaled = biased as i128;
10263 check_precision(scaled, precision, col_name)?;
10264 Ok(Value::Numeric { scaled, scale })
10265}
10266
10267fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
10274 let s = s.trim();
10275 if s.is_empty() {
10276 return None;
10277 }
10278 let (negative, rest) = match s.as_bytes()[0] {
10279 b'-' => (true, &s[1..]),
10280 b'+' => (false, &s[1..]),
10281 _ => (false, s),
10282 };
10283 if rest.is_empty() {
10284 return None;
10285 }
10286 if rest.bytes().any(|b| b == b'e' || b == b'E') {
10290 return None;
10291 }
10292 let (int_part, frac_part) = match rest.find('.') {
10293 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
10294 None => (rest, ""),
10295 };
10296 if int_part.is_empty() && frac_part.is_empty() {
10297 return None;
10298 }
10299 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
10300 return None;
10301 }
10302 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
10303 return None;
10304 }
10305 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
10306 if scale_u32 > u32::from(u8::MAX) {
10307 return None;
10308 }
10309 let scale = scale_u32 as u8;
10310 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
10311 if negative {
10312 digits.push('-');
10313 }
10314 digits.push_str(int_part);
10315 digits.push_str(frac_part);
10316 let digits = if digits == "-" {
10318 return None;
10319 } else if digits.is_empty() {
10320 "0"
10321 } else {
10322 digits.as_str()
10323 };
10324 let mantissa: i128 = digits.parse().ok()?;
10325 Some((mantissa, scale))
10326}
10327
10328fn numeric_rescale(
10331 scaled: i128,
10332 src_scale: u8,
10333 precision: u8,
10334 dst_scale: u8,
10335 col_name: &str,
10336) -> Result<Value, EngineError> {
10337 let new_scaled = if dst_scale >= src_scale {
10338 let bump = pow10_i128(dst_scale - src_scale);
10339 scaled.checked_mul(bump).ok_or_else(|| {
10340 EngineError::Unsupported(alloc::format!(
10341 "overflow rescaling NUMERIC for column `{col_name}`"
10342 ))
10343 })?
10344 } else {
10345 let drop = pow10_i128(src_scale - dst_scale);
10346 let half = drop / 2;
10347 if scaled >= 0 {
10348 (scaled + half) / drop
10349 } else {
10350 (scaled - half) / drop
10351 }
10352 };
10353 check_precision(new_scaled, precision, col_name)?;
10354 Ok(Value::Numeric {
10355 scaled: new_scaled,
10356 scale: dst_scale,
10357 })
10358}
10359
10360const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
10363 if scale == 0 {
10364 return scaled;
10365 }
10366 let factor = pow10_i128_const(scale);
10367 scaled / factor
10368}
10369
10370fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
10374 if precision == 0 {
10375 return Ok(());
10376 }
10377 let limit = pow10_i128(precision);
10378 if scaled.unsigned_abs() >= limit.unsigned_abs() {
10379 return Err(EngineError::Unsupported(alloc::format!(
10380 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
10381 )));
10382 }
10383 Ok(())
10384}
10385
10386const fn pow10_i128_const(p: u8) -> i128 {
10387 let mut acc: i128 = 1;
10388 let mut i = 0;
10389 while i < p {
10390 acc *= 10;
10391 i += 1;
10392 }
10393 acc
10394}
10395
10396fn pow10_i128(p: u8) -> i128 {
10397 pow10_i128_const(p)
10398}
10399
10400impl Engine {
10415 #[allow(
10426 clippy::too_many_lines,
10427 clippy::type_complexity,
10428 clippy::needless_range_loop
10429 )] fn exec_select_with_window(
10431 &self,
10432 stmt: &SelectStatement,
10433 cancel: CancelToken<'_>,
10434 ) -> Result<QueryResult, EngineError> {
10435 let from = stmt.from.as_ref().ok_or_else(|| {
10436 EngineError::Unsupported("window functions require a FROM clause".into())
10437 })?;
10438 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
10447 let filtered: Vec<Row>;
10448 if from.joins.is_empty() {
10449 let primary = &from.primary;
10450 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
10451 StorageError::TableNotFound {
10452 name: primary.name.clone(),
10453 }
10454 })?;
10455 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
10456 schema_cols_owned = table.schema().columns.clone();
10457 alias_opt = Some(alias);
10458 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
10463 let mut owned: Vec<Row> = Vec::new();
10464 for (i, row) in table.rows().iter().enumerate() {
10465 if i.is_multiple_of(256) {
10466 cancel.check()?;
10467 }
10468 if let Some(w) = &stmt.where_ {
10469 let cond = eval::eval_expr(w, row, &ctx)?;
10470 if !matches!(cond, Value::Bool(true)) {
10471 continue;
10472 }
10473 }
10474 owned.push(row.clone());
10475 }
10476 filtered = owned;
10477 } else {
10478 let (combined_schema, rows) =
10479 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel, None)?;
10480 schema_cols_owned = combined_schema;
10481 alias_opt = None;
10482 filtered = rows;
10483 }
10484 let schema_cols = &schema_cols_owned;
10485 let ctx = self.ev_ctx(schema_cols, alias_opt);
10486 let alias = alias_opt.unwrap_or("");
10487 let n_rows = filtered.len();
10488 let filtered_refs: Vec<&Row> = filtered.iter().collect();
10492
10493 let mut window_nodes: Vec<Expr> = Vec::new();
10495 for item in &stmt.items {
10496 if let SelectItem::Expr { expr, .. } = item {
10497 collect_window_nodes(expr, &mut window_nodes);
10498 }
10499 }
10500
10501 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
10504 for wnode in &window_nodes {
10505 let Expr::WindowFunction {
10506 name,
10507 args,
10508 partition_by,
10509 order_by,
10510 frame,
10511 null_treatment,
10512 } = wnode
10513 else {
10514 unreachable!("collect_window_nodes pushes only WindowFunction");
10515 };
10516 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)> =
10518 Vec::with_capacity(n_rows);
10519 for (i, row) in filtered.iter().enumerate() {
10520 let pkey: Vec<Value> = partition_by
10521 .iter()
10522 .map(|p| eval::eval_expr(p, row, &ctx))
10523 .collect::<Result<_, _>>()?;
10524 let okey: Vec<(Value, bool, Option<bool>)> = order_by
10525 .iter()
10526 .map(|(e, desc, nf)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc, *nf)))
10527 .collect::<Result<_, _>>()?;
10528 indexed.push((pkey, okey, i));
10529 }
10530 indexed.sort_by(|a, b| {
10533 let p_cmp = partition_key_cmp(&a.0, &b.0);
10534 if p_cmp != core::cmp::Ordering::Equal {
10535 return p_cmp;
10536 }
10537 order_key_cmp(&a.1, &b.1)
10538 });
10539 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
10541 let mut p_start = 0;
10542 while p_start < indexed.len() {
10543 let mut p_end = p_start + 1;
10544 while p_end < indexed.len()
10545 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
10546 == core::cmp::Ordering::Equal
10547 {
10548 p_end += 1;
10549 }
10550 compute_window_partition(
10552 name,
10553 args,
10554 !order_by.is_empty(),
10555 frame.as_ref(),
10556 *null_treatment,
10557 &indexed[p_start..p_end],
10558 &filtered_refs,
10559 &ctx,
10560 &mut out_vals,
10561 )?;
10562 p_start = p_end;
10563 }
10564 win_vals.push(out_vals);
10565 }
10566
10567 let mut ext_cols = schema_cols.clone();
10569 for i in 0..window_nodes.len() {
10570 ext_cols.push(ColumnSchema::new(
10571 alloc::format!("__win_{i}"),
10572 DataType::Text, true,
10574 ));
10575 }
10576 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
10578 for i in 0..n_rows {
10579 let mut values = filtered[i].values.clone();
10580 for w in 0..window_nodes.len() {
10581 values.push(win_vals[w][i].clone());
10582 }
10583 ext_rows.push(Row::new(values));
10584 }
10585 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
10587 for item in &stmt.items {
10588 let new_item = match item {
10589 SelectItem::Wildcard => SelectItem::Wildcard,
10590 SelectItem::Expr { expr, alias } => {
10591 let mut e = expr.clone();
10592 rewrite_window_to_columns(&mut e, &window_nodes);
10593 SelectItem::Expr {
10594 expr: e,
10595 alias: alias.clone(),
10596 }
10597 }
10598 };
10599 rewritten_items.push(new_item);
10600 }
10601
10602 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
10608 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
10609 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
10610 for (i, row) in ext_rows.iter().enumerate() {
10611 if i.is_multiple_of(256) {
10612 cancel.check()?;
10613 }
10614 let mut values = Vec::with_capacity(projection.len());
10615 for p in &projection {
10616 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
10617 }
10618 let order_keys = if stmt.order_by.is_empty() {
10619 Vec::new()
10620 } else {
10621 let mut keys = Vec::with_capacity(stmt.order_by.len());
10622 for o in &stmt.order_by {
10623 let mut e = o.expr.clone();
10624 rewrite_window_to_columns(&mut e, &window_nodes);
10625 let key = eval::eval_expr(&e, row, &ext_ctx)?;
10626 keys.push(value_to_order_key(&key)?);
10627 }
10628 keys
10629 };
10630 tagged.push((order_keys, Row::new(values)));
10631 }
10632 if !stmt.order_by.is_empty() {
10634 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
10635 sort_by_keys(&mut tagged, &descs);
10636 }
10637 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
10638 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
10639 let final_cols: Vec<ColumnSchema> = projection
10640 .into_iter()
10641 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
10642 .collect();
10643 Ok(QueryResult::Rows {
10644 columns: final_cols,
10645 rows: out_rows,
10646 })
10647 }
10648
10649 fn exec_select_with_meta_views(
10666 &self,
10667 stmt: &SelectStatement,
10668 cancel: CancelToken<'_>,
10669 ) -> Result<QueryResult, EngineError> {
10670 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
10671 collect_meta_view_names(stmt, &mut needed);
10672 let mut catalog = self.active_catalog().clone();
10673 for view in &needed {
10674 if catalog.get(view).is_some() {
10675 continue;
10676 }
10677 match view.as_str() {
10678 "__spg_info_columns" => {
10679 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
10680 materialise_meta_view(&mut catalog, view, schema, rows)?;
10681 }
10682 "__spg_info_tables" => {
10683 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
10684 materialise_meta_view(&mut catalog, view, schema, rows)?;
10685 }
10686 "__spg_pg_class" => {
10687 let (schema, rows) = synth_pg_class(self.active_catalog());
10688 materialise_meta_view(&mut catalog, view, schema, rows)?;
10689 }
10690 "__spg_pg_attribute" => {
10691 let (schema, rows) = synth_pg_attribute(self.active_catalog());
10692 materialise_meta_view(&mut catalog, view, schema, rows)?;
10693 }
10694 "__spg_pg_type" => {
10697 let (schema, rows) = synth_pg_type(self.active_catalog());
10698 materialise_meta_view(&mut catalog, view, schema, rows)?;
10699 }
10700 "__spg_pg_proc" => {
10703 let (schema, rows) = synth_pg_proc(self.active_catalog());
10704 materialise_meta_view(&mut catalog, view, schema, rows)?;
10705 }
10706 "__spg_pg_trigger" => {
10713 let (schema, rows) = synth_pg_trigger(self.active_catalog());
10714 materialise_meta_view(&mut catalog, view, schema, rows)?;
10715 }
10716 "__spg_pg_namespace" => {
10719 let (schema, rows) = synth_pg_namespace(self.active_catalog());
10720 materialise_meta_view(&mut catalog, view, schema, rows)?;
10721 }
10722 "__spg_pg_indexes" => {
10725 let (schema, rows) = synth_pg_indexes(self.active_catalog());
10726 materialise_meta_view(&mut catalog, view, schema, rows)?;
10727 }
10728 "__spg_pg_index" => {
10731 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
10732 materialise_meta_view(&mut catalog, view, schema, rows)?;
10733 }
10734 "__spg_pg_constraint" => {
10737 let (schema, rows) = synth_pg_constraint(self.active_catalog());
10738 materialise_meta_view(&mut catalog, view, schema, rows)?;
10739 }
10740 "__spg_pg_database" => {
10745 let (schema, rows) = synth_pg_database(self.active_catalog());
10746 materialise_meta_view(&mut catalog, view, schema, rows)?;
10747 }
10748 "__spg_pg_roles" | "__spg_pg_user" => {
10749 let (schema, rows) = synth_pg_roles(self);
10750 materialise_meta_view(&mut catalog, view, schema, rows)?;
10751 }
10752 "__spg_pg_views" => {
10756 let (schema, rows) = synth_pg_views(self.active_catalog());
10757 materialise_meta_view(&mut catalog, view, schema, rows)?;
10758 }
10759 "__spg_pg_matviews" => {
10763 let (schema, _) = synth_pg_views(self.active_catalog());
10764 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
10765 }
10766 "__spg_pg_extension" => {
10769 let (schema, rows) = synth_pg_extension();
10770 materialise_meta_view(&mut catalog, view, schema, rows)?;
10771 }
10772 "__spg_pg_settings" => {
10774 let (schema, rows) = synth_pg_settings(self);
10775 materialise_meta_view(&mut catalog, view, schema, rows)?;
10776 }
10777 "__spg_info_key_column_usage" => {
10779 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
10780 materialise_meta_view(&mut catalog, view, schema, rows)?;
10781 }
10782 "__spg_info_referential_constraints" => {
10784 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
10785 materialise_meta_view(&mut catalog, view, schema, rows)?;
10786 }
10787 "__spg_info_statistics" => {
10789 let (schema, rows) = synth_info_statistics(self.active_catalog());
10790 materialise_meta_view(&mut catalog, view, schema, rows)?;
10791 }
10792 "__spg_info_routines" => {
10794 let (schema, rows) = synth_info_routines();
10795 materialise_meta_view(&mut catalog, view, schema, rows)?;
10796 }
10797 "__spg_mysql_user" => {
10799 let (schema, rows) = synth_mysql_user(self);
10800 materialise_meta_view(&mut catalog, view, schema, rows)?;
10801 }
10802 "__spg_mysql_db" => {
10803 let (schema, rows) = synth_mysql_db();
10804 materialise_meta_view(&mut catalog, view, schema, rows)?;
10805 }
10806 _ => {
10807 return Err(EngineError::Unsupported(alloc::format!(
10808 "meta view {view:?} is not yet materialisable; \
10809 v7.16.2 covers information_schema.columns / .tables \
10810 and pg_catalog.pg_class / pg_attribute; \
10811 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
10812 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
10813 pg_user / pg_views / pg_matviews / pg_settings"
10814 )));
10815 }
10816 }
10817 }
10818 let mut temp = Engine::restore(catalog);
10819 if let Some(c) = self.clock {
10820 temp = temp.with_clock(c);
10821 }
10822 if let Some(f) = self.salt_fn {
10823 temp = temp.with_salt_fn(f);
10824 }
10825 temp.meta_views_materialised = true;
10826 temp.exec_select_cancel(stmt, cancel)
10827 }
10828
10829 fn exec_with_ctes(
10830 &self,
10831 stmt: &SelectStatement,
10832 cancel: CancelToken<'_>,
10833 ) -> Result<QueryResult, EngineError> {
10834 cancel.check()?;
10835 let mut catalog = self.active_catalog().clone();
10836 for cte in &stmt.ctes {
10837 if catalog.get(&cte.name).is_some() {
10838 return Err(EngineError::Unsupported(alloc::format!(
10839 "CTE name {:?} shadows an existing table; rename the CTE",
10840 cte.name
10841 )));
10842 }
10843 let (columns, rows) = if cte.recursive {
10844 self.materialise_recursive_cte(cte, &catalog, cancel)?
10845 } else {
10846 let mut cte_engine = Engine::restore(catalog.clone());
10852 if let Some(c) = self.clock {
10853 cte_engine = cte_engine.with_clock(c);
10854 }
10855 if let Some(f) = self.salt_fn {
10856 cte_engine = cte_engine.with_salt_fn(f);
10857 }
10858 let body_result = cte_engine.exec_select_cancel(&cte.body, cancel)?;
10859 let QueryResult::Rows { columns, rows } = body_result else {
10860 return Err(EngineError::Unsupported(alloc::format!(
10861 "CTE {:?} body did not return rows",
10862 cte.name
10863 )));
10864 };
10865 (columns, rows)
10866 };
10867 let inferred = infer_column_types(&columns, &rows);
10872 let mut columns = inferred;
10873 if !cte.column_overrides.is_empty() {
10875 if cte.column_overrides.len() != columns.len() {
10876 return Err(EngineError::Unsupported(alloc::format!(
10877 "CTE {:?} column list has {} names but body returns {} columns",
10878 cte.name,
10879 cte.column_overrides.len(),
10880 columns.len()
10881 )));
10882 }
10883 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10884 col.name.clone_from(name);
10885 }
10886 }
10887 let schema = TableSchema::new(cte.name.clone(), columns);
10888 catalog.create_table(schema).map_err(EngineError::Storage)?;
10889 let table = catalog
10890 .get_mut(&cte.name)
10891 .expect("just-created CTE table must exist");
10892 for row in rows {
10893 table.insert(row).map_err(EngineError::Storage)?;
10894 }
10895 }
10896 let mut body = stmt.clone();
10899 body.ctes = Vec::new();
10900 let mut temp = Engine::restore(catalog);
10901 if let Some(c) = self.clock {
10902 temp = temp.with_clock(c);
10903 }
10904 if let Some(f) = self.salt_fn {
10905 temp = temp.with_salt_fn(f);
10906 }
10907 temp.exec_select_cancel(&body, cancel)
10908 }
10909
10910 #[allow(clippy::too_many_lines)]
10920 fn materialise_recursive_cte(
10921 &self,
10922 cte: &spg_sql::ast::Cte,
10923 base_catalog: &Catalog,
10924 cancel: CancelToken<'_>,
10925 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10926 const MAX_TOTAL_ROWS: usize = 1_000_000;
10927 const MAX_ITERATIONS: usize = 100_000;
10928 cancel.check()?;
10929 if cte.body.unions.is_empty() {
10930 return Err(EngineError::Unsupported(alloc::format!(
10931 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10932 cte.name
10933 )));
10934 }
10935 let mut anchor = cte.body.clone();
10937 let union_terms = core::mem::take(&mut anchor.unions);
10938 anchor.ctes = Vec::new();
10939 if select_refers_to(&anchor, &cte.name) {
10941 return Err(EngineError::Unsupported(alloc::format!(
10942 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10943 cte.name
10944 )));
10945 }
10946 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10947 let QueryResult::Rows {
10948 columns: anchor_cols,
10949 rows: anchor_rows,
10950 } = anchor_result
10951 else {
10952 return Err(EngineError::Unsupported(alloc::format!(
10953 "WITH RECURSIVE {:?}: anchor did not return rows",
10954 cte.name
10955 )));
10956 };
10957 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10961 if !cte.column_overrides.is_empty() {
10962 if cte.column_overrides.len() != columns.len() {
10963 return Err(EngineError::Unsupported(alloc::format!(
10964 "CTE {:?} column list has {} names but anchor returns {} columns",
10965 cte.name,
10966 cte.column_overrides.len(),
10967 columns.len()
10968 )));
10969 }
10970 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10971 col.name.clone_from(name);
10972 }
10973 }
10974 let mut all_rows: Vec<Row> = anchor_rows.clone();
10975 let mut working_set: Vec<Row> = anchor_rows;
10976 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10977 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10980 if !all_union_all {
10981 for r in &all_rows {
10982 seen.insert(encode_row_key(r));
10983 }
10984 }
10985 for iter in 0..MAX_ITERATIONS {
10986 cancel.check()?;
10987 if working_set.is_empty() {
10988 break;
10989 }
10990 let mut iter_catalog = base_catalog.clone();
10992 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10993 iter_catalog
10994 .create_table(schema)
10995 .map_err(EngineError::Storage)?;
10996 {
10997 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10998 for row in &working_set {
10999 table.insert(row.clone()).map_err(EngineError::Storage)?;
11000 }
11001 }
11002 let mut iter_engine = Engine::restore(iter_catalog);
11003 if let Some(c) = self.clock {
11004 iter_engine = iter_engine.with_clock(c);
11005 }
11006 if let Some(f) = self.salt_fn {
11007 iter_engine = iter_engine.with_salt_fn(f);
11008 }
11009 let mut next_set: Vec<Row> = Vec::new();
11011 for (_, term) in &union_terms {
11012 let mut term = term.clone();
11013 term.ctes = Vec::new();
11014 let r = iter_engine.exec_select_cancel(&term, cancel)?;
11015 let QueryResult::Rows {
11016 columns: rc,
11017 rows: rs,
11018 } = r
11019 else {
11020 return Err(EngineError::Unsupported(alloc::format!(
11021 "WITH RECURSIVE {:?}: recursive term did not return rows",
11022 cte.name
11023 )));
11024 };
11025 if rc.len() != columns.len() {
11026 return Err(EngineError::Unsupported(alloc::format!(
11027 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
11028 cte.name,
11029 rc.len(),
11030 columns.len()
11031 )));
11032 }
11033 for row in rs {
11034 if !all_union_all {
11035 let key = encode_row_key(&row);
11036 if !seen.insert(key) {
11037 continue;
11038 }
11039 }
11040 next_set.push(row);
11041 }
11042 }
11043 if next_set.is_empty() {
11044 break;
11045 }
11046 all_rows.extend(next_set.iter().cloned());
11047 working_set = next_set;
11048 if all_rows.len() > MAX_TOTAL_ROWS {
11049 return Err(EngineError::Unsupported(alloc::format!(
11050 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
11051 cte.name
11052 )));
11053 }
11054 if iter + 1 == MAX_ITERATIONS {
11055 return Err(EngineError::Unsupported(alloc::format!(
11056 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
11057 cte.name
11058 )));
11059 }
11060 }
11061 Ok((columns, all_rows))
11062 }
11063
11064 fn resolve_select_subqueries(
11065 &self,
11066 stmt: &mut SelectStatement,
11067 cancel: CancelToken<'_>,
11068 ) -> Result<(), EngineError> {
11069 for item in &mut stmt.items {
11070 if let SelectItem::Expr { expr, .. } = item {
11071 self.resolve_expr_subqueries(expr, cancel)?;
11072 }
11073 }
11074 if let Some(w) = &mut stmt.where_ {
11075 self.resolve_expr_subqueries(w, cancel)?;
11076 }
11077 if let Some(from) = &mut stmt.from {
11081 for j in &mut from.joins {
11082 if let Some(on) = &mut j.on {
11083 self.resolve_expr_subqueries(on, cancel)?;
11084 }
11085 }
11086 }
11087 if let Some(gs) = &mut stmt.group_by {
11088 for g in gs {
11089 self.resolve_expr_subqueries(g, cancel)?;
11090 }
11091 }
11092 if let Some(h) = &mut stmt.having {
11093 self.resolve_expr_subqueries(h, cancel)?;
11094 }
11095 for o in &mut stmt.order_by {
11096 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
11097 }
11098 for (_, peer) in &mut stmt.unions {
11099 self.resolve_select_subqueries(peer, cancel)?;
11100 }
11101 Ok(())
11102 }
11103
11104 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
11106 &self,
11107 e: &mut Expr,
11108 cancel: CancelToken<'_>,
11109 ) -> Result<(), EngineError> {
11110 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
11112 *e = replacement;
11113 return Ok(());
11114 }
11115 match e {
11116 Expr::AggregateOrdered { call, order_by, .. } => {
11117 self.resolve_expr_subqueries(call, cancel)?;
11118 for o in order_by.iter_mut() {
11119 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
11120 }
11121 }
11122 Expr::Binary { lhs, rhs, .. } => {
11123 self.resolve_expr_subqueries(lhs, cancel)?;
11124 self.resolve_expr_subqueries(rhs, cancel)?;
11125 }
11126 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11127 self.resolve_expr_subqueries(expr, cancel)?;
11128 }
11129 Expr::FunctionCall { args, .. } => {
11130 for a in args {
11131 self.resolve_expr_subqueries(a, cancel)?;
11132 }
11133 }
11134 Expr::Like { expr, pattern, .. } => {
11135 self.resolve_expr_subqueries(expr, cancel)?;
11136 self.resolve_expr_subqueries(pattern, cancel)?;
11137 }
11138 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
11139 Expr::WindowFunction {
11142 args,
11143 partition_by,
11144 order_by,
11145 ..
11146 } => {
11147 for a in args {
11148 self.resolve_expr_subqueries(a, cancel)?;
11149 }
11150 for p in partition_by {
11151 self.resolve_expr_subqueries(p, cancel)?;
11152 }
11153 for (e, _, _) in order_by {
11154 self.resolve_expr_subqueries(e, cancel)?;
11155 }
11156 }
11157 Expr::ScalarSubquery(_)
11161 | Expr::Exists { .. }
11162 | Expr::InSubquery { .. }
11163 | Expr::Literal(_)
11164 | Expr::Placeholder(_)
11165 | Expr::Column(_) => {}
11166 Expr::Array(items) => {
11168 for elem in items {
11169 self.resolve_expr_subqueries(elem, cancel)?;
11170 }
11171 }
11172 Expr::ArraySubscript { target, index } => {
11173 self.resolve_expr_subqueries(target, cancel)?;
11174 self.resolve_expr_subqueries(index, cancel)?;
11175 }
11176 Expr::AnyAll { expr, array, .. } => {
11177 self.resolve_expr_subqueries(expr, cancel)?;
11178 self.resolve_expr_subqueries(array, cancel)?;
11179 }
11180 Expr::Case {
11181 operand,
11182 branches,
11183 else_branch,
11184 } => {
11185 if let Some(o) = operand {
11186 self.resolve_expr_subqueries(o, cancel)?;
11187 }
11188 for (w, t) in branches {
11189 self.resolve_expr_subqueries(w, cancel)?;
11190 self.resolve_expr_subqueries(t, cancel)?;
11191 }
11192 if let Some(e) = else_branch {
11193 self.resolve_expr_subqueries(e, cancel)?;
11194 }
11195 }
11196 }
11197 Ok(())
11198 }
11199
11200 fn eval_expr_with_correlated(
11208 &self,
11209 expr: &Expr,
11210 row: &Row,
11211 ctx: &EvalContext<'_>,
11212 cancel: CancelToken<'_>,
11213 mut memo: Option<&mut memoize::MemoizeCache>,
11214 ) -> Result<Value, EngineError> {
11215 if !expr_has_subquery(expr) {
11216 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
11217 }
11218 if let Some(m) = memo.as_deref_mut() {
11224 let key = core::ptr::from_ref::<Expr>(expr) as usize;
11225 let mut subs: Vec<&SelectStatement> = Vec::new();
11226 collect_scalar_subqueries(expr, &mut subs);
11227 let plan_ok = match m.expr_plans.get(&key) {
11228 Some((count, _, _)) => *count == subs.len(),
11229 None => false,
11230 };
11231 if !plan_ok && !subs.is_empty() {
11232 let mut plan: Vec<Option<alloc::rc::Rc<memoize::GroupMap>>> =
11233 Vec::with_capacity(subs.len());
11234 for sub in &subs {
11235 let repr = alloc::format!("{sub}");
11236 if !m.group_maps.contains_key(&repr) {
11237 let built = self
11238 .try_batch_correlated_scalar(sub, cancel)?
11239 .map(alloc::rc::Rc::new);
11240 m.group_maps.insert(repr.clone(), built);
11241 }
11242 plan.push(m.group_maps.get(&repr).cloned().flatten());
11243 }
11244 let mut template = expr.clone();
11245 hollow_scalar_subqueries(&mut template);
11246 m.expr_plans.insert(key, (subs.len(), plan, template));
11247 }
11248 if let Some((_, plan, template)) = m.expr_plans.get(&key)
11249 && !plan.is_empty()
11250 && plan.iter().all(|p| p.is_some())
11251 {
11252 let plan = plan.clone();
11259 let mut e = template.clone();
11260 let mut idx = 0usize;
11261 let ok = splice_planned_subqueries(&mut e, &plan, &mut idx, row, ctx)?;
11262 if ok {
11263 if expr_has_subquery(&e) {
11264 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11265 }
11266 return eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval);
11267 }
11268 }
11269 }
11270 let mut e = expr.clone();
11271 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11272 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
11273 }
11274
11275 fn resolve_correlated_in_expr(
11276 &self,
11277 e: &mut Expr,
11278 row: &Row,
11279 ctx: &EvalContext<'_>,
11280 cancel: CancelToken<'_>,
11281 mut memo: Option<&mut memoize::MemoizeCache>,
11282 ) -> Result<(), EngineError> {
11283 match e {
11284 Expr::AggregateOrdered { call, order_by, .. } => {
11285 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
11286 for o in order_by.iter_mut() {
11287 self.resolve_correlated_in_expr(
11288 &mut o.expr,
11289 row,
11290 ctx,
11291 cancel,
11292 memo.as_deref_mut(),
11293 )?;
11294 }
11295 }
11296 Expr::ScalarSubquery(inner) => {
11297 if memo.is_some() {
11304 let repr = alloc::format!("{}", **inner);
11305 let entry_known = memo
11306 .as_ref()
11307 .is_some_and(|m| m.group_maps.contains_key(&repr));
11308 if !entry_known {
11309 let built = self
11310 .try_batch_correlated_scalar(inner, cancel)?
11311 .map(alloc::rc::Rc::new);
11312 if let Some(m) = memo.as_deref_mut() {
11313 m.group_maps.insert(repr.clone(), built);
11314 }
11315 }
11316 if let Some(m) = memo.as_deref_mut()
11317 && let Some(Some(gm)) = m.group_maps.get(&repr)
11318 {
11319 let (outer_col, map) = gm.as_ref();
11320 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
11321 .map_err(EngineError::Eval)?;
11322 let v = if matches!(key_v, Value::Null) {
11323 Value::Null
11324 } else {
11325 map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
11326 .cloned()
11327 .unwrap_or(Value::Null)
11328 };
11329 *e = value_to_literal_expr(v)?;
11330 return Ok(());
11331 }
11332 }
11333 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
11338 subquery_repr: alloc::format!("{}", **inner),
11339 outer_values: row.values.clone(),
11340 });
11341 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
11342 && let Some(cached) = cache.get(k)
11343 {
11344 *e = value_to_literal_expr(cached)?;
11345 return Ok(());
11346 }
11347 let mut s = (**inner).clone();
11348 substitute_outer_columns(&mut s, row, ctx);
11349 let r = self.exec_select_cancel(&s, cancel)?;
11350 let QueryResult::Rows { rows, .. } = r else {
11351 return Err(EngineError::Unsupported(
11352 "scalar subquery: inner did not return rows".into(),
11353 ));
11354 };
11355 let value = match rows.as_slice() {
11356 [] => Value::Null,
11357 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
11358 _ => {
11359 return Err(EngineError::Unsupported(alloc::format!(
11360 "scalar subquery returned {} rows; expected 0 or 1",
11361 rows.len()
11362 )));
11363 }
11364 };
11365 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
11366 cache.insert(k, value.clone());
11367 }
11368 *e = value_to_literal_expr(value)?;
11369 }
11370 Expr::Exists { subquery, negated } => {
11371 let mut s = (**subquery).clone();
11372 substitute_outer_columns(&mut s, row, ctx);
11373 let r = self.exec_select_cancel(&s, cancel)?;
11374 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
11375 let bit = if *negated { !exists } else { exists };
11376 *e = Expr::Literal(Literal::Bool(bit));
11377 }
11378 Expr::InSubquery {
11379 expr: lhs,
11380 subquery,
11381 negated,
11382 } => {
11383 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11384 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
11385 let mut s = (**subquery).clone();
11386 substitute_outer_columns(&mut s, row, ctx);
11387 let r = self.exec_select_cancel(&s, cancel)?;
11388 let QueryResult::Rows { columns, rows, .. } = r else {
11389 return Err(EngineError::Unsupported(
11390 "IN-subquery: inner did not return rows".into(),
11391 ));
11392 };
11393 if columns.len() != 1 {
11394 return Err(EngineError::Unsupported(alloc::format!(
11395 "IN-subquery must project exactly one column; got {}",
11396 columns.len()
11397 )));
11398 }
11399 let mut found = false;
11400 let mut any_null = false;
11401 for r0 in rows {
11402 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
11403 if v.is_null() {
11404 any_null = true;
11405 continue;
11406 }
11407 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
11408 found = true;
11409 break;
11410 }
11411 }
11412 let bit = if found {
11413 !*negated
11414 } else if any_null {
11415 return Err(EngineError::Unsupported(
11416 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
11417 ));
11418 } else {
11419 *negated
11420 };
11421 *e = Expr::Literal(Literal::Bool(bit));
11422 }
11423 Expr::Binary { lhs, rhs, .. } => {
11424 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11425 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
11426 }
11427 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11428 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11429 }
11430 Expr::Like { expr, pattern, .. } => {
11431 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11432 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
11433 }
11434 Expr::FunctionCall { args, .. } => {
11435 for a in args {
11436 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
11437 }
11438 }
11439 Expr::Extract { source, .. } => {
11440 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
11441 }
11442 Expr::WindowFunction { .. }
11443 | Expr::Literal(_)
11444 | Expr::Placeholder(_)
11445 | Expr::Column(_) => {}
11446 Expr::Array(items) => {
11448 for elem in items {
11449 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
11450 }
11451 }
11452 Expr::ArraySubscript { target, index } => {
11453 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
11454 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
11455 }
11456 Expr::AnyAll { expr, array, .. } => {
11457 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11458 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
11459 }
11460 Expr::Case {
11461 operand,
11462 branches,
11463 else_branch,
11464 } => {
11465 if let Some(o) = operand {
11466 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
11467 }
11468 for (w, t) in branches {
11469 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
11470 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
11471 }
11472 if let Some(e) = else_branch {
11473 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
11474 }
11475 }
11476 }
11477 Ok(())
11478 }
11479
11480 fn subquery_replacement(
11481 &self,
11482 e: &Expr,
11483 cancel: CancelToken<'_>,
11484 ) -> Result<Option<Expr>, EngineError> {
11485 match e {
11486 Expr::ScalarSubquery(inner) => {
11487 let mut s = (**inner).clone();
11488 self.resolve_select_subqueries(&mut s, cancel)?;
11491 let r = match self.exec_bare_select_cancel(&s, cancel) {
11492 Ok(r) => r,
11493 Err(e) if is_correlation_error(&e) => return Ok(None),
11494 Err(e) => return Err(e),
11495 };
11496 let QueryResult::Rows { rows, .. } = r else {
11497 return Err(EngineError::Unsupported(
11498 "scalar subquery: inner statement did not return rows".into(),
11499 ));
11500 };
11501 let value = match rows.as_slice() {
11502 [] => Value::Null,
11503 [row] => row.values.first().cloned().unwrap_or(Value::Null),
11504 _ => {
11505 return Err(EngineError::Unsupported(alloc::format!(
11506 "scalar subquery returned {} rows; expected 0 or 1",
11507 rows.len()
11508 )));
11509 }
11510 };
11511 Ok(Some(value_to_literal_expr(value)?))
11512 }
11513 Expr::Exists { subquery, negated } => {
11514 let mut s = (**subquery).clone();
11515 self.resolve_select_subqueries(&mut s, cancel)?;
11516 let r = match self.exec_bare_select_cancel(&s, cancel) {
11517 Ok(r) => r,
11518 Err(e) if is_correlation_error(&e) => return Ok(None),
11519 Err(e) => return Err(e),
11520 };
11521 let exists = match r {
11522 QueryResult::Rows { rows, .. } => !rows.is_empty(),
11523 QueryResult::CommandOk { .. } => false,
11524 };
11525 let bit = if *negated { !exists } else { exists };
11526 Ok(Some(Expr::Literal(Literal::Bool(bit))))
11527 }
11528 Expr::InSubquery {
11529 expr,
11530 subquery,
11531 negated,
11532 } => {
11533 let mut s = (**subquery).clone();
11534 self.resolve_select_subqueries(&mut s, cancel)?;
11535 let r = match self.exec_bare_select_cancel(&s, cancel) {
11536 Ok(r) => r,
11537 Err(e) if is_correlation_error(&e) => return Ok(None),
11538 Err(e) => return Err(e),
11539 };
11540 let QueryResult::Rows { columns, rows, .. } = r else {
11541 return Err(EngineError::Unsupported(
11542 "IN-subquery: inner statement did not return rows".into(),
11543 ));
11544 };
11545 if columns.len() != 1 {
11546 return Err(EngineError::Unsupported(alloc::format!(
11547 "IN-subquery must project exactly one column; got {}",
11548 columns.len()
11549 )));
11550 }
11551 let mut acc: Option<Expr> = None;
11554 for row in rows {
11555 let v = row.values.into_iter().next().unwrap_or(Value::Null);
11556 let lit = value_to_literal_expr(v)?;
11557 let cmp = Expr::Binary {
11558 lhs: expr.clone(),
11559 op: BinOp::Eq,
11560 rhs: Box::new(lit),
11561 };
11562 acc = Some(match acc {
11563 None => cmp,
11564 Some(prev) => Expr::Binary {
11565 lhs: Box::new(prev),
11566 op: BinOp::Or,
11567 rhs: Box::new(cmp),
11568 },
11569 });
11570 }
11571 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
11572 let final_expr = if *negated {
11573 Expr::Unary {
11574 op: UnOp::Not,
11575 expr: Box::new(combined),
11576 }
11577 } else {
11578 combined
11579 };
11580 Ok(Some(final_expr))
11581 }
11582 _ => Ok(None),
11583 }
11584 }
11585}
11586
11587fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
11599 if let Some(from) = &stmt.from
11600 && from_refers_to(from, target)
11601 {
11602 return true;
11603 }
11604 for (_, peer) in &stmt.unions {
11605 if select_refers_to(peer, target) {
11606 return true;
11607 }
11608 }
11609 for item in &stmt.items {
11610 if let SelectItem::Expr { expr, .. } = item
11611 && expr_refers_to(expr, target)
11612 {
11613 return true;
11614 }
11615 }
11616 if let Some(w) = &stmt.where_
11617 && expr_refers_to(w, target)
11618 {
11619 return true;
11620 }
11621 false
11622}
11623
11624fn from_refers_to(from: &FromClause, target: &str) -> bool {
11625 if from.primary.name.eq_ignore_ascii_case(target) {
11626 return true;
11627 }
11628 from.joins
11629 .iter()
11630 .any(|j| j.table.name.eq_ignore_ascii_case(target))
11631}
11632
11633fn collect_qualified_refs(
11638 stmt: &SelectStatement,
11639 out: &mut alloc::collections::BTreeSet<(String, String)>,
11640) -> Option<()> {
11641 for item in &stmt.items {
11642 match item {
11643 SelectItem::Wildcard => return None,
11644 SelectItem::Expr { expr, .. } => collect_qualified_refs_expr(expr, out)?,
11645 }
11646 }
11647 if let Some(w) = &stmt.where_ {
11648 collect_qualified_refs_expr(w, out)?;
11649 }
11650 if let Some(from) = &stmt.from {
11651 for j in &from.joins {
11652 if let Some(on) = &j.on {
11653 collect_qualified_refs_expr(on, out)?;
11654 }
11655 if j.table.lateral_subquery.is_some() {
11656 return None;
11657 }
11658 }
11659 }
11660 if let Some(gs) = &stmt.group_by {
11661 for g in gs {
11662 collect_qualified_refs_expr(g, out)?;
11663 }
11664 }
11665 if let Some(h) = &stmt.having {
11666 collect_qualified_refs_expr(h, out)?;
11667 }
11668 for o in &stmt.order_by {
11669 collect_qualified_refs_expr(&o.expr, out)?;
11670 }
11671 for (_, peer) in &stmt.unions {
11672 collect_qualified_refs(peer, out)?;
11673 }
11674 for cte in &stmt.ctes {
11675 collect_qualified_refs(&cte.body, out)?;
11676 }
11677 Some(())
11678}
11679
11680fn collect_qualified_refs_expr(
11681 e: &Expr,
11682 out: &mut alloc::collections::BTreeSet<(String, String)>,
11683) -> Option<()> {
11684 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
11687 let mut subs: Vec<&SelectStatement> = Vec::new();
11688 visit_expr_columns_and_subqueries(
11689 e,
11690 &mut |c: &spg_sql::ast::ColumnName| cols.push(c.clone()),
11691 &mut |sub| subs.push(sub),
11692 );
11693 for c in cols {
11694 match c.qualifier {
11695 Some(q) => {
11696 out.insert((q, c.name));
11697 }
11698 None => return None,
11699 }
11700 }
11701 for sub in subs {
11702 collect_qualified_refs(sub, out)?;
11703 }
11704 Some(())
11705}
11706
11707fn visit_expr_columns_and_subqueries<'a>(
11710 e: &'a Expr,
11711 on_col: &mut impl FnMut(&'a spg_sql::ast::ColumnName),
11712 on_sub: &mut impl FnMut(&'a SelectStatement),
11713) {
11714 match e {
11715 Expr::Column(c) => on_col(c),
11716 Expr::ScalarSubquery(s) => on_sub(s),
11717 Expr::Exists { subquery, .. } => on_sub(subquery),
11718 Expr::InSubquery { expr, subquery, .. } => {
11719 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11720 on_sub(subquery);
11721 }
11722 Expr::Binary { lhs, rhs, .. } => {
11723 visit_expr_columns_and_subqueries(lhs, on_col, on_sub);
11724 visit_expr_columns_and_subqueries(rhs, on_col, on_sub);
11725 }
11726 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11727 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11728 }
11729 Expr::Like { expr, pattern, .. } => {
11730 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11731 visit_expr_columns_and_subqueries(pattern, on_col, on_sub);
11732 }
11733 Expr::FunctionCall { args, .. } => {
11734 for a in args {
11735 visit_expr_columns_and_subqueries(a, on_col, on_sub);
11736 }
11737 }
11738 Expr::AggregateOrdered { call, order_by, .. } => {
11739 visit_expr_columns_and_subqueries(call, on_col, on_sub);
11740 for o in order_by {
11741 visit_expr_columns_and_subqueries(&o.expr, on_col, on_sub);
11742 }
11743 }
11744 Expr::Case {
11745 operand,
11746 branches,
11747 else_branch,
11748 } => {
11749 if let Some(op) = operand {
11750 visit_expr_columns_and_subqueries(op, on_col, on_sub);
11751 }
11752 for (w, t) in branches {
11753 visit_expr_columns_and_subqueries(w, on_col, on_sub);
11754 visit_expr_columns_and_subqueries(t, on_col, on_sub);
11755 }
11756 if let Some(eb) = else_branch {
11757 visit_expr_columns_and_subqueries(eb, on_col, on_sub);
11758 }
11759 }
11760 Expr::ArraySubscript { target, index } => {
11761 visit_expr_columns_and_subqueries(target, on_col, on_sub);
11762 visit_expr_columns_and_subqueries(index, on_col, on_sub);
11763 }
11764 Expr::Literal(_) | Expr::Placeholder(_) => {}
11765 _ => {
11770 static BAIL: spg_sql::ast::ColumnName = spg_sql::ast::ColumnName {
11773 qualifier: None,
11774 name: String::new(),
11775 };
11776 on_col(&BAIL);
11777 }
11778 }
11779}
11780
11781fn collect_column_qualifiers<'e>(e: &'e Expr, out: &mut Vec<&'e str>, all_qualified: &mut bool) {
11785 if let Expr::Column(c) = e {
11786 match &c.qualifier {
11787 Some(q) => out.push(q.as_str()),
11788 None => *all_qualified = false,
11789 }
11790 return;
11791 }
11792 match e {
11795 Expr::Binary { lhs, rhs, .. } => {
11796 collect_column_qualifiers(lhs, out, all_qualified);
11797 collect_column_qualifiers(rhs, out, all_qualified);
11798 }
11799 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11800 collect_column_qualifiers(expr, out, all_qualified);
11801 }
11802 Expr::Like { expr, pattern, .. } => {
11803 collect_column_qualifiers(expr, out, all_qualified);
11804 collect_column_qualifiers(pattern, out, all_qualified);
11805 }
11806 Expr::FunctionCall { args, .. } => {
11807 for a in args {
11808 collect_column_qualifiers(a, out, all_qualified);
11809 }
11810 }
11811 Expr::Literal(_) | Expr::Placeholder(_) => {}
11812 _ => *all_qualified = false,
11815 }
11816}
11817
11818fn expr_refers_to(e: &Expr, target: &str) -> bool {
11819 match e {
11820 Expr::AggregateOrdered { call, order_by, .. } => {
11821 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
11822 }
11823 Expr::ScalarSubquery(s) => select_refers_to(s, target),
11824 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
11825 select_refers_to(subquery, target)
11826 }
11827 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
11828 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11829 expr_refers_to(expr, target)
11830 }
11831 Expr::Like { expr, pattern, .. } => {
11832 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
11833 }
11834 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
11835 Expr::Extract { source, .. } => expr_refers_to(source, target),
11836 Expr::WindowFunction {
11837 args,
11838 partition_by,
11839 order_by,
11840 ..
11841 } => {
11842 args.iter().any(|a| expr_refers_to(a, target))
11843 || partition_by.iter().any(|p| expr_refers_to(p, target))
11844 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
11845 }
11846 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
11847 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
11848 Expr::ArraySubscript { target: t, index } => {
11849 expr_refers_to(t, target) || expr_refers_to(index, target)
11850 }
11851 Expr::AnyAll { expr, array, .. } => {
11852 expr_refers_to(expr, target) || expr_refers_to(array, target)
11853 }
11854 Expr::Case {
11855 operand,
11856 branches,
11857 else_branch,
11858 } => {
11859 operand
11860 .as_deref()
11861 .is_some_and(|o| expr_refers_to(o, target))
11862 || branches
11863 .iter()
11864 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
11865 || else_branch
11866 .as_deref()
11867 .is_some_and(|e| expr_refers_to(e, target))
11868 }
11869 }
11870}
11871
11872fn pg_data_type_text(ty: DataType) -> alloc::string::String {
11883 let s = match ty {
11884 DataType::Int => "integer",
11885 DataType::BigInt => "bigint",
11886 DataType::SmallInt => "smallint",
11887 DataType::Float => "double precision",
11888 DataType::Bool => "boolean",
11889 DataType::Text => "text",
11890 DataType::Varchar(_) => "character varying",
11891 DataType::Date => "date",
11892 DataType::Timestamp => "timestamp without time zone",
11893 DataType::Timestamptz => "timestamp with time zone",
11894 DataType::Json => "jsonb",
11895 DataType::Bytes => "bytea",
11896 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
11897 DataType::TsVector => "tsvector",
11898 DataType::TsQuery => "tsquery",
11899 DataType::Vector { .. } => "USER-DEFINED",
11900 _ => "USER-DEFINED",
11903 };
11904 alloc::string::String::from(s)
11905}
11906
11907fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11914 let schema = alloc::vec![
11915 ColumnSchema::new("table_catalog", DataType::Text, false),
11916 ColumnSchema::new("table_schema", DataType::Text, false),
11917 ColumnSchema::new("table_name", DataType::Text, false),
11918 ColumnSchema::new("column_name", DataType::Text, false),
11919 ColumnSchema::new("ordinal_position", DataType::Int, false),
11920 ColumnSchema::new("is_nullable", DataType::Text, false),
11921 ColumnSchema::new("data_type", DataType::Text, false),
11922 ];
11923 let mut rows: Vec<Row> = Vec::new();
11924 for tname in cat.table_names() {
11925 let Some(t) = cat.get(&tname) else { continue };
11926 for (i, col) in t.schema().columns.iter().enumerate() {
11927 #[allow(clippy::cast_possible_wrap)]
11928 let ordinal = (i + 1) as i32;
11929 rows.push(Row::new(alloc::vec![
11930 Value::Text("spg".into()),
11931 Value::Text("public".into()),
11932 Value::Text(tname.clone()),
11933 Value::Text(col.name.clone()),
11934 Value::Int(ordinal),
11935 Value::Text(if col.nullable {
11936 "YES".into()
11937 } else {
11938 "NO".into()
11939 }),
11940 Value::Text(pg_data_type_text(col.ty)),
11941 ]));
11942 }
11943 }
11944 (schema, rows)
11945}
11946
11947fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11949 let schema = alloc::vec![
11950 ColumnSchema::new("table_catalog", DataType::Text, false),
11951 ColumnSchema::new("table_schema", DataType::Text, false),
11952 ColumnSchema::new("table_name", DataType::Text, false),
11953 ColumnSchema::new("table_type", DataType::Text, false),
11954 ];
11955 let mut rows: Vec<Row> = Vec::new();
11956 for tname in cat.table_names() {
11957 rows.push(Row::new(alloc::vec![
11958 Value::Text("spg".into()),
11959 Value::Text("public".into()),
11960 Value::Text(tname.clone()),
11961 Value::Text("BASE TABLE".into()),
11962 ]));
11963 }
11964 (schema, rows)
11965}
11966
11967fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11971 let schema = alloc::vec![
11972 ColumnSchema::new("relname", DataType::Text, false),
11973 ColumnSchema::new("relkind", DataType::Text, false),
11974 ColumnSchema::new("relnamespace", DataType::BigInt, false),
11975 ];
11976 let mut rows: Vec<Row> = Vec::new();
11977 for tname in cat.table_names() {
11978 rows.push(Row::new(alloc::vec![
11979 Value::Text(tname.clone()),
11980 Value::Text("r".into()),
11981 Value::BigInt(2200), ]));
11983 }
11984 (schema, rows)
11985}
11986
11987fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11991 let schema = alloc::vec![
11992 ColumnSchema::new("attrelid", DataType::Text, false),
11993 ColumnSchema::new("attname", DataType::Text, false),
11994 ColumnSchema::new("attnum", DataType::Int, false),
11995 ColumnSchema::new("atttypid", DataType::Text, false),
11996 ColumnSchema::new("attnotnull", DataType::Bool, false),
11997 ];
11998 let mut rows: Vec<Row> = Vec::new();
11999 for tname in cat.table_names() {
12000 let Some(t) = cat.get(&tname) else { continue };
12001 for (i, col) in t.schema().columns.iter().enumerate() {
12002 #[allow(clippy::cast_possible_wrap)]
12003 let ordinal = (i + 1) as i32;
12004 rows.push(Row::new(alloc::vec![
12005 Value::Text(tname.clone()),
12006 Value::Text(col.name.clone()),
12007 Value::Int(ordinal),
12008 Value::Text(pg_data_type_text(col.ty)),
12009 Value::Bool(!col.nullable),
12010 ]));
12011 }
12012 }
12013 (schema, rows)
12014}
12015
12016fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12033 let schema = alloc::vec![
12034 ColumnSchema::new("oid", DataType::BigInt, false),
12035 ColumnSchema::new("typname", DataType::Text, false),
12036 ColumnSchema::new("typlen", DataType::SmallInt, false),
12037 ColumnSchema::new("typtype", DataType::Text, false),
12038 ColumnSchema::new("typcategory", DataType::Text, false),
12039 ColumnSchema::new("typelem", DataType::BigInt, false),
12040 ColumnSchema::new("typarray", DataType::BigInt, false),
12041 ColumnSchema::new("typnamespace", DataType::BigInt, false),
12042 ];
12043 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
12046 (16, "bool", 1, "b", "B", 0, 1000),
12048 (17, "bytea", -1, "b", "U", 0, 1001),
12049 (18, "char", 1, "b", "S", 0, 1002),
12050 (19, "name", 64, "b", "S", 0, 1003),
12051 (20, "int8", 8, "b", "N", 0, 1016),
12052 (21, "int2", 2, "b", "N", 0, 1005),
12053 (23, "int4", 4, "b", "N", 0, 1007),
12054 (24, "regproc", 4, "b", "N", 0, 1008),
12055 (25, "text", -1, "b", "S", 0, 1009),
12056 (26, "oid", 4, "b", "N", 0, 1028),
12057 (114, "json", -1, "b", "U", 0, 199),
12058 (142, "xml", -1, "b", "U", 0, 143),
12059 (700, "float4", 4, "b", "N", 0, 1021),
12060 (701, "float8", 8, "b", "N", 0, 1022),
12061 (650, "cidr", -1, "b", "I", 0, 651),
12062 (869, "inet", -1, "b", "I", 0, 1041),
12063 (829, "macaddr", 6, "b", "U", 0, 1040),
12064 (1042, "bpchar", -1, "b", "S", 0, 1014),
12065 (1043, "varchar", -1, "b", "S", 0, 1015),
12066 (1082, "date", 4, "b", "D", 0, 1182),
12067 (1083, "time", 8, "b", "D", 0, 1183),
12068 (1114, "timestamp", 8, "b", "D", 0, 1115),
12069 (1184, "timestamptz", 8, "b", "D", 0, 1185),
12070 (1186, "interval", 16, "b", "T", 0, 1187),
12071 (1266, "timetz", 12, "b", "D", 0, 1270),
12072 (1700, "numeric", -1, "b", "N", 0, 1231),
12073 (790, "money", 8, "b", "N", 0, 791),
12074 (2950, "uuid", 16, "b", "U", 0, 2951),
12075 (3802, "jsonb", -1, "b", "U", 0, 3807),
12076 (3614, "tsvector", -1, "b", "U", 0, 3643),
12077 (3615, "tsquery", -1, "b", "U", 0, 3645),
12078 (3908, "tstzrange", -1, "r", "R", 0, 3909),
12080 (3910, "tsrange", -1, "r", "R", 0, 3911),
12081 (3904, "int4range", -1, "r", "R", 0, 3905),
12082 (3926, "int8range", -1, "r", "R", 0, 3927),
12083 (3906, "numrange", -1, "r", "R", 0, 3907),
12084 (3912, "daterange", -1, "r", "R", 0, 3913),
12085 ];
12086 let arrays: &[(i64, &str, i64)] = &[
12089 (1000, "_bool", 16),
12090 (1001, "_bytea", 17),
12091 (1002, "_char", 18),
12092 (1003, "_name", 19),
12093 (1016, "_int8", 20),
12094 (1005, "_int2", 21),
12095 (1007, "_int4", 23),
12096 (1008, "_regproc", 24),
12097 (1009, "_text", 25),
12098 (1028, "_oid", 26),
12099 (199, "_json", 114),
12100 (143, "_xml", 142),
12101 (1021, "_float4", 700),
12102 (1022, "_float8", 701),
12103 (651, "_cidr", 650),
12104 (1041, "_inet", 869),
12105 (1040, "_macaddr", 829),
12106 (1014, "_bpchar", 1042),
12107 (1015, "_varchar", 1043),
12108 (1182, "_date", 1082),
12109 (1183, "_time", 1083),
12110 (1115, "_timestamp", 1114),
12111 (1185, "_timestamptz", 1184),
12112 (1187, "_interval", 1186),
12113 (1270, "_timetz", 1266),
12114 (1231, "_numeric", 1700),
12115 (791, "_money", 790),
12116 (2951, "_uuid", 2950),
12117 (3807, "_jsonb", 3802),
12118 (3643, "_tsvector", 3614),
12119 (3645, "_tsquery", 3615),
12120 ];
12121 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
12122 for &(oid, name, len, ty, cat, elem, arr) in scalars {
12123 rows.push(Row::new(alloc::vec![
12124 Value::BigInt(oid),
12125 Value::Text(name.into()),
12126 Value::SmallInt(len),
12127 Value::Text(ty.into()),
12128 Value::Text(cat.into()),
12129 Value::BigInt(elem),
12130 Value::BigInt(arr),
12131 Value::BigInt(2200),
12132 ]));
12133 }
12134 for &(oid, name, elem) in arrays {
12135 rows.push(Row::new(alloc::vec![
12136 Value::BigInt(oid),
12137 Value::Text(name.into()),
12138 Value::SmallInt(-1),
12139 Value::Text("b".into()),
12140 Value::Text("A".into()),
12141 Value::BigInt(elem),
12142 Value::BigInt(0),
12143 Value::BigInt(2200),
12144 ]));
12145 }
12146 (schema, rows)
12147}
12148
12149fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12168 let schema = alloc::vec![
12169 ColumnSchema::new("tgname", DataType::Text, false),
12170 ColumnSchema::new("relname", DataType::Text, false),
12171 ColumnSchema::new("tgenabled", DataType::Text, false),
12172 ColumnSchema::new("timing", DataType::Text, false),
12173 ColumnSchema::new("events", DataType::Text, false),
12174 ColumnSchema::new("function", DataType::Text, false),
12175 ];
12176 let rows: Vec<Row> = cat
12177 .triggers()
12178 .iter()
12179 .map(|t| {
12180 Row::new(alloc::vec![
12181 Value::Text(t.name.clone()),
12182 Value::Text(t.table.clone()),
12183 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
12184 Value::Text(t.timing.clone()),
12185 Value::Text(t.events.join(" OR ")),
12186 Value::Text(t.function.clone()),
12187 ])
12188 })
12189 .collect();
12190 (schema, rows)
12191}
12192
12193fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12194 let schema = alloc::vec![
12195 ColumnSchema::new("oid", DataType::BigInt, false),
12196 ColumnSchema::new("proname", DataType::Text, false),
12197 ColumnSchema::new("pronamespace", DataType::BigInt, false),
12198 ColumnSchema::new("prokind", DataType::Text, false),
12199 ColumnSchema::new("pronargs", DataType::Int, false),
12200 ColumnSchema::new("prorettype", DataType::BigInt, false),
12201 ];
12202 let funcs: &[(i64, &str, &str, i32, i64)] = &[
12205 (1318, "length", "f", 1, 23),
12207 (871, "upper", "f", 1, 25),
12208 (870, "lower", "f", 1, 25),
12209 (936, "substring", "f", 3, 25),
12210 (937, "substring", "f", 2, 25),
12211 (3055, "btrim", "f", 1, 25),
12212 (885, "btrim", "f", 2, 25),
12213 (3056, "ltrim", "f", 1, 25),
12214 (875, "ltrim", "f", 2, 25),
12215 (3057, "rtrim", "f", 1, 25),
12216 (876, "rtrim", "f", 2, 25),
12217 (1397, "abs", "f", 1, 23),
12218 (1396, "abs", "f", 1, 20),
12219 (1606, "round", "f", 1, 1700),
12220 (1707, "round", "f", 2, 1700),
12221 (2308, "ceil", "f", 1, 701),
12222 (2309, "ceiling", "f", 1, 701),
12223 (2310, "floor", "f", 1, 701),
12224 (1376, "sqrt", "f", 1, 701),
12225 (1369, "ln", "f", 1, 701),
12226 (1373, "exp", "f", 1, 701),
12227 (1368, "power", "f", 2, 701),
12228 (2228, "random", "f", 0, 701),
12229 (1299, "now", "f", 0, 1184),
12231 (1274, "current_timestamp", "f", 0, 1184),
12232 (1140, "current_date", "f", 0, 1082),
12233 (2050, "current_time", "f", 0, 1083),
12234 (1158, "date_trunc", "f", 2, 1184),
12235 (1171, "date_part", "f", 2, 701),
12236 (1172, "age", "f", 1, 1186),
12237 (936, "to_char", "f", 2, 25),
12238 (861, "current_database", "f", 0, 19),
12240 (745, "current_user", "f", 0, 19),
12241 (745, "session_user", "f", 0, 19),
12242 (1402, "current_schema", "f", 0, 19),
12243 (3058, "concat", "f", -1, 25),
12245 (3059, "concat_ws", "f", -1, 25),
12246 (3539, "format", "f", -1, 25),
12247 (2877, "pg_typeof", "f", 1, 2206),
12249 (3198, "json_build_object", "f", -1, 114),
12251 (3199, "jsonb_build_object", "f", -1, 3802),
12252 (3271, "json_build_array", "f", -1, 114),
12253 (3272, "jsonb_build_array", "f", -1, 3802),
12254 (3253, "gen_random_uuid", "f", 0, 2950),
12256 (3252, "uuid_generate_v4", "f", 0, 2950),
12257 (2147, "count", "a", 0, 20),
12259 (2803, "count", "a", -1, 20),
12260 (2116, "max", "a", 1, 23),
12261 (2132, "min", "a", 1, 23),
12262 (2108, "sum", "a", 1, 20),
12263 (2100, "avg", "a", 1, 1700),
12264 (2517, "string_agg", "a", 2, 25),
12265 (2747, "array_agg", "a", 1, 1009),
12266 (2517, "bool_and", "a", 1, 16),
12267 (2518, "bool_or", "a", 1, 16),
12268 (2519, "every", "a", 1, 16),
12269 (3100, "row_number", "w", 0, 20),
12271 (3101, "rank", "w", 0, 20),
12272 (3102, "dense_rank", "w", 0, 20),
12273 (3103, "percent_rank", "w", 0, 701),
12274 (3104, "cume_dist", "w", 0, 701),
12275 (3105, "lag", "w", -1, 2283),
12276 (3106, "lead", "w", -1, 2283),
12277 (3107, "first_value", "w", 1, 2283),
12278 (3108, "last_value", "w", 1, 2283),
12279 (3109, "nth_value", "w", 2, 2283),
12280 ];
12281 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
12282 for &(oid, name, kind, nargs, rettype) in funcs {
12283 rows.push(Row::new(alloc::vec![
12284 Value::BigInt(oid),
12285 Value::Text(name.into()),
12286 Value::BigInt(11),
12287 Value::Text(kind.into()),
12288 Value::Int(nargs),
12289 Value::BigInt(rettype),
12290 ]));
12291 }
12292 (schema, rows)
12293}
12294
12295fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12301 let schema = alloc::vec![
12302 ColumnSchema::new("user", DataType::Text, false),
12303 ColumnSchema::new("host", DataType::Text, false),
12304 ColumnSchema::new("select_priv", DataType::Text, false),
12305 ];
12306 let mut rows: Vec<Row> = Vec::new();
12307 rows.push(Row::new(alloc::vec![
12308 Value::Text("root".into()),
12309 Value::Text("localhost".into()),
12310 Value::Text("Y".into()),
12311 ]));
12312 for (name, _) in engine.users.iter() {
12313 if name != "root" {
12314 rows.push(Row::new(alloc::vec![
12315 Value::Text(name.to_string()),
12316 Value::Text("%".into()),
12317 Value::Text("Y".into()),
12318 ]));
12319 }
12320 }
12321 (schema, rows)
12322}
12323
12324fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
12329 let schema = alloc::vec![
12330 ColumnSchema::new("host", DataType::Text, false),
12331 ColumnSchema::new("db", DataType::Text, false),
12332 ColumnSchema::new("user", DataType::Text, false),
12333 ColumnSchema::new("select_priv", DataType::Text, false),
12334 ];
12335 let rows = alloc::vec![Row::new(alloc::vec![
12336 Value::Text("localhost".into()),
12337 Value::Text("postgres".into()),
12338 Value::Text("root".into()),
12339 Value::Text("Y".into()),
12340 ])];
12341 (schema, rows)
12342}
12343
12344fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12357 let schema = alloc::vec![
12358 ColumnSchema::new("constraint_name", DataType::Text, false),
12359 ColumnSchema::new("table_name", DataType::Text, false),
12360 ColumnSchema::new("column_name", DataType::Text, false),
12361 ColumnSchema::new("ordinal_position", DataType::Int, false),
12362 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12363 ColumnSchema::new("referenced_column_name", DataType::Text, false),
12364 ];
12365 let mut rows: Vec<Row> = Vec::new();
12366 for tname in cat.table_names() {
12367 let Some(t) = cat.get(&tname) else { continue };
12368 let cols = &t.schema().columns;
12369 let col_name_at = |pos: usize| -> String {
12370 cols.get(pos)
12371 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12372 };
12373 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12375 let conname = fk
12376 .name
12377 .clone()
12378 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12379 for (i, (&local, &parent)) in fk
12380 .local_columns
12381 .iter()
12382 .zip(fk.parent_columns.iter())
12383 .enumerate()
12384 {
12385 let parent_name = cat
12386 .get(&fk.parent_table)
12387 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
12388 .unwrap_or_else(|| alloc::format!("col{parent}"));
12389 #[allow(clippy::cast_possible_wrap)]
12390 let ordinal = (i + 1) as i32;
12391 rows.push(Row::new(alloc::vec![
12392 Value::Text(conname.clone()),
12393 Value::Text(tname.clone()),
12394 Value::Text(col_name_at(local)),
12395 Value::Int(ordinal),
12396 Value::Text(fk.parent_table.clone()),
12397 Value::Text(parent_name),
12398 ]));
12399 }
12400 }
12401 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12403 let conname = if uc.is_primary_key {
12404 alloc::format!("{}_pkey", tname)
12405 } else {
12406 alloc::format!("{}_uniq{ci}", tname)
12407 };
12408 for (i, &local) in uc.columns.iter().enumerate() {
12409 #[allow(clippy::cast_possible_wrap)]
12410 let ordinal = (i + 1) as i32;
12411 rows.push(Row::new(alloc::vec![
12412 Value::Text(conname.clone()),
12413 Value::Text(tname.clone()),
12414 Value::Text(col_name_at(local)),
12415 Value::Int(ordinal),
12416 Value::Text(String::new()),
12417 Value::Text(String::new()),
12418 ]));
12419 }
12420 }
12421 }
12422 (schema, rows)
12423}
12424
12425fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12428 let schema = alloc::vec![
12429 ColumnSchema::new("constraint_name", DataType::Text, false),
12430 ColumnSchema::new("table_name", DataType::Text, false),
12431 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12432 ColumnSchema::new("update_rule", DataType::Text, false),
12433 ColumnSchema::new("delete_rule", DataType::Text, false),
12434 ];
12435 fn rule_name(a: spg_storage::FkAction) -> &'static str {
12436 match a {
12437 spg_storage::FkAction::Cascade => "CASCADE",
12438 spg_storage::FkAction::SetNull => "SET NULL",
12439 spg_storage::FkAction::SetDefault => "SET DEFAULT",
12440 spg_storage::FkAction::Restrict => "RESTRICT",
12441 spg_storage::FkAction::NoAction => "NO ACTION",
12442 }
12443 }
12444 let mut rows: Vec<Row> = Vec::new();
12445 for tname in cat.table_names() {
12446 let Some(t) = cat.get(&tname) else { continue };
12447 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12448 let conname = fk
12449 .name
12450 .clone()
12451 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12452 rows.push(Row::new(alloc::vec![
12453 Value::Text(conname),
12454 Value::Text(tname.clone()),
12455 Value::Text(fk.parent_table.clone()),
12456 Value::Text(rule_name(fk.on_update).into()),
12457 Value::Text(rule_name(fk.on_delete).into()),
12458 ]));
12459 }
12460 }
12461 (schema, rows)
12462}
12463
12464fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12468 let schema = alloc::vec![
12469 ColumnSchema::new("table_name", DataType::Text, false),
12470 ColumnSchema::new("index_name", DataType::Text, false),
12471 ColumnSchema::new("column_name", DataType::Text, false),
12472 ColumnSchema::new("seq_in_index", DataType::Int, false),
12473 ColumnSchema::new("non_unique", DataType::Int, false),
12474 ColumnSchema::new("index_type", DataType::Text, false),
12475 ];
12476 let mut rows: Vec<Row> = Vec::new();
12477 for tname in cat.table_names() {
12478 let Some(t) = cat.get(&tname) else { continue };
12479 for idx in t.indices() {
12480 let col = t
12481 .schema()
12482 .columns
12483 .get(idx.column_position)
12484 .map_or("?".into(), |c| c.name.clone());
12485 rows.push(Row::new(alloc::vec![
12486 Value::Text(tname.clone()),
12487 Value::Text(idx.name.clone()),
12488 Value::Text(col),
12489 Value::Int(1),
12490 Value::Int(i32::from(!idx.is_unique)),
12491 Value::Text("BTREE".into()),
12492 ]));
12493 }
12494 }
12495 (schema, rows)
12496}
12497
12498fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
12502 let schema = alloc::vec![
12503 ColumnSchema::new("routine_name", DataType::Text, false),
12504 ColumnSchema::new("routine_type", DataType::Text, false),
12505 ColumnSchema::new("data_type", DataType::Text, false),
12506 ];
12507 (schema, Vec::new())
12508}
12509
12510fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12525 let schema = alloc::vec![
12526 ColumnSchema::new("conname", DataType::Text, false),
12527 ColumnSchema::new("contype", DataType::Text, false),
12528 ColumnSchema::new("conrelid", DataType::Text, false),
12529 ColumnSchema::new("confrelid", DataType::Text, false),
12530 ColumnSchema::new("conkey", DataType::Text, false),
12531 ColumnSchema::new("confkey", DataType::Text, false),
12532 ];
12533 let mut rows: Vec<Row> = Vec::new();
12534 for tname in cat.table_names() {
12535 let Some(t) = cat.get(&tname) else { continue };
12536 let cols = &t.schema().columns;
12537 let col_name_at = |pos: usize| -> String {
12538 cols.get(pos)
12539 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12540 };
12541 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12543 let kind = if uc.is_primary_key { "p" } else { "u" };
12544 let conname = if uc.is_primary_key {
12545 alloc::format!("{}_pkey", tname)
12546 } else {
12547 alloc::format!("{}_uniq{ci}", tname)
12548 };
12549 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
12550 rows.push(Row::new(alloc::vec![
12551 Value::Text(conname),
12552 Value::Text(kind.into()),
12553 Value::Text(tname.clone()),
12554 Value::Text(String::new()),
12555 Value::Text(conkey.join(",")),
12556 Value::Text(String::new()),
12557 ]));
12558 }
12559 for idx in t.indices() {
12564 if !idx.is_unique {
12565 continue;
12566 }
12567 let is_primary = idx.name.ends_with("_pkey");
12568 let conname = idx.name.clone();
12569 let kind = if is_primary { "p" } else { "u" };
12570 let col_name = col_name_at(idx.column_position);
12571 let already = t
12574 .schema()
12575 .uniqueness_constraints
12576 .iter()
12577 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
12578 if already {
12579 continue;
12580 }
12581 rows.push(Row::new(alloc::vec![
12582 Value::Text(conname),
12583 Value::Text(kind.into()),
12584 Value::Text(tname.clone()),
12585 Value::Text(String::new()),
12586 Value::Text(col_name),
12587 Value::Text(String::new()),
12588 ]));
12589 }
12590 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12592 let conname = fk
12593 .name
12594 .clone()
12595 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12596 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
12597 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
12600 fk.parent_columns
12601 .iter()
12602 .map(|&p| {
12603 parent
12604 .schema()
12605 .columns
12606 .get(p)
12607 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
12608 })
12609 .collect()
12610 } else {
12611 fk.parent_columns
12612 .iter()
12613 .map(|p| alloc::format!("col{p}"))
12614 .collect()
12615 };
12616 rows.push(Row::new(alloc::vec![
12617 Value::Text(conname),
12618 Value::Text("f".into()),
12619 Value::Text(tname.clone()),
12620 Value::Text(fk.parent_table.clone()),
12621 Value::Text(conkey.join(",")),
12622 Value::Text(confkey.join(",")),
12623 ]));
12624 }
12625 }
12626 (schema, rows)
12627}
12628
12629fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12634 let schema = alloc::vec![
12635 ColumnSchema::new("oid", DataType::BigInt, false),
12636 ColumnSchema::new("datname", DataType::Text, false),
12637 ColumnSchema::new("datdba", DataType::BigInt, false),
12638 ColumnSchema::new("encoding", DataType::Int, false),
12639 ColumnSchema::new("datcollate", DataType::Text, false),
12640 ];
12641 let rows = alloc::vec![Row::new(alloc::vec![
12642 Value::BigInt(16384),
12643 Value::Text("postgres".into()),
12644 Value::BigInt(10),
12645 Value::Int(6), Value::Text("en_US.UTF-8".into()),
12647 ])];
12648 (schema, rows)
12649}
12650
12651fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12656 let schema = alloc::vec![
12657 ColumnSchema::new("oid", DataType::BigInt, false),
12658 ColumnSchema::new("rolname", DataType::Text, false),
12659 ColumnSchema::new("rolsuper", DataType::Bool, false),
12660 ColumnSchema::new("rolinherit", DataType::Bool, false),
12661 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
12662 ];
12663 let mut rows: Vec<Row> = Vec::new();
12664 let oid: i64 = 10;
12665 for (i, (name, _)) in engine.users.iter().enumerate() {
12666 rows.push(Row::new(alloc::vec![
12667 Value::BigInt(oid + (i as i64) + 1),
12668 Value::Text(name.to_string()),
12669 Value::Bool(false),
12670 Value::Bool(true),
12671 Value::Bool(true),
12672 ]));
12673 }
12674 if !rows
12677 .iter()
12678 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
12679 {
12680 rows.insert(
12681 0,
12682 Row::new(alloc::vec![
12683 Value::BigInt(10),
12684 Value::Text("postgres".into()),
12685 Value::Bool(true),
12686 Value::Bool(true),
12687 Value::Bool(true),
12688 ]),
12689 );
12690 }
12691 (schema, rows)
12692}
12693
12694fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
12703 let schema = alloc::vec![
12704 ColumnSchema::new("oid", DataType::BigInt, false),
12705 ColumnSchema::new("extname", DataType::Text, false),
12706 ColumnSchema::new("extversion", DataType::Text, false),
12707 ColumnSchema::new("extnamespace", DataType::Text, false),
12708 ];
12709 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
12710 let rows = exts
12711 .iter()
12712 .enumerate()
12713 .map(|(i, (name, ver))| {
12714 Row::new(alloc::vec![
12715 Value::BigInt(16384 + i as i64),
12716 Value::Text((*name).into()),
12717 Value::Text((*ver).into()),
12718 Value::Text("pg_catalog".into()),
12719 ])
12720 })
12721 .collect();
12722 (schema, rows)
12723}
12724
12725fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12726 let schema = alloc::vec![
12727 ColumnSchema::new("schemaname", DataType::Text, false),
12728 ColumnSchema::new("viewname", DataType::Text, false),
12729 ColumnSchema::new("definition", DataType::Text, false),
12730 ];
12731 let mut rows: Vec<Row> = Vec::new();
12732 for (name, def) in cat.views() {
12733 rows.push(Row::new(alloc::vec![
12734 Value::Text("public".into()),
12735 Value::Text(name.clone()),
12736 Value::Text(def.body.clone()),
12737 ]));
12738 }
12739 (schema, rows)
12740}
12741
12742fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12748 let schema = alloc::vec![
12749 ColumnSchema::new("name", DataType::Text, false),
12750 ColumnSchema::new("setting", DataType::Text, false),
12751 ColumnSchema::new("category", DataType::Text, false),
12752 ];
12753 let mut rows: Vec<Row> = Vec::new();
12754 let defaults: &[(&str, &str, &str)] = &[
12756 ("server_version", "16.0 (spg)", "Preset Options"),
12757 ("server_encoding", "UTF8", "Client Connection Defaults"),
12758 ("client_encoding", "UTF8", "Client Connection Defaults"),
12759 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
12760 ("TimeZone", "UTC", "Client Connection Defaults"),
12761 ("standard_conforming_strings", "on", "Compatibility"),
12762 ("integer_datetimes", "on", "Compatibility"),
12763 ("max_connections", "100", "Connections and Authentication"),
12764 ];
12765 for &(name, val, cat) in defaults {
12766 rows.push(Row::new(alloc::vec![
12767 Value::Text(name.into()),
12768 Value::Text(val.into()),
12769 Value::Text(cat.into()),
12770 ]));
12771 }
12772 for (k, v) in &engine.session_params {
12774 if !defaults
12775 .iter()
12776 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
12777 {
12778 rows.push(Row::new(alloc::vec![
12779 Value::Text(k.clone()),
12780 Value::Text(v.clone()),
12781 Value::Text("Session".into()),
12782 ]));
12783 }
12784 }
12785 (schema, rows)
12786}
12787
12788fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12799 let schema = alloc::vec![
12800 ColumnSchema::new("schemaname", DataType::Text, false),
12801 ColumnSchema::new("tablename", DataType::Text, false),
12802 ColumnSchema::new("indexname", DataType::Text, false),
12803 ColumnSchema::new("indexdef", DataType::Text, false),
12804 ];
12805 let mut rows: Vec<Row> = Vec::new();
12806 for tname in cat.table_names() {
12807 let Some(t) = cat.get(&tname) else { continue };
12808 for idx in t.indices() {
12809 let col_name = t
12810 .schema()
12811 .columns
12812 .get(idx.column_position)
12813 .map_or("?".into(), |c| c.name.clone());
12814 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
12815 let indexdef = alloc::format!(
12816 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
12817 idx.name,
12818 tname,
12819 col_name
12820 );
12821 rows.push(Row::new(alloc::vec![
12822 Value::Text("public".into()),
12823 Value::Text(tname.clone()),
12824 Value::Text(idx.name.clone()),
12825 Value::Text(indexdef),
12826 ]));
12827 }
12828 }
12829 (schema, rows)
12830}
12831
12832fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12844 let schema = alloc::vec![
12845 ColumnSchema::new("indexrelid", DataType::BigInt, false),
12846 ColumnSchema::new("indrelid", DataType::BigInt, false),
12847 ColumnSchema::new("indnatts", DataType::Int, false),
12848 ColumnSchema::new("indisunique", DataType::Bool, false),
12849 ColumnSchema::new("indisprimary", DataType::Bool, false),
12850 ];
12851 let mut rows: Vec<Row> = Vec::new();
12852 let mut idx_oid: i64 = 100_000;
12853 for (table_idx, tname) in cat.table_names().iter().enumerate() {
12854 let Some(t) = cat.get(tname) else { continue };
12855 for idx in t.indices() {
12856 idx_oid += 1;
12857 #[allow(clippy::cast_possible_wrap)]
12858 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
12859 let is_primary = idx.name.ends_with("_pkey");
12862 rows.push(Row::new(alloc::vec![
12863 Value::BigInt(idx_oid),
12864 Value::BigInt((table_idx + 1) as i64),
12865 Value::Int(nattrs),
12866 Value::Bool(idx.is_unique),
12867 Value::Bool(is_primary),
12868 ]));
12869 }
12870 }
12871 (schema, rows)
12872}
12873
12874fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12879 let schema = alloc::vec![
12880 ColumnSchema::new("oid", DataType::BigInt, false),
12881 ColumnSchema::new("nspname", DataType::Text, false),
12882 ColumnSchema::new("nspowner", DataType::BigInt, false),
12883 ];
12884 let rows = alloc::vec![
12885 Row::new(alloc::vec![
12886 Value::BigInt(11),
12887 Value::Text("pg_catalog".into()),
12888 Value::BigInt(10),
12889 ]),
12890 Row::new(alloc::vec![
12891 Value::BigInt(2200),
12892 Value::Text("public".into()),
12893 Value::BigInt(10),
12894 ]),
12895 Row::new(alloc::vec![
12896 Value::BigInt(13000),
12897 Value::Text("information_schema".into()),
12898 Value::BigInt(10),
12899 ]),
12900 ];
12901 (schema, rows)
12902}
12903
12904fn materialise_meta_view(
12907 catalog: &mut Catalog,
12908 name: &str,
12909 columns: Vec<ColumnSchema>,
12910 rows: Vec<Row>,
12911) -> Result<(), EngineError> {
12912 let schema = TableSchema::new(name.to_string(), columns);
12913 catalog.create_table(schema).map_err(EngineError::Storage)?;
12914 let table = catalog
12915 .get_mut(name)
12916 .expect("just-created meta view must exist");
12917 for row in rows {
12918 table.insert(row).map_err(EngineError::Storage)?;
12919 }
12920 Ok(())
12921}
12922
12923fn collect_view_refs(
12936 tref: &spg_sql::ast::TableRef,
12937 cat: &spg_storage::Catalog,
12938 into: &mut Vec<String>,
12939) {
12940 if cat.views().contains_key(&tref.name)
12941 && cat.get(&tref.name).is_none()
12942 && !into.iter().any(|n| n == &tref.name)
12943 {
12944 into.push(tref.name.clone());
12945 }
12946}
12947
12948fn select_references_meta_view(stmt: &SelectStatement) -> bool {
12949 fn is_meta(name: &str) -> bool {
12950 name.starts_with("__spg_info_")
12951 || name.starts_with("__spg_pg_")
12952 || name.starts_with("__spg_mysql_")
12953 }
12954 if let Some(from) = &stmt.from {
12955 if is_meta(&from.primary.name) {
12956 return true;
12957 }
12958 for j in &from.joins {
12959 if is_meta(&j.table.name) {
12960 return true;
12961 }
12962 }
12963 }
12964 for cte in &stmt.ctes {
12965 if select_references_meta_view(&cte.body) {
12966 return true;
12967 }
12968 }
12969 false
12970}
12971
12972fn collect_meta_view_names(
12977 stmt: &SelectStatement,
12978 into: &mut alloc::collections::BTreeSet<String>,
12979) {
12980 fn is_meta(name: &str) -> bool {
12981 name.starts_with("__spg_info_")
12982 || name.starts_with("__spg_pg_")
12983 || name.starts_with("__spg_mysql_")
12984 }
12985 if let Some(from) = &stmt.from {
12986 if is_meta(&from.primary.name) {
12987 into.insert(from.primary.name.clone());
12988 }
12989 for j in &from.joins {
12990 if is_meta(&j.table.name) {
12991 into.insert(j.table.name.clone());
12992 }
12993 }
12994 }
12995 for cte in &stmt.ctes {
12996 collect_meta_view_names(&cte.body, into);
12997 }
12998}
12999
13000fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
13001 let mut out = columns.to_vec();
13002 for (col_idx, col) in out.iter_mut().enumerate() {
13003 if col.ty != DataType::Text {
13004 continue;
13005 }
13006 let mut inferred: Option<DataType> = None;
13007 let mut all_null = true;
13008 for row in rows {
13009 let Some(v) = row.values.get(col_idx) else {
13010 continue;
13011 };
13012 let ty = match v {
13013 Value::Null => continue,
13014 Value::SmallInt(_) => DataType::SmallInt,
13015 Value::Int(_) => DataType::Int,
13016 Value::BigInt(_) => DataType::BigInt,
13017 Value::Float(_) => DataType::Float,
13018 Value::Bool(_) => DataType::Bool,
13019 Value::Vector(_) => DataType::Vector {
13020 dim: 0,
13021 encoding: VecEncoding::F32,
13022 },
13023 _ => DataType::Text,
13024 };
13025 all_null = false;
13026 inferred = Some(match inferred {
13027 None => ty,
13028 Some(prev) if prev == ty => prev,
13029 Some(_) => DataType::Text,
13030 });
13031 }
13032 if let Some(t) = inferred {
13033 col.ty = t;
13034 col.nullable = true;
13035 } else if all_null {
13036 col.nullable = true;
13037 }
13038 }
13039 out
13040}
13041
13042#[allow(clippy::too_many_lines, clippy::format_push_string)]
13047fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
13064 use alloc::collections::BTreeSet;
13065 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
13066 let mut out: Vec<String> = Vec::new();
13067 let cat = engine.active_catalog();
13068 let Some(from) = &stmt.from else {
13072 return out;
13073 };
13074 let mut tables: Vec<String> = Vec::new();
13075 tables.push(from.primary.name.clone());
13076 for j in &from.joins {
13077 tables.push(j.table.name.clone());
13078 }
13079 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
13082 if let Some(w) = &stmt.where_ {
13083 collect_column_refs(w, &mut col_refs);
13084 }
13085 for j in &from.joins {
13086 if let Some(on) = &j.on {
13087 collect_column_refs(on, &mut col_refs);
13088 }
13089 }
13090 for cn in &col_refs {
13091 let owner: Option<String> = if let Some(q) = &cn.qualifier {
13094 tables.iter().find(|t| t == &q).cloned()
13095 } else {
13096 tables.iter().find_map(|t| {
13097 cat.get(t).and_then(|tbl| {
13098 if tbl.schema().column_position(&cn.name).is_some() {
13099 Some(t.clone())
13100 } else {
13101 None
13102 }
13103 })
13104 })
13105 };
13106 let Some(owner) = owner else {
13107 continue;
13108 };
13109 let Some(tbl) = cat.get(&owner) else {
13110 continue;
13111 };
13112 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
13113 continue;
13114 };
13115 let already_indexed = tbl.indices().iter().any(|i| {
13118 matches!(i.kind, spg_storage::IndexKind::BTree(_))
13119 && i.column_position == col_pos
13120 && i.expression.is_none()
13121 && i.partial_predicate.is_none()
13122 });
13123 if already_indexed {
13124 continue;
13125 }
13126 if seen.insert((owner.clone(), cn.name.clone())) {
13127 out.push(alloc::format!(
13128 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
13129 owner,
13130 cn.name,
13131 owner,
13132 cn.name
13133 ));
13134 }
13135 }
13136 out
13137}
13138
13139fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
13142 match expr {
13143 Expr::Column(cn) => out.push(cn.clone()),
13144 Expr::FunctionCall { args, .. } => {
13145 for a in args {
13146 collect_column_refs(a, out);
13147 }
13148 }
13149 Expr::Binary { lhs, rhs, .. } => {
13150 collect_column_refs(lhs, out);
13151 collect_column_refs(rhs, out);
13152 }
13153 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
13154 _ => {}
13155 }
13156}
13157
13158fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
13159 let catalog = engine.active_catalog();
13160 let cold_ids = catalog.cold_segment_ids_global();
13161 let any_cold = !cold_ids.is_empty();
13162 let cold_ids_repr = if any_cold {
13163 let mut s = alloc::string::String::from("[");
13164 for (i, id) in cold_ids.iter().enumerate() {
13165 if i > 0 {
13166 s.push(',');
13167 }
13168 s.push_str(&alloc::format!("{id}"));
13169 }
13170 s.push(']');
13171 s
13172 } else {
13173 alloc::string::String::new()
13174 };
13175 for (idx, line) in lines.iter_mut().enumerate() {
13176 let trimmed = line.trim_start();
13177 let is_top_level = idx == 0;
13178 if is_top_level {
13179 line.push_str(&alloc::format!(" (rows={total_rows})"));
13180 continue;
13181 }
13182 if let Some(rest) = trimmed.strip_prefix("From: ") {
13183 let (name, scan_kind) = match rest.split_once(" [") {
13184 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
13185 None => (rest.trim(), ""),
13186 };
13187 let bare = name.split_whitespace().next().unwrap_or(name);
13188 let hot = catalog.get(bare).map(|t| t.rows().len());
13189 let annot = match (hot, scan_kind) {
13194 (Some(h), "full scan") => {
13195 let mut s = alloc::format!(" (hot_rows={h}");
13196 if any_cold {
13197 s.push_str(&alloc::format!(
13198 ", cold_tier=present, cold_segments={cold_ids_repr}"
13199 ));
13200 }
13201 s.push(')');
13202 s
13203 }
13204 (Some(h), "index seek") => {
13205 let mut s = alloc::format!(" (hot_rows≤{h}");
13206 if any_cold {
13207 s.push_str(&alloc::format!(
13208 ", cold_tier=present, cold_segments={cold_ids_repr}"
13209 ));
13210 }
13211 s.push(')');
13212 s
13213 }
13214 _ => " (rows=—)".to_string(),
13215 };
13216 line.push_str(&annot);
13217 continue;
13218 }
13219 line.push_str(" (rows=—)");
13221 }
13222}
13223
13224fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
13225 let pad = " ".repeat(depth);
13226 let top = if !stmt.ctes.is_empty() {
13228 if stmt.ctes.iter().any(|c| c.recursive) {
13229 "CTEScan (WITH RECURSIVE)"
13230 } else {
13231 "CTEScan (WITH)"
13232 }
13233 } else if !stmt.unions.is_empty() {
13234 "UnionScan"
13235 } else if select_has_window(stmt) {
13236 "WindowAgg"
13237 } else if aggregate::uses_aggregate(stmt) {
13238 "Aggregate"
13239 } else if stmt.distinct {
13240 "Distinct"
13241 } else if stmt.from.is_some() {
13242 "TableScan"
13243 } else {
13244 "Result"
13245 };
13246 out.push(alloc::format!("{pad}{top}"));
13247 let child = " ".repeat(depth + 1);
13248 for cte in &stmt.ctes {
13250 let head = if cte.recursive {
13251 alloc::format!("{child}CTE (recursive): {}", cte.name)
13252 } else {
13253 alloc::format!("{child}CTE: {}", cte.name)
13254 };
13255 out.push(head);
13256 explain_select(&cte.body, engine, depth + 2, out);
13257 }
13258 if let Some(from) = &stmt.from {
13260 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
13261 if let Some(alias) = &from.primary.alias {
13262 tag.push_str(&alloc::format!(" AS {alias}"));
13263 }
13264 if let Some(w) = &stmt.where_
13267 && let Some(table) = engine.active_catalog().get(&from.primary.name)
13268 {
13269 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
13270 let cols = &table.schema().columns;
13271 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
13272 tag.push_str(" [index seek]");
13273 } else {
13274 tag.push_str(" [full scan]");
13275 }
13276 } else {
13277 tag.push_str(" [full scan]");
13278 }
13279 out.push(tag);
13280 for j in &from.joins {
13281 let kind = match j.kind {
13282 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
13283 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
13284 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
13285 };
13286 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
13287 if let Some(alias) = &j.table.alias {
13288 s.push_str(&alloc::format!(" AS {alias}"));
13289 }
13290 if j.on.is_some() {
13291 s.push_str(" (ON …)");
13292 }
13293 out.push(s);
13294 }
13295 }
13296 if let Some(w) = &stmt.where_ {
13298 let mut s = alloc::format!("{child}Filter: {w}");
13299 if expr_has_subquery(w) {
13300 s.push_str(" [subquery]");
13301 }
13302 out.push(s);
13303 }
13304 if let Some(gs) = &stmt.group_by {
13305 let mut parts = Vec::new();
13306 for g in gs {
13307 parts.push(alloc::format!("{g}"));
13308 }
13309 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
13310 }
13311 if let Some(h) = &stmt.having {
13312 out.push(alloc::format!("{child}Having: {h}"));
13313 }
13314 for o in &stmt.order_by {
13315 let dir = if o.desc { "DESC" } else { "ASC" };
13316 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
13317 }
13318 if let Some(lim) = stmt.limit {
13319 out.push(alloc::format!("{child}Limit: {lim}"));
13320 }
13321 if let Some(off) = stmt.offset {
13322 out.push(alloc::format!("{child}Offset: {off}"));
13323 }
13324 if stmt
13326 .items
13327 .iter()
13328 .any(|it| matches!(it, SelectItem::Wildcard))
13329 {
13330 out.push(alloc::format!("{child}Project: *"));
13331 } else {
13332 out.push(alloc::format!(
13333 "{child}Project: {} item(s)",
13334 stmt.items.len()
13335 ));
13336 }
13337 for (kind, peer) in &stmt.unions {
13339 let label = match kind {
13340 UnionKind::All => "UNION ALL",
13341 UnionKind::Distinct => "UNION",
13342 };
13343 out.push(alloc::format!("{child}{label}"));
13344 explain_select(peer, engine, depth + 2, out);
13345 }
13346}
13347
13348fn is_correlation_error(e: &EngineError) -> bool {
13353 matches!(
13354 e,
13355 EngineError::Eval(
13356 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
13357 )
13358 )
13359}
13360
13361struct JoinedPeer<'a> {
13372 eager_rows: Option<Vec<Row>>,
13373 cols: Vec<ColumnSchema>,
13374 alias: String,
13375 kind: JoinKind,
13376 on: Option<&'a Expr>,
13377 lateral: Option<&'a SelectStatement>,
13378 join_table: Option<String>,
13381}
13382
13383fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
13390 match expr {
13391 Expr::Column(c) => c.name.clone(),
13393 Expr::FunctionCall { name, .. } => name.clone(),
13396 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
13398 _ => alloc::format!("column{}", idx + 1),
13400 }
13401}
13402
13403fn substitute_outer_columns_multi(
13410 stmt: &mut SelectStatement,
13411 outer_row: &Row,
13412 outer_schema: &[ColumnSchema],
13413) {
13414 substitute_outer_in_select(stmt, outer_row, outer_schema);
13415}
13416
13417fn substitute_outer_in_select(
13418 stmt: &mut SelectStatement,
13419 outer_row: &Row,
13420 outer_schema: &[ColumnSchema],
13421) {
13422 for item in &mut stmt.items {
13423 if let SelectItem::Expr { expr, .. } = item {
13424 substitute_outer_in_expr(expr, outer_row, outer_schema);
13425 }
13426 }
13427 if let Some(w) = &mut stmt.where_ {
13428 substitute_outer_in_expr(w, outer_row, outer_schema);
13429 }
13430 if let Some(gs) = &mut stmt.group_by {
13431 for g in gs {
13432 substitute_outer_in_expr(g, outer_row, outer_schema);
13433 }
13434 }
13435 if let Some(h) = &mut stmt.having {
13436 substitute_outer_in_expr(h, outer_row, outer_schema);
13437 }
13438 for o in &mut stmt.order_by {
13439 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
13440 }
13441 for (_, peer) in &mut stmt.unions {
13442 substitute_outer_in_select(peer, outer_row, outer_schema);
13443 }
13444}
13445
13446fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
13447 if let Expr::Column(c) = e
13448 && let Some(qual) = &c.qualifier
13449 {
13450 let composite = alloc::format!("{qual}.{}", c.name);
13451 if let Some(idx) = outer_schema
13452 .iter()
13453 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13454 {
13455 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
13456 if let Ok(lit) = value_to_literal_expr(v) {
13457 *e = lit;
13458 return;
13459 }
13460 }
13461 }
13462 match e {
13463 Expr::Binary { lhs, rhs, .. } => {
13464 substitute_outer_in_expr(lhs, outer_row, outer_schema);
13465 substitute_outer_in_expr(rhs, outer_row, outer_schema);
13466 }
13467 Expr::Unary { expr: inner, .. } => {
13468 substitute_outer_in_expr(inner, outer_row, outer_schema);
13469 }
13470 Expr::FunctionCall { args, .. } => {
13471 for a in args {
13472 substitute_outer_in_expr(a, outer_row, outer_schema);
13473 }
13474 }
13475 Expr::Cast { expr: inner, .. } => {
13476 substitute_outer_in_expr(inner, outer_row, outer_schema);
13477 }
13478 Expr::Case {
13479 operand,
13480 branches,
13481 else_branch,
13482 } => {
13483 if let Some(op) = operand {
13484 substitute_outer_in_expr(op, outer_row, outer_schema);
13485 }
13486 for (cond, val) in branches {
13487 substitute_outer_in_expr(cond, outer_row, outer_schema);
13488 substitute_outer_in_expr(val, outer_row, outer_schema);
13489 }
13490 if let Some(e) = else_branch {
13491 substitute_outer_in_expr(e, outer_row, outer_schema);
13492 }
13493 }
13494 _ => {}
13495 }
13496}
13497
13498impl Engine {
13499 fn try_batch_correlated_scalar(
13508 &self,
13509 inner: &SelectStatement,
13510 cancel: CancelToken<'_>,
13511 ) -> Result<Option<memoize::GroupMap>, EngineError> {
13512 use spg_sql::ast::{BinOp, SelectItem as SI};
13513 if !inner.ctes.is_empty()
13514 || !inner.unions.is_empty()
13515 || inner.group_by.is_some()
13516 || inner.having.is_some()
13517 || inner.distinct
13518 || inner.items.len() != 1
13519 || inner.order_by.len() > 1
13520 || inner.offset.is_some()
13521 {
13522 return Ok(None);
13523 }
13524 if let Some(le) = inner.limit
13526 && le.as_literal() != Some(1)
13527 {
13528 return Ok(None);
13529 }
13530 let Some(from) = &inner.from else {
13531 return Ok(None);
13532 };
13533 if from.primary.lateral_subquery.is_some() || from.primary.unnest_expr.is_some() {
13534 return Ok(None);
13535 }
13536 let mut inner_aliases: Vec<String> = Vec::new();
13538 inner_aliases.push(
13539 from.primary
13540 .alias
13541 .clone()
13542 .unwrap_or_else(|| from.primary.name.clone()),
13543 );
13544 for j in &from.joins {
13545 if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
13546 return Ok(None);
13547 }
13548 inner_aliases.push(
13549 j.table
13550 .alias
13551 .clone()
13552 .unwrap_or_else(|| j.table.name.clone()),
13553 );
13554 }
13555 let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
13556 match &c.qualifier {
13557 Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
13558 None => false,
13559 }
13560 };
13561 let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
13562 match &c.qualifier {
13563 Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
13564 None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
13567 }
13568 };
13569 let all_inner = |e: &Expr| -> bool {
13572 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
13573 let mut subs: Vec<&SelectStatement> = Vec::new();
13574 visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
13575 subs.push(sub)
13576 });
13577 subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
13578 };
13579 let Some(w) = &inner.where_ else {
13580 return Ok(None);
13581 };
13582 let conjuncts = reorder::split_and_conjunctions(w);
13583 let mut corr: Option<(spg_sql::ast::ColumnName, spg_sql::ast::ColumnName)> = None; let mut rest: Vec<&Expr> = Vec::new();
13585 for c in conjuncts {
13586 if let Expr::Binary {
13587 lhs,
13588 op: BinOp::Eq,
13589 rhs,
13590 } = c
13591 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
13592 {
13593 let pair = if is_inner(a) && is_outer(b) {
13594 Some((a.clone(), b.clone()))
13595 } else if is_inner(b) && is_outer(a) {
13596 Some((b.clone(), a.clone()))
13597 } else {
13598 None
13599 };
13600 if let Some(p) = pair {
13601 if corr.is_some() {
13602 return Ok(None); }
13604 corr = Some(p);
13605 continue;
13606 }
13607 }
13608 if !all_inner(c) {
13609 return Ok(None);
13610 }
13611 rest.push(c);
13612 }
13613 let Some((inner_col, outer_col)) = corr else {
13614 return Ok(None);
13615 };
13616 let SI::Expr { expr: out_expr, .. } = &inner.items[0] else {
13617 return Ok(None);
13618 };
13619 if !all_inner(out_expr) {
13620 return Ok(None);
13621 }
13622 let order = inner.order_by.first();
13623 if let Some(o) = order
13624 && !all_inner(&o.expr)
13625 {
13626 return Ok(None);
13627 }
13628 let mut batch = inner.clone();
13631 batch.limit = None;
13632 batch.offset = None;
13633 batch.order_by = Vec::new();
13634 batch.where_ = rest
13635 .iter()
13636 .map(|e| (*e).clone())
13637 .reduce(|a, b| Expr::Binary {
13638 lhs: alloc::boxed::Box::new(a),
13639 op: BinOp::And,
13640 rhs: alloc::boxed::Box::new(b),
13641 });
13642 let mut items: Vec<SI> = alloc::vec![SI::Expr {
13643 expr: Expr::Column(inner_col),
13644 alias: None,
13645 }];
13646 if let Some(o) = order {
13647 items.push(SI::Expr {
13648 expr: o.expr.clone(),
13649 alias: None,
13650 });
13651 }
13652 items.push(SI::Expr {
13653 expr: out_expr.clone(),
13654 alias: None,
13655 });
13656 batch.items = items;
13657 let r = self.exec_select_cancel(&batch, cancel)?;
13658 let QueryResult::Rows { rows, .. } = r else {
13659 return Ok(None);
13660 };
13661 let has_order = order.is_some();
13662 let (desc, nf) = order
13663 .map(|o| (o.desc, o.nulls_first))
13664 .unwrap_or((false, None));
13665 let mut best: alloc::collections::BTreeMap<String, (Option<Value>, Value)> =
13666 alloc::collections::BTreeMap::new();
13667 for row in rows {
13668 let key_v = row.values.first().cloned().unwrap_or(Value::Null);
13669 if matches!(key_v, Value::Null) {
13670 continue;
13671 }
13672 let key = aggregate::encode_key(core::slice::from_ref(&key_v));
13673 let (ord_v, out_v) = if has_order {
13674 (
13675 Some(row.values.get(1).cloned().unwrap_or(Value::Null)),
13676 row.values.get(2).cloned().unwrap_or(Value::Null),
13677 )
13678 } else {
13679 (None, row.values.get(1).cloned().unwrap_or(Value::Null))
13680 };
13681 match best.get(&key) {
13682 None => {
13683 best.insert(key, (ord_v, out_v));
13684 }
13685 Some((cur_ord, _)) if has_order => {
13686 let cand = ord_v.clone().unwrap_or(Value::Null);
13690 let cur = cur_ord.clone().unwrap_or(Value::Null);
13691 if order_by_value_cmp(desc, nf, &cand, &cur) == core::cmp::Ordering::Less {
13692 best.insert(key, (ord_v, out_v));
13693 }
13694 }
13695 Some(_) => {} }
13697 }
13698 let map = best.into_iter().map(|(k, (_, v))| (k, v)).collect();
13699 Ok(Some((outer_col, map)))
13700 }
13701}
13702
13703fn collect_scalar_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
13707 match e {
13708 Expr::ScalarSubquery(s) => out.push(s),
13709 Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13710 Expr::Binary { lhs, rhs, .. } => {
13711 collect_scalar_subqueries(lhs, out);
13712 collect_scalar_subqueries(rhs, out);
13713 }
13714 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13715 collect_scalar_subqueries(expr, out);
13716 }
13717 Expr::Like { expr, pattern, .. } => {
13718 collect_scalar_subqueries(expr, out);
13719 collect_scalar_subqueries(pattern, out);
13720 }
13721 Expr::FunctionCall { args, .. } => {
13722 for a in args {
13723 collect_scalar_subqueries(a, out);
13724 }
13725 }
13726 Expr::AggregateOrdered { call, order_by, .. } => {
13727 collect_scalar_subqueries(call, out);
13728 for o in order_by {
13729 collect_scalar_subqueries(&o.expr, out);
13730 }
13731 }
13732 Expr::Case {
13733 operand,
13734 branches,
13735 else_branch,
13736 } => {
13737 if let Some(op) = operand {
13738 collect_scalar_subqueries(op, out);
13739 }
13740 for (w, t) in branches {
13741 collect_scalar_subqueries(w, out);
13742 collect_scalar_subqueries(t, out);
13743 }
13744 if let Some(eb) = else_branch {
13745 collect_scalar_subqueries(eb, out);
13746 }
13747 }
13748 Expr::ArraySubscript { target, index } => {
13749 collect_scalar_subqueries(target, out);
13750 collect_scalar_subqueries(index, out);
13751 }
13752 _ => {}
13753 }
13754}
13755
13756fn hollow_scalar_subqueries(e: &mut Expr) {
13759 match e {
13760 Expr::ScalarSubquery(s) => {
13761 let hollow = SelectStatement {
13762 items: Vec::new(),
13763 ..SelectStatement::default()
13764 };
13765 **s = hollow;
13766 }
13767 Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13768 Expr::Binary { lhs, rhs, .. } => {
13769 hollow_scalar_subqueries(lhs);
13770 hollow_scalar_subqueries(rhs);
13771 }
13772 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13773 hollow_scalar_subqueries(expr);
13774 }
13775 Expr::Like { expr, pattern, .. } => {
13776 hollow_scalar_subqueries(expr);
13777 hollow_scalar_subqueries(pattern);
13778 }
13779 Expr::FunctionCall { args, .. } => {
13780 for a in args.iter_mut() {
13781 hollow_scalar_subqueries(a);
13782 }
13783 }
13784 Expr::AggregateOrdered { call, order_by, .. } => {
13785 hollow_scalar_subqueries(call);
13786 for o in order_by.iter_mut() {
13787 hollow_scalar_subqueries(&mut o.expr);
13788 }
13789 }
13790 Expr::Case {
13791 operand,
13792 branches,
13793 else_branch,
13794 } => {
13795 if let Some(op) = operand {
13796 hollow_scalar_subqueries(op);
13797 }
13798 for (w, t) in branches.iter_mut() {
13799 hollow_scalar_subqueries(w);
13800 hollow_scalar_subqueries(t);
13801 }
13802 if let Some(eb) = else_branch {
13803 hollow_scalar_subqueries(eb);
13804 }
13805 }
13806 Expr::ArraySubscript { target, index } => {
13807 hollow_scalar_subqueries(target);
13808 hollow_scalar_subqueries(index);
13809 }
13810 _ => {}
13811 }
13812}
13813
13814fn splice_planned_subqueries(
13819 e: &mut Expr,
13820 plan: &[Option<alloc::rc::Rc<memoize::GroupMap>>],
13821 idx: &mut usize,
13822 row: &Row,
13823 ctx: &EvalContext<'_>,
13824) -> Result<bool, EngineError> {
13825 match e {
13826 Expr::ScalarSubquery(_) => {
13827 let Some(Some(gm)) = plan.get(*idx) else {
13828 return Ok(false);
13829 };
13830 *idx += 1;
13831 let (outer_col, map) = gm.as_ref();
13832 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
13833 .map_err(EngineError::Eval)?;
13834 let v = if matches!(key_v, Value::Null) {
13835 Value::Null
13836 } else {
13837 map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
13838 .cloned()
13839 .unwrap_or(Value::Null)
13840 };
13841 *e = value_to_literal_expr(v)?;
13842 Ok(true)
13843 }
13844 Expr::Exists { .. } | Expr::InSubquery { .. } => Ok(true),
13845 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_subqueries(lhs, plan, idx, row, ctx)?
13846 && splice_planned_subqueries(rhs, plan, idx, row, ctx)?),
13847 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13848 splice_planned_subqueries(expr, plan, idx, row, ctx)
13849 }
13850 Expr::Like { expr, pattern, .. } => {
13851 Ok(splice_planned_subqueries(expr, plan, idx, row, ctx)?
13852 && splice_planned_subqueries(pattern, plan, idx, row, ctx)?)
13853 }
13854 Expr::FunctionCall { args, .. } => {
13855 for a in args.iter_mut() {
13856 if !splice_planned_subqueries(a, plan, idx, row, ctx)? {
13857 return Ok(false);
13858 }
13859 }
13860 Ok(true)
13861 }
13862 Expr::AggregateOrdered { call, order_by, .. } => {
13863 if !splice_planned_subqueries(call, plan, idx, row, ctx)? {
13864 return Ok(false);
13865 }
13866 for o in order_by.iter_mut() {
13867 if !splice_planned_subqueries(&mut o.expr, plan, idx, row, ctx)? {
13868 return Ok(false);
13869 }
13870 }
13871 Ok(true)
13872 }
13873 Expr::Case {
13874 operand,
13875 branches,
13876 else_branch,
13877 } => {
13878 if let Some(op) = operand {
13879 if !splice_planned_subqueries(op, plan, idx, row, ctx)? {
13880 return Ok(false);
13881 }
13882 }
13883 for (w, t) in branches.iter_mut() {
13884 if !splice_planned_subqueries(w, plan, idx, row, ctx)?
13885 || !splice_planned_subqueries(t, plan, idx, row, ctx)?
13886 {
13887 return Ok(false);
13888 }
13889 }
13890 if let Some(eb) = else_branch {
13891 if !splice_planned_subqueries(eb, plan, idx, row, ctx)? {
13892 return Ok(false);
13893 }
13894 }
13895 Ok(true)
13896 }
13897 Expr::ArraySubscript { target, index } => {
13898 Ok(splice_planned_subqueries(target, plan, idx, row, ctx)?
13899 && splice_planned_subqueries(index, plan, idx, row, ctx)?)
13900 }
13901 _ => Ok(true),
13902 }
13903}
13904
13905fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
13906 let outer_alias = ctx.table_alias.unwrap_or("");
13913 substitute_in_select(stmt, row, ctx, outer_alias);
13914}
13915
13916fn substitute_in_select(
13917 stmt: &mut SelectStatement,
13918 row: &Row,
13919 ctx: &EvalContext<'_>,
13920 outer_alias: &str,
13921) {
13922 for item in &mut stmt.items {
13923 if let SelectItem::Expr { expr, .. } = item {
13924 substitute_in_expr(expr, row, ctx, outer_alias);
13925 }
13926 }
13927 if let Some(w) = &mut stmt.where_ {
13928 substitute_in_expr(w, row, ctx, outer_alias);
13929 }
13930 if let Some(gs) = &mut stmt.group_by {
13931 for g in gs {
13932 substitute_in_expr(g, row, ctx, outer_alias);
13933 }
13934 }
13935 if let Some(h) = &mut stmt.having {
13936 substitute_in_expr(h, row, ctx, outer_alias);
13937 }
13938 for o in &mut stmt.order_by {
13939 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
13940 }
13941 for (_, peer) in &mut stmt.unions {
13942 substitute_in_select(peer, row, ctx, outer_alias);
13943 }
13944}
13945
13946fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
13947 if let Expr::Column(c) = e
13953 && c.qualifier.is_none()
13954 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
13955 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
13956 {
13957 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
13958 if let Ok(lit) = value_to_literal_expr(v) {
13959 *e = lit;
13960 return;
13961 }
13962 }
13963 if let Expr::Column(c) = e
13964 && let Some(qual) = &c.qualifier
13965 {
13966 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
13970 ctx.columns
13971 .iter()
13972 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
13973 } else {
13974 None
13975 }
13976 .or_else(|| {
13977 let composite = alloc::format!("{qual}.{name}", name = c.name);
13978 ctx.columns
13979 .iter()
13980 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13981 });
13982 if let Some(idx) = idx {
13983 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
13984 if let Ok(lit) = value_to_literal_expr(v) {
13985 *e = lit;
13986 return;
13987 }
13988 }
13989 }
13990 match e {
13991 Expr::AggregateOrdered { call, order_by, .. } => {
13992 substitute_in_expr(call, row, ctx, outer_alias);
13993 for o in order_by.iter_mut() {
13994 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
13995 }
13996 }
13997 Expr::Binary { lhs, rhs, .. } => {
13998 substitute_in_expr(lhs, row, ctx, outer_alias);
13999 substitute_in_expr(rhs, row, ctx, outer_alias);
14000 }
14001 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14002 substitute_in_expr(expr, row, ctx, outer_alias);
14003 }
14004 Expr::Like { expr, pattern, .. } => {
14005 substitute_in_expr(expr, row, ctx, outer_alias);
14006 substitute_in_expr(pattern, row, ctx, outer_alias);
14007 }
14008 Expr::FunctionCall { args, .. } => {
14009 for a in args {
14010 substitute_in_expr(a, row, ctx, outer_alias);
14011 }
14012 }
14013 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
14014 Expr::WindowFunction {
14015 args,
14016 partition_by,
14017 order_by,
14018 ..
14019 } => {
14020 for a in args {
14021 substitute_in_expr(a, row, ctx, outer_alias);
14022 }
14023 for p in partition_by {
14024 substitute_in_expr(p, row, ctx, outer_alias);
14025 }
14026 for (o, _, _) in order_by {
14027 substitute_in_expr(o, row, ctx, outer_alias);
14028 }
14029 }
14030 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
14031 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
14032 substitute_in_select(subquery, row, ctx, outer_alias);
14033 }
14034 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14035 Expr::Array(items) => {
14036 for elem in items {
14037 substitute_in_expr(elem, row, ctx, outer_alias);
14038 }
14039 }
14040 Expr::ArraySubscript { target, index } => {
14041 substitute_in_expr(target, row, ctx, outer_alias);
14042 substitute_in_expr(index, row, ctx, outer_alias);
14043 }
14044 Expr::AnyAll { expr, array, .. } => {
14045 substitute_in_expr(expr, row, ctx, outer_alias);
14046 substitute_in_expr(array, row, ctx, outer_alias);
14047 }
14048 Expr::Case {
14049 operand,
14050 branches,
14051 else_branch,
14052 } => {
14053 if let Some(o) = operand {
14054 substitute_in_expr(o, row, ctx, outer_alias);
14055 }
14056 for (w, t) in branches {
14057 substitute_in_expr(w, row, ctx, outer_alias);
14058 substitute_in_expr(t, row, ctx, outer_alias);
14059 }
14060 if let Some(e) = else_branch {
14061 substitute_in_expr(e, row, ctx, outer_alias);
14062 }
14063 }
14064 }
14065}
14066
14067fn encode_row_key(row: &Row) -> Vec<u8> {
14071 let mut out = Vec::new();
14072 for v in &row.values {
14073 let s = alloc::format!("{v:?}|");
14074 out.extend_from_slice(s.as_bytes());
14075 }
14076 out
14077}
14078
14079fn select_has_window(stmt: &SelectStatement) -> bool {
14080 for item in &stmt.items {
14081 if let SelectItem::Expr { expr, .. } = item
14082 && expr_has_window(expr)
14083 {
14084 return true;
14085 }
14086 }
14087 false
14088}
14089
14090fn expr_has_window(e: &Expr) -> bool {
14091 match e {
14092 Expr::WindowFunction { .. } => true,
14093 Expr::AggregateOrdered { call, order_by, .. } => {
14094 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
14095 }
14096 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
14097 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14098 expr_has_window(expr)
14099 }
14100 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
14101 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
14102 Expr::Extract { source, .. } => expr_has_window(source),
14103 Expr::ScalarSubquery(_)
14104 | Expr::Exists { .. }
14105 | Expr::InSubquery { .. }
14106 | Expr::Literal(_)
14107 | Expr::Placeholder(_)
14108 | Expr::Column(_) => false,
14109 Expr::Array(items) => items.iter().any(expr_has_window),
14110 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
14111 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
14112 Expr::Case {
14113 operand,
14114 branches,
14115 else_branch,
14116 } => {
14117 operand.as_deref().is_some_and(expr_has_window)
14118 || branches
14119 .iter()
14120 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
14121 || else_branch.as_deref().is_some_and(expr_has_window)
14122 }
14123 }
14124}
14125
14126fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
14127 if let Expr::WindowFunction { .. } = e {
14128 if !out.iter().any(|x| x == e) {
14133 out.push(e.clone());
14134 }
14135 return;
14136 }
14137 match e {
14138 Expr::WindowFunction { .. } => unreachable!(),
14140 Expr::Binary { lhs, rhs, .. } => {
14141 collect_window_nodes(lhs, out);
14142 collect_window_nodes(rhs, out);
14143 }
14144 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14145 collect_window_nodes(expr, out);
14146 }
14147 Expr::FunctionCall { args, .. } => {
14148 for a in args {
14149 collect_window_nodes(a, out);
14150 }
14151 }
14152 Expr::Like { expr, pattern, .. } => {
14153 collect_window_nodes(expr, out);
14154 collect_window_nodes(pattern, out);
14155 }
14156 Expr::Extract { source, .. } => collect_window_nodes(source, out),
14157 _ => {}
14158 }
14159}
14160
14161fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
14162 if let Expr::WindowFunction { .. } = e
14163 && let Some(idx) = window_nodes.iter().position(|w| w == e)
14164 {
14165 *e = Expr::Column(spg_sql::ast::ColumnName {
14166 qualifier: None,
14167 name: alloc::format!("__win_{idx}"),
14168 });
14169 return;
14170 }
14171 match e {
14172 Expr::Binary { lhs, rhs, .. } => {
14173 rewrite_window_to_columns(lhs, window_nodes);
14174 rewrite_window_to_columns(rhs, window_nodes);
14175 }
14176 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14177 rewrite_window_to_columns(expr, window_nodes);
14178 }
14179 Expr::FunctionCall { args, .. } => {
14180 for a in args {
14181 rewrite_window_to_columns(a, window_nodes);
14182 }
14183 }
14184 Expr::Like { expr, pattern, .. } => {
14185 rewrite_window_to_columns(expr, window_nodes);
14186 rewrite_window_to_columns(pattern, window_nodes);
14187 }
14188 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
14189 _ => {}
14190 }
14191}
14192
14193fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
14197 for (x, y) in a.iter().zip(b.iter()) {
14198 let c = value_cmp(x, y);
14199 if c != core::cmp::Ordering::Equal {
14200 return c;
14201 }
14202 }
14203 a.len().cmp(&b.len())
14204}
14205
14206fn order_key_cmp(
14207 a: &[(Value, bool, Option<bool>)],
14208 b: &[(Value, bool, Option<bool>)],
14209) -> core::cmp::Ordering {
14210 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
14213 let c = order_by_value_cmp(*desc, *nf, va, vb);
14214 if c != core::cmp::Ordering::Equal {
14215 return c;
14216 }
14217 }
14218 a.len().cmp(&b.len())
14219}
14220
14221const fn value_is_integer(v: &Value) -> bool {
14227 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
14228}
14229
14230const fn value_to_i64(v: &Value) -> i64 {
14234 match v {
14235 Value::SmallInt(n) => *n as i64,
14236 Value::Int(n) => *n as i64,
14237 Value::BigInt(n) => *n,
14238 _ => panic!("value_to_i64 called on non-integer Value"),
14239 }
14240}
14241
14242fn generate_series_integers(
14248 start: i64,
14249 stop: i64,
14250 step: i64,
14251 cancel: &CancelToken<'_>,
14252) -> Result<alloc::vec::Vec<Row>, EngineError> {
14253 if step == 0 {
14254 return Err(EngineError::Unsupported(
14255 "generate_series(): step argument cannot be zero".into(),
14256 ));
14257 }
14258 let mut out = alloc::vec::Vec::new();
14259 let mut cur = start;
14260 const MAX_ROWS: usize = 10_000_000;
14264 loop {
14265 cancel.check()?;
14266 if step > 0 && cur > stop {
14267 break;
14268 }
14269 if step < 0 && cur < stop {
14270 break;
14271 }
14272 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
14273 if out.len() > MAX_ROWS {
14274 return Err(EngineError::Unsupported(alloc::format!(
14275 "generate_series(): exceeded {MAX_ROWS} rows; \
14276 narrow start/stop or use a larger step"
14277 )));
14278 }
14279 cur = match cur.checked_add(step) {
14280 Some(n) => n,
14281 None => break,
14282 };
14283 }
14284 Ok(out)
14285}
14286
14287fn generate_series_timestamps(
14292 start: i64,
14293 stop: i64,
14294 step: Value,
14295 cancel: &CancelToken<'_>,
14296) -> Result<alloc::vec::Vec<Row>, EngineError> {
14297 let (months, micros) = match &step {
14298 Value::Interval { months, micros } => (*months, *micros),
14299 _ => unreachable!("caller guards step.is_interval"),
14300 };
14301 if months == 0 && micros == 0 {
14302 return Err(EngineError::Unsupported(
14303 "generate_series(): INTERVAL step cannot be zero".into(),
14304 ));
14305 }
14306 let ascending = months > 0 || micros > 0;
14307 let mut out = alloc::vec::Vec::new();
14308 let mut cur = Value::Timestamp(start);
14309 const MAX_ROWS: usize = 10_000_000;
14310 loop {
14311 cancel.check()?;
14312 let cur_t = match cur {
14313 Value::Timestamp(t) => t,
14314 _ => unreachable!("loop invariant: cur is Timestamp"),
14315 };
14316 if ascending && cur_t > stop {
14317 break;
14318 }
14319 if !ascending && cur_t < stop {
14320 break;
14321 }
14322 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
14323 if out.len() > MAX_ROWS {
14324 return Err(EngineError::Unsupported(alloc::format!(
14325 "generate_series(): exceeded {MAX_ROWS} rows; \
14326 narrow start/stop or use a larger step"
14327 )));
14328 }
14329 let next = eval::apply_binary_interval(
14330 spg_sql::ast::BinOp::Add,
14331 &cur,
14332 &Value::Interval { months, micros },
14333 )
14334 .map_err(EngineError::Eval)?;
14335 cur = match next {
14336 Some(v) => v,
14337 None => break,
14338 };
14339 }
14340 Ok(out)
14341}
14342
14343#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
14349 desc: bool,
14350 nulls_first: Option<bool>,
14351 a: &Value,
14352 b: &Value,
14353) -> core::cmp::Ordering {
14354 use core::cmp::Ordering;
14355 let nf = nulls_first.unwrap_or(desc);
14356 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
14357 (true, true) => Ordering::Equal,
14358 (true, false) => {
14359 if nf {
14360 Ordering::Less
14361 } else {
14362 Ordering::Greater
14363 }
14364 }
14365 (false, true) => {
14366 if nf {
14367 Ordering::Greater
14368 } else {
14369 Ordering::Less
14370 }
14371 }
14372 (false, false) => {
14373 let c = value_cmp(a, b);
14374 if desc { c.reverse() } else { c }
14375 }
14376 }
14377}
14378
14379fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
14380 use core::cmp::Ordering;
14381 match (a, b) {
14382 (Value::Null, Value::Null) => Ordering::Equal,
14383 (Value::Null, _) => Ordering::Less,
14384 (_, Value::Null) => Ordering::Greater,
14385 (Value::Int(x), Value::Int(y)) => x.cmp(y),
14386 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
14387 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
14388 (Value::Text(x), Value::Text(y)) => x.cmp(y),
14389 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
14390 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
14391 (Value::Date(x), Value::Date(y)) => x.cmp(y),
14392 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
14393 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
14396 }
14397}
14398
14399#[allow(
14405 clippy::too_many_arguments,
14406 clippy::cast_possible_truncation,
14407 clippy::cast_possible_wrap,
14408 clippy::cast_precision_loss,
14409 clippy::cast_sign_loss,
14410 clippy::doc_markdown,
14411 clippy::too_many_lines,
14412 clippy::type_complexity,
14413 clippy::match_same_arms
14414)]
14415fn compute_window_partition(
14416 name: &str,
14417 args: &[Expr],
14418 ordered: bool,
14419 frame: Option<&WindowFrame>,
14420 null_treatment: spg_sql::ast::NullTreatment,
14421 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14422 filtered_rows: &[&Row],
14423 ctx: &EvalContext<'_>,
14424 out_vals: &mut [Value],
14425) -> Result<(), EngineError> {
14426 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
14427 let lower = name.to_ascii_lowercase();
14428 match lower.as_str() {
14429 "row_number" => {
14430 for (rank, (_, _, idx)) in slice.iter().enumerate() {
14431 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
14432 }
14433 Ok(())
14434 }
14435 "rank" => {
14436 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14437 let mut current_rank: i64 = 1;
14438 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14439 if let Some(p) = prev_key
14440 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14441 {
14442 current_rank = (i + 1) as i64;
14443 }
14444 if prev_key.is_none() {
14445 current_rank = 1;
14446 }
14447 out_vals[*idx] = Value::BigInt(current_rank);
14448 prev_key = Some(okey.as_slice());
14449 }
14450 Ok(())
14451 }
14452 "dense_rank" => {
14453 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14454 let mut current_rank: i64 = 0;
14455 for (_, okey, idx) in slice {
14456 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
14457 current_rank += 1;
14458 }
14459 out_vals[*idx] = Value::BigInt(current_rank);
14460 prev_key = Some(okey.as_slice());
14461 }
14462 Ok(())
14463 }
14464 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
14465 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
14468 slice.iter().map(|_| Value::Null).collect()
14469 } else {
14470 slice
14471 .iter()
14472 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14473 .collect::<Result<_, _>>()
14474 .map_err(EngineError::Eval)?
14475 };
14476 let eff = effective_frame(frame, ordered)?;
14480 #[allow(clippy::needless_range_loop)]
14481 for i in 0..slice.len() {
14482 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14483 let mut sum: f64 = 0.0;
14484 let mut count: i64 = 0;
14485 let mut min_v: Option<f64> = None;
14486 let mut max_v: Option<f64> = None;
14487 let mut row_count: i64 = 0;
14488 if lo <= hi {
14489 for j in lo..=hi {
14490 let v = &arg_values[j];
14491 match lower.as_str() {
14492 "count_star" => row_count += 1,
14493 "count" => {
14494 if !v.is_null() {
14495 count += 1;
14496 }
14497 }
14498 _ => {
14499 if let Some(x) = value_to_f64(v) {
14500 sum += x;
14501 count += 1;
14502 min_v = Some(min_v.map_or(x, |m| m.min(x)));
14503 max_v = Some(max_v.map_or(x, |m| m.max(x)));
14504 }
14505 }
14506 }
14507 }
14508 }
14509 let value = match lower.as_str() {
14510 "count_star" => Value::BigInt(row_count),
14511 "count" => Value::BigInt(count),
14512 "sum" => Value::Float(sum),
14513 "avg" => {
14514 if count == 0 {
14515 Value::Null
14516 } else {
14517 Value::Float(sum / count as f64)
14518 }
14519 }
14520 "min" => min_v.map_or(Value::Null, Value::Float),
14521 "max" => max_v.map_or(Value::Null, Value::Float),
14522 _ => unreachable!(),
14523 };
14524 let (_, _, idx) = &slice[i];
14525 out_vals[*idx] = value;
14526 }
14527 Ok(())
14528 }
14529 "lag" | "lead" => {
14530 if args.is_empty() {
14533 return Err(EngineError::Unsupported(alloc::format!(
14534 "{lower}() requires at least one argument"
14535 )));
14536 }
14537 let offset: i64 = if args.len() >= 2 {
14538 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
14539 .map_err(EngineError::Eval)?;
14540 match v {
14541 Value::SmallInt(n) => i64::from(n),
14542 Value::Int(n) => i64::from(n),
14543 Value::BigInt(n) => n,
14544 _ => {
14545 return Err(EngineError::Unsupported(alloc::format!(
14546 "{lower}() offset must be integer"
14547 )));
14548 }
14549 }
14550 } else {
14551 1
14552 };
14553 let default: Value = if args.len() >= 3 {
14554 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
14555 .map_err(EngineError::Eval)?
14556 } else {
14557 Value::Null
14558 };
14559 let values: Vec<Value> = slice
14560 .iter()
14561 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14562 .collect::<Result<_, _>>()
14563 .map_err(EngineError::Eval)?;
14564 let n = slice.len();
14565 for (i, (_, _, idx)) in slice.iter().enumerate() {
14566 let signed_offset = if lower == "lag" { -offset } else { offset };
14567 let v = if ignore_nulls {
14568 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
14572 let needed: i64 = signed_offset.abs();
14573 if needed == 0 {
14574 values[i].clone()
14575 } else {
14576 let mut j: i64 = i as i64;
14577 let mut hits: i64 = 0;
14578 let mut found: Option<Value> = None;
14579 loop {
14580 j += step;
14581 if j < 0 || j >= n as i64 {
14582 break;
14583 }
14584 #[allow(clippy::cast_sign_loss)]
14585 let v = &values[j as usize];
14586 if !v.is_null() {
14587 hits += 1;
14588 if hits == needed {
14589 found = Some(v.clone());
14590 break;
14591 }
14592 }
14593 }
14594 found.unwrap_or_else(|| default.clone())
14595 }
14596 } else {
14597 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
14598 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
14599 default.clone()
14600 } else {
14601 #[allow(clippy::cast_sign_loss)]
14602 {
14603 values[target_signed as usize].clone()
14604 }
14605 }
14606 };
14607 out_vals[*idx] = v;
14608 }
14609 Ok(())
14610 }
14611 "first_value" | "last_value" | "nth_value" => {
14612 if args.is_empty() {
14613 return Err(EngineError::Unsupported(alloc::format!(
14614 "{lower}() requires at least one argument"
14615 )));
14616 }
14617 let values: Vec<Value> = slice
14618 .iter()
14619 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14620 .collect::<Result<_, _>>()
14621 .map_err(EngineError::Eval)?;
14622 let nth: usize = if lower == "nth_value" {
14623 if args.len() < 2 {
14624 return Err(EngineError::Unsupported(
14625 "nth_value() requires (expr, n)".into(),
14626 ));
14627 }
14628 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
14629 .map_err(EngineError::Eval)?;
14630 let raw = match v {
14631 Value::SmallInt(n) => i64::from(n),
14632 Value::Int(n) => i64::from(n),
14633 Value::BigInt(n) => n,
14634 _ => {
14635 return Err(EngineError::Unsupported(
14636 "nth_value() n must be integer".into(),
14637 ));
14638 }
14639 };
14640 if raw < 1 {
14641 return Err(EngineError::Unsupported(
14642 "nth_value() n must be >= 1".into(),
14643 ));
14644 }
14645 #[allow(clippy::cast_sign_loss)]
14646 {
14647 raw as usize
14648 }
14649 } else {
14650 0
14651 };
14652 let eff = effective_frame(frame, ordered)?;
14653 for i in 0..slice.len() {
14654 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14655 let (_, _, idx) = &slice[i];
14656 let v = if lo > hi {
14657 Value::Null
14658 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
14659 if lower == "first_value" {
14662 (lo..=hi)
14663 .find_map(|j| {
14664 let v = &values[j];
14665 (!v.is_null()).then(|| v.clone())
14666 })
14667 .unwrap_or(Value::Null)
14668 } else {
14669 (lo..=hi)
14670 .rev()
14671 .find_map(|j| {
14672 let v = &values[j];
14673 (!v.is_null()).then(|| v.clone())
14674 })
14675 .unwrap_or(Value::Null)
14676 }
14677 } else {
14678 match lower.as_str() {
14679 "first_value" => values[lo].clone(),
14680 "last_value" => values[hi].clone(),
14681 "nth_value" => {
14682 let pos = lo + nth - 1;
14683 if pos > hi {
14684 Value::Null
14685 } else {
14686 values[pos].clone()
14687 }
14688 }
14689 _ => unreachable!(),
14690 }
14691 };
14692 out_vals[*idx] = v;
14693 }
14694 Ok(())
14695 }
14696 "ntile" => {
14697 if args.is_empty() {
14698 return Err(EngineError::Unsupported(
14699 "ntile(n) requires an integer argument".into(),
14700 ));
14701 }
14702 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
14703 .map_err(EngineError::Eval)?;
14704 let bucket_count: i64 = match v {
14705 Value::SmallInt(n) => i64::from(n),
14706 Value::Int(n) => i64::from(n),
14707 Value::BigInt(n) => n,
14708 _ => {
14709 return Err(EngineError::Unsupported(
14710 "ntile() argument must be integer".into(),
14711 ));
14712 }
14713 };
14714 if bucket_count < 1 {
14715 return Err(EngineError::Unsupported(
14716 "ntile() argument must be >= 1".into(),
14717 ));
14718 }
14719 #[allow(clippy::cast_sign_loss)]
14720 let buckets = bucket_count as usize;
14721 let n = slice.len();
14722 let base = n / buckets;
14725 let extras = n % buckets;
14726 let mut bucket: usize = 1;
14727 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
14728 let mut buckets_with_extra_remaining = extras;
14729 for (_, _, idx) in slice {
14730 if remaining_in_bucket == 0 {
14731 bucket += 1;
14732 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
14733 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
14734 base + 1
14735 } else {
14736 base
14737 };
14738 if remaining_in_bucket == 0 {
14741 remaining_in_bucket = 1;
14742 }
14743 }
14744 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
14745 remaining_in_bucket -= 1;
14746 }
14747 Ok(())
14748 }
14749 "percent_rank" => {
14750 let n = slice.len();
14753 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14754 let mut current_rank: i64 = 1;
14755 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14756 if let Some(p) = prev_key
14757 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14758 {
14759 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
14760 }
14761 if prev_key.is_none() {
14762 current_rank = 1;
14763 }
14764 #[allow(clippy::cast_precision_loss)]
14765 let pr = if n <= 1 {
14766 0.0
14767 } else {
14768 (current_rank - 1) as f64 / (n - 1) as f64
14769 };
14770 out_vals[*idx] = Value::Float(pr);
14771 prev_key = Some(okey.as_slice());
14772 }
14773 Ok(())
14774 }
14775 "cume_dist" => {
14776 let n = slice.len();
14778 for i in 0..slice.len() {
14780 let peer_end = peer_group_end(slice, i);
14781 #[allow(clippy::cast_precision_loss)]
14782 let cd = (peer_end + 1) as f64 / n as f64;
14783 let (_, _, idx) = &slice[i];
14784 out_vals[*idx] = Value::Float(cd);
14785 }
14786 Ok(())
14787 }
14788 other => Err(EngineError::Unsupported(alloc::format!(
14789 "window function {other:?} not supported (v4.21: row_number/rank/dense_rank/sum/avg/count/min/max/lag/lead/first_value/last_value/nth_value/ntile/percent_rank/cume_dist)"
14790 ))),
14791 }
14792}
14793
14794fn effective_frame(
14801 frame: Option<&WindowFrame>,
14802 ordered: bool,
14803) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
14804 match frame {
14805 None => {
14806 if ordered {
14807 Ok((
14808 FrameKind::Range,
14809 FrameBound::UnboundedPreceding,
14810 FrameBound::CurrentRow,
14811 ))
14812 } else {
14813 Ok((
14814 FrameKind::Rows,
14815 FrameBound::UnboundedPreceding,
14816 FrameBound::UnboundedFollowing,
14817 ))
14818 }
14819 }
14820 Some(fr) => {
14821 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
14822 if matches!(fr.start, FrameBound::UnboundedFollowing)
14824 || matches!(end, FrameBound::UnboundedPreceding)
14825 {
14826 return Err(EngineError::Unsupported(alloc::format!(
14827 "invalid frame: start={:?} end={:?}",
14828 fr.start,
14829 end
14830 )));
14831 }
14832 if fr.kind == FrameKind::Range
14837 && (matches!(
14838 fr.start,
14839 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
14840 ) || matches!(
14841 end,
14842 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
14843 ))
14844 {
14845 return Err(EngineError::Unsupported(
14846 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
14847 ));
14848 }
14849 Ok((fr.kind, fr.start.clone(), end))
14850 }
14851 }
14852}
14853
14854#[allow(clippy::type_complexity)]
14858fn frame_bounds_for_row(
14859 eff: &(FrameKind, FrameBound, FrameBound),
14860 i: usize,
14861 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14862) -> (usize, usize) {
14863 let (kind, start, end) = eff;
14864 let n = slice.len();
14865 let last = n.saturating_sub(1);
14866 let (mut lo, mut hi) = match kind {
14867 FrameKind::Rows => {
14868 let lo = match start {
14869 FrameBound::UnboundedPreceding => 0,
14870 FrameBound::OffsetPreceding(k) => {
14871 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14872 i.saturating_sub(k)
14873 }
14874 FrameBound::CurrentRow => i,
14875 FrameBound::OffsetFollowing(k) => {
14876 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14877 i.saturating_add(k).min(last)
14878 }
14879 FrameBound::UnboundedFollowing => last,
14880 };
14881 let hi = match end {
14882 FrameBound::UnboundedPreceding => 0,
14883 FrameBound::OffsetPreceding(k) => {
14884 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14885 i.saturating_sub(k)
14886 }
14887 FrameBound::CurrentRow => i,
14888 FrameBound::OffsetFollowing(k) => {
14889 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14890 i.saturating_add(k).min(last)
14891 }
14892 FrameBound::UnboundedFollowing => last,
14893 };
14894 (lo, hi)
14895 }
14896 FrameKind::Range => {
14897 let lo = match start {
14903 FrameBound::UnboundedPreceding => 0,
14904 FrameBound::CurrentRow => peer_group_start(slice, i),
14905 FrameBound::UnboundedFollowing => last,
14906 _ => unreachable!("offset bounds rejected for RANGE"),
14907 };
14908 let hi = match end {
14909 FrameBound::UnboundedPreceding => 0,
14910 FrameBound::CurrentRow => peer_group_end(slice, i),
14911 FrameBound::UnboundedFollowing => last,
14912 _ => unreachable!("offset bounds rejected for RANGE"),
14913 };
14914 (lo, hi)
14915 }
14916 };
14917 if hi >= n {
14918 hi = last;
14919 }
14920 if lo >= n {
14921 lo = last;
14922 }
14923 (lo, hi)
14924}
14925
14926#[allow(clippy::type_complexity)]
14930fn peer_group_start(
14931 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14932 i: usize,
14933) -> usize {
14934 let key = &slice[i].1;
14935 let mut j = i;
14936 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
14937 j -= 1;
14938 }
14939 j
14940}
14941
14942#[allow(clippy::type_complexity)]
14945fn peer_group_end(
14946 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14947 i: usize,
14948) -> usize {
14949 let key = &slice[i].1;
14950 let mut j = i;
14951 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
14952 j += 1;
14953 }
14954 j
14955}
14956
14957fn value_to_f64(v: &Value) -> Option<f64> {
14958 match v {
14959 Value::SmallInt(n) => Some(f64::from(*n)),
14960 Value::Int(n) => Some(f64::from(*n)),
14961 #[allow(clippy::cast_precision_loss)]
14962 Value::BigInt(n) => Some(*n as f64),
14963 Value::Float(x) => Some(*x),
14964 _ => None,
14965 }
14966}
14967
14968fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
14972 let mut any = false;
14973 for item in &stmt.items {
14974 if let SelectItem::Expr { expr, .. } = item {
14975 any = any || expr_has_subquery(expr);
14976 }
14977 }
14978 if let Some(w) = &stmt.where_ {
14979 any = any || expr_has_subquery(w);
14980 }
14981 if let Some(h) = &stmt.having {
14982 any = any || expr_has_subquery(h);
14983 }
14984 for o in &stmt.order_by {
14985 any = any || expr_has_subquery(&o.expr);
14986 }
14987 for (_, peer) in &stmt.unions {
14988 any = any || expr_tree_has_subquery(peer);
14989 }
14990 any
14991}
14992
14993pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
14994 match e {
14995 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
14996 Expr::AggregateOrdered { call, order_by, .. } => {
14997 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
14998 }
14999 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
15000 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15001 expr_has_subquery(expr)
15002 }
15003 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
15004 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
15005 Expr::Extract { source, .. } => expr_has_subquery(source),
15006 Expr::WindowFunction {
15007 args,
15008 partition_by,
15009 order_by,
15010 ..
15011 } => {
15012 args.iter().any(expr_has_subquery)
15013 || partition_by.iter().any(expr_has_subquery)
15014 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
15015 }
15016 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
15017 Expr::Array(items) => items.iter().any(expr_has_subquery),
15018 Expr::ArraySubscript { target, index } => {
15019 expr_has_subquery(target) || expr_has_subquery(index)
15020 }
15021 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
15022 Expr::Case {
15023 operand,
15024 branches,
15025 else_branch,
15026 } => {
15027 operand.as_deref().is_some_and(expr_has_subquery)
15028 || branches
15029 .iter()
15030 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
15031 || else_branch.as_deref().is_some_and(expr_has_subquery)
15032 }
15033 }
15034}
15035
15036fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
15043 let lit = match v {
15044 Value::Null => Literal::Null,
15045 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15046 Value::Int(n) => Literal::Integer(i64::from(n)),
15047 Value::BigInt(n) => Literal::Integer(n),
15048 Value::Float(x) => Literal::Float(x),
15049 Value::Text(s) | Value::Json(s) => Literal::String(s),
15050 Value::Bool(b) => Literal::Bool(b),
15051 other => {
15052 return Err(EngineError::Unsupported(alloc::format!(
15053 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
15054 other.data_type()
15055 )));
15056 }
15057 };
15058 Ok(Expr::Literal(lit))
15059}
15060
15061fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
15067 let lit = match v {
15068 Value::Null => Literal::Null,
15069 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15070 Value::Int(n) => Literal::Integer(i64::from(n)),
15071 Value::BigInt(n) => Literal::Integer(n),
15072 Value::Float(x) => Literal::Float(x),
15073 Value::Text(s) | Value::Json(s) => Literal::String(s),
15074 Value::Bool(b) => Literal::Bool(b),
15075 Value::Vector(xs) => Literal::Vector(xs),
15076 Value::Date(days) => {
15080 let micros = (i64::from(days)) * 86_400_000_000;
15081 Literal::String(format_timestamp_micros_as_date(micros))
15082 }
15083 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
15084 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
15085 other => {
15086 return Err(EngineError::Unsupported(alloc::format!(
15087 "INSERT … SELECT cannot materialise value of type {:?}; \
15088 add an explicit CAST in the inner SELECT",
15089 other.data_type()
15090 )));
15091 }
15092 };
15093 Ok(Expr::Literal(lit))
15094}
15095
15096fn format_timestamp_micros(us: i64) -> String {
15097 let days = us.div_euclid(86_400_000_000);
15099 let intra_day = us.rem_euclid(86_400_000_000);
15100 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
15101 let secs = intra_day / 1_000_000;
15102 let us_rem = intra_day % 1_000_000;
15103 let h = (secs / 3600) % 24;
15104 let m = (secs / 60) % 60;
15105 let s = secs % 60;
15106 if us_rem == 0 {
15107 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
15108 } else {
15109 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
15110 }
15111}
15112
15113fn format_timestamp_micros_as_date(us: i64) -> String {
15114 let days = us.div_euclid(86_400_000_000);
15117 let jdn = days + 2_440_588;
15119 let (y, mo, d) = jdn_to_ymd(jdn);
15120 alloc::format!("{y:04}-{mo:02}-{d:02}")
15121}
15122
15123fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
15124 let l = jdn + 68569;
15126 let n = (4 * l) / 146_097;
15127 let l = l - (146_097 * n + 3) / 4;
15128 let i = (4000 * (l + 1)) / 1_461_001;
15129 let l = l - (1461 * i) / 4 + 31;
15130 let j = (80 * l) / 2447;
15131 let day = (l - (2447 * j) / 80) as u32;
15132 let l = j / 11;
15133 let month = (j + 2 - 12 * l) as u32;
15134 let year = 100 * (n - 49) + i + l;
15135 (year, month, day)
15136}
15137
15138fn format_numeric(scaled: i128, scale: u8) -> String {
15139 if scale == 0 {
15140 return alloc::format!("{scaled}");
15141 }
15142 let abs = scaled.unsigned_abs();
15143 let divisor = 10u128.pow(u32::from(scale));
15144 let whole = abs / divisor;
15145 let frac = abs % divisor;
15146 let sign = if scaled < 0 { "-" } else { "" };
15147 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
15148}
15149
15150fn rewrite_column_in_source(
15174 src: &str,
15175 old: &str,
15176 new: &str,
15177) -> Result<alloc::string::String, EngineError> {
15178 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15179 EngineError::Unsupported(alloc::format!(
15180 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
15181 failed to parse for rewrite ({e})"
15182 ))
15183 })?;
15184 rewrite_column_in_expr(&mut expr, old, new);
15185 Ok(alloc::format!("{expr}"))
15186}
15187
15188fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
15196 match e {
15197 Expr::AggregateOrdered { call, order_by, .. } => {
15198 rewrite_column_in_expr(call, old, new);
15199 for o in order_by.iter_mut() {
15200 rewrite_column_in_expr(&mut o.expr, old, new);
15201 }
15202 }
15203 Expr::Column(c) => {
15204 if c.name.eq_ignore_ascii_case(old) {
15205 c.name = new.to_string();
15206 }
15207 }
15208 Expr::Binary { lhs, rhs, .. } => {
15209 rewrite_column_in_expr(lhs, old, new);
15210 rewrite_column_in_expr(rhs, old, new);
15211 }
15212 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15213 rewrite_column_in_expr(expr, old, new);
15214 }
15215 Expr::FunctionCall { args, .. } => {
15216 for a in args {
15217 rewrite_column_in_expr(a, old, new);
15218 }
15219 }
15220 Expr::Like { expr, pattern, .. } => {
15221 rewrite_column_in_expr(expr, old, new);
15222 rewrite_column_in_expr(pattern, old, new);
15223 }
15224 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
15225 Expr::WindowFunction {
15226 args,
15227 partition_by,
15228 order_by,
15229 ..
15230 } => {
15231 for a in args {
15232 rewrite_column_in_expr(a, old, new);
15233 }
15234 for p in partition_by {
15235 rewrite_column_in_expr(p, old, new);
15236 }
15237 for (o, _, _) in order_by {
15238 rewrite_column_in_expr(o, old, new);
15239 }
15240 }
15241 Expr::Array(items) => {
15242 for elem in items {
15243 rewrite_column_in_expr(elem, old, new);
15244 }
15245 }
15246 Expr::ArraySubscript { target, index } => {
15247 rewrite_column_in_expr(target, old, new);
15248 rewrite_column_in_expr(index, old, new);
15249 }
15250 Expr::AnyAll { expr, array, .. } => {
15251 rewrite_column_in_expr(expr, old, new);
15252 rewrite_column_in_expr(array, old, new);
15253 }
15254 Expr::Case {
15255 operand,
15256 branches,
15257 else_branch,
15258 } => {
15259 if let Some(o) = operand {
15260 rewrite_column_in_expr(o, old, new);
15261 }
15262 for (w, t) in branches {
15263 rewrite_column_in_expr(w, old, new);
15264 rewrite_column_in_expr(t, old, new);
15265 }
15266 if let Some(e) = else_branch {
15267 rewrite_column_in_expr(e, old, new);
15268 }
15269 }
15270 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
15274 Expr::Literal(_) | Expr::Placeholder(_) => {}
15275 }
15276}
15277
15278pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
15286 match stmt {
15287 Statement::Select(s) => substitute_select(s, params)?,
15288 Statement::Insert(ins) => {
15289 for row in &mut ins.rows {
15290 for e in row {
15291 substitute_expr(e, params)?;
15292 }
15293 }
15294 if let Some(clause) = &mut ins.on_conflict
15298 && let spg_sql::ast::OnConflictAction::Update {
15299 assignments,
15300 where_,
15301 } = &mut clause.action
15302 {
15303 for (_, e) in assignments.iter_mut() {
15304 substitute_expr(e, params)?;
15305 }
15306 if let Some(w) = where_ {
15307 substitute_expr(w, params)?;
15308 }
15309 }
15310 }
15311 Statement::Update(u) => {
15312 for (_, e) in &mut u.assignments {
15313 substitute_expr(e, params)?;
15314 }
15315 if let Some(w) = &mut u.where_ {
15316 substitute_expr(w, params)?;
15317 }
15318 }
15319 Statement::Delete(d) => {
15320 if let Some(w) = &mut d.where_ {
15321 substitute_expr(w, params)?;
15322 }
15323 }
15324 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
15325 _ => {}
15328 }
15329 Ok(())
15330}
15331
15332pub(crate) fn walk_select_exprs_mut(
15342 s: &mut SelectStatement,
15343 f: &mut impl FnMut(&mut Expr) -> Result<(), EngineError>,
15344) -> Result<(), EngineError> {
15345 for cte in &mut s.ctes {
15346 walk_select_exprs_mut(&mut cte.body, f)?;
15347 }
15348 for item in &mut s.items {
15349 if let SelectItem::Expr { expr, .. } = item {
15350 f(expr)?;
15351 }
15352 }
15353 if let Some(from) = &mut s.from {
15354 if let Some(sub) = &mut from.primary.lateral_subquery {
15355 walk_select_exprs_mut(sub, f)?;
15356 }
15357 for j in &mut from.joins {
15358 if let Some(sub) = &mut j.table.lateral_subquery {
15359 walk_select_exprs_mut(sub, f)?;
15360 }
15361 if let Some(on) = &mut j.on {
15362 f(on)?;
15363 }
15364 }
15365 }
15366 if let Some(w) = &mut s.where_ {
15367 f(w)?;
15368 }
15369 if let Some(gs) = &mut s.group_by {
15370 for g in gs {
15371 f(g)?;
15372 }
15373 }
15374 if let Some(h) = &mut s.having {
15375 f(h)?;
15376 }
15377 for o in &mut s.order_by {
15378 f(&mut o.expr)?;
15379 }
15380 for (_, peer) in &mut s.unions {
15381 walk_select_exprs_mut(peer, f)?;
15382 }
15383 Ok(())
15384}
15385
15386fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
15387 walk_select_exprs_mut(s, &mut |e| substitute_expr(e, params))?;
15388 for cte in &mut s.ctes {
15393 resolve_limit_offset_placeholders(&mut cte.body, params)?;
15394 }
15395 for (_, peer) in &mut s.unions {
15396 resolve_limit_offset_placeholders(peer, params)?;
15397 }
15398 if let Some(le) = s.limit {
15403 s.limit = Some(resolve_limit_placeholder(le, params)?);
15404 }
15405 if let Some(le) = s.offset {
15406 s.offset = Some(resolve_limit_placeholder(le, params)?);
15407 }
15408 Ok(())
15409}
15410
15411fn resolve_limit_offset_placeholders(
15414 s: &mut SelectStatement,
15415 params: &[Value],
15416) -> Result<(), EngineError> {
15417 if let Some(le) = s.limit {
15418 s.limit = Some(resolve_limit_placeholder(le, params)?);
15419 }
15420 if let Some(le) = s.offset {
15421 s.offset = Some(resolve_limit_placeholder(le, params)?);
15422 }
15423 for cte in &mut s.ctes {
15424 resolve_limit_offset_placeholders(&mut cte.body, params)?;
15425 }
15426 for (_, peer) in &mut s.unions {
15427 resolve_limit_offset_placeholders(peer, params)?;
15428 }
15429 Ok(())
15430}
15431
15432fn resolve_limit_placeholder(
15433 le: spg_sql::ast::LimitExpr,
15434 params: &[Value],
15435) -> Result<spg_sql::ast::LimitExpr, EngineError> {
15436 use spg_sql::ast::LimitExpr;
15437 match le {
15438 LimitExpr::Literal(_) => Ok(le),
15439 LimitExpr::Placeholder(n) => {
15440 let idx = usize::from(n).saturating_sub(1);
15441 let v = params.get(idx).ok_or_else(|| {
15442 EngineError::Eval(EvalError::PlaceholderOutOfRange {
15443 n,
15444 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
15445 })
15446 })?;
15447 let int = match v {
15448 Value::SmallInt(x) => Some(i64::from(*x)),
15449 Value::Int(x) => Some(i64::from(*x)),
15450 Value::BigInt(x) => Some(*x),
15451 _ => None,
15452 }
15453 .ok_or_else(|| {
15454 EngineError::Unsupported(alloc::format!(
15455 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
15456 ))
15457 })?;
15458 if int < 0 {
15459 return Err(EngineError::Unsupported(alloc::format!(
15460 "LIMIT/OFFSET ${n} bound to negative value {int}"
15461 )));
15462 }
15463 let bounded = u32::try_from(int).map_err(|_| {
15464 EngineError::Unsupported(alloc::format!(
15465 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
15466 ))
15467 })?;
15468 Ok(LimitExpr::Literal(bounded))
15469 }
15470 }
15471}
15472
15473fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
15474 if let Expr::Placeholder(n) = e {
15475 let idx = usize::from(*n).saturating_sub(1);
15476 let v = params.get(idx).ok_or_else(|| {
15477 EngineError::Eval(EvalError::PlaceholderOutOfRange {
15478 n: *n,
15479 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
15480 })
15481 })?;
15482 *e = Expr::Literal(value_to_literal(v.clone()));
15483 return Ok(());
15484 }
15485 match e {
15486 Expr::AggregateOrdered { call, order_by, .. } => {
15487 substitute_expr(call, params)?;
15488 for o in order_by.iter_mut() {
15489 substitute_expr(&mut o.expr, params)?;
15490 }
15491 }
15492 Expr::Binary { lhs, rhs, .. } => {
15493 substitute_expr(lhs, params)?;
15494 substitute_expr(rhs, params)?;
15495 }
15496 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15497 substitute_expr(expr, params)?;
15498 }
15499 Expr::FunctionCall { args, .. } => {
15500 for a in args {
15501 substitute_expr(a, params)?;
15502 }
15503 }
15504 Expr::Like { expr, pattern, .. } => {
15505 substitute_expr(expr, params)?;
15506 substitute_expr(pattern, params)?;
15507 }
15508 Expr::Extract { source, .. } => substitute_expr(source, params)?,
15509 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
15510 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
15511 Expr::InSubquery { expr, subquery, .. } => {
15512 substitute_expr(expr, params)?;
15513 substitute_select(subquery, params)?;
15514 }
15515 Expr::WindowFunction {
15516 args,
15517 partition_by,
15518 order_by,
15519 ..
15520 } => {
15521 for a in args {
15522 substitute_expr(a, params)?;
15523 }
15524 for p in partition_by {
15525 substitute_expr(p, params)?;
15526 }
15527 for (e, _, _) in order_by {
15528 substitute_expr(e, params)?;
15529 }
15530 }
15531 Expr::Literal(_) | Expr::Column(_) => {}
15532 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
15534 Expr::Array(items) => {
15535 for elem in items {
15536 substitute_expr(elem, params)?;
15537 }
15538 }
15539 Expr::ArraySubscript { target, index } => {
15540 substitute_expr(target, params)?;
15541 substitute_expr(index, params)?;
15542 }
15543 Expr::AnyAll { expr, array, .. } => {
15544 substitute_expr(expr, params)?;
15545 substitute_expr(array, params)?;
15546 }
15547 Expr::Case {
15548 operand,
15549 branches,
15550 else_branch,
15551 } => {
15552 if let Some(o) = operand {
15553 substitute_expr(o, params)?;
15554 }
15555 for (w, t) in branches {
15556 substitute_expr(w, params)?;
15557 substitute_expr(t, params)?;
15558 }
15559 if let Some(e) = else_branch {
15560 substitute_expr(e, params)?;
15561 }
15562 }
15563 }
15564 Ok(())
15565}
15566
15567fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
15585 use core::cmp::Ordering;
15586 match (a, b) {
15587 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
15588 (Value::Int(a), Value::Int(b)) => a.cmp(b),
15589 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
15590 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
15591 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
15592 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
15593 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
15594 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
15595 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
15596 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
15597 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
15598 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
15599 (Value::Date(a), Value::Date(b)) => a.cmp(b),
15600 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
15601 (Value::SmallInt(n), Value::Float(x)) => {
15603 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
15604 }
15605 (Value::Float(x), Value::SmallInt(n)) => {
15606 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
15607 }
15608 (Value::Int(n), Value::Float(x)) => {
15609 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
15610 }
15611 (Value::Float(x), Value::Int(n)) => {
15612 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
15613 }
15614 (Value::BigInt(n), Value::Float(x)) => {
15615 #[allow(clippy::cast_precision_loss)]
15616 let nf = *n as f64;
15617 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
15618 }
15619 (Value::Float(x), Value::BigInt(n)) => {
15620 #[allow(clippy::cast_precision_loss)]
15621 let nf = *n as f64;
15622 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
15623 }
15624 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
15627 }
15628}
15629
15630fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
15637 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
15638 out.push('[');
15639 for (i, b) in bounds.iter().enumerate() {
15640 if i > 0 {
15641 out.push_str(", ");
15642 }
15643 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
15644 if needs_quote {
15645 out.push('"');
15646 for ch in b.chars() {
15647 if ch == '"' || ch == '\\' {
15648 out.push('\\');
15649 }
15650 out.push(ch);
15651 }
15652 out.push('"');
15653 } else {
15654 out.push_str(b);
15655 }
15656 }
15657 out.push(']');
15658 out
15659}
15660
15661pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
15671 match v {
15672 Value::Null => "NULL".to_string(),
15673 Value::SmallInt(n) => alloc::format!("{n}"),
15674 Value::Int(n) => alloc::format!("{n}"),
15675 Value::BigInt(n) => alloc::format!("{n}"),
15676 Value::Float(x) => alloc::format!("{x:?}"),
15677 Value::Text(s) | Value::Json(s) => s.clone(),
15678 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
15679 Value::Date(d) => eval::format_date(*d),
15680 Value::Timestamp(t) => eval::format_timestamp(*t),
15681 Value::Time(us) => eval::format_time(*us),
15683 Value::Year(y) => alloc::format!("{y:04}"),
15685 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
15687 Value::Money(c) => eval::format_money(*c),
15689 v @ Value::Range { .. } => format_range_str(v),
15691 Value::Hstore(pairs) => format_hstore_str(pairs),
15693 Value::IntArray2D(rows) => format_int_2d_text(rows),
15695 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
15696 Value::TextArray2D(rows) => format_text_2d_text(rows),
15697 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
15698 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
15699 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
15700 alloc::format!("{v:?}")
15704 }
15705 _ => alloc::format!("{v:?}"),
15709 }
15710}
15711
15712const fn is_internal_table_name(_name: &str) -> bool {
15719 false
15720}
15721
15722fn value_to_literal(v: Value) -> Literal {
15723 match v {
15724 Value::Null => Literal::Null,
15725 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15726 Value::Int(n) => Literal::Integer(i64::from(n)),
15727 Value::BigInt(n) => Literal::Integer(n),
15728 Value::Float(x) => Literal::Float(x),
15729 Value::Text(s) | Value::Json(s) => Literal::String(s),
15730 Value::Bool(b) => Literal::Bool(b),
15731 Value::Vector(v) => Literal::Vector(v),
15732 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
15733 Value::Date(d) => Literal::String(eval::format_date(d)),
15734 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
15735 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
15741 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
15746 Value::TextArray(items) => Literal::TextArray(items),
15751 Value::IntArray(items) => Literal::IntArray(items),
15752 Value::BigIntArray(items) => Literal::BigIntArray(items),
15753 Value::Interval { months, micros } => Literal::Interval {
15754 months,
15755 micros,
15756 text: eval::format_interval(months, micros),
15757 },
15758 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
15761 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
15762 v => Literal::String(alloc::format!("{v:?}")),
15766 }
15767}
15768
15769fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
15770 let Some(now) = now_micros else {
15771 return;
15772 };
15773 match stmt {
15774 Statement::Select(s) => rewrite_select_clock(s, now),
15775 Statement::Insert(ins) => {
15776 for row in &mut ins.rows {
15777 for e in row {
15778 rewrite_expr_clock(e, now);
15779 }
15780 }
15781 if let Some(clause) = &mut ins.on_conflict
15785 && let spg_sql::ast::OnConflictAction::Update {
15786 assignments,
15787 where_,
15788 } = &mut clause.action
15789 {
15790 for (_, e) in assignments.iter_mut() {
15791 rewrite_expr_clock(e, now);
15792 }
15793 if let Some(w) = where_ {
15794 rewrite_expr_clock(w, now);
15795 }
15796 }
15797 }
15798 Statement::Update(u) => {
15802 for (_, e) in &mut u.assignments {
15803 rewrite_expr_clock(e, now);
15804 }
15805 if let Some(w) = &mut u.where_ {
15806 rewrite_expr_clock(w, now);
15807 }
15808 }
15809 Statement::Delete(d) => {
15810 if let Some(w) = &mut d.where_ {
15811 rewrite_expr_clock(w, now);
15812 }
15813 }
15814 _ => {}
15815 }
15816}
15817
15818fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
15819 let _ = walk_select_exprs_mut(s, &mut |e| {
15824 rewrite_expr_clock(e, now);
15825 Ok(())
15826 });
15827}
15828
15829fn rewrite_expr_clock(e: &mut Expr, now: i64) {
15837 if let Some(replacement) = clock_replacement_for(e, now) {
15841 *e = replacement;
15842 return;
15843 }
15844 match e {
15845 Expr::AggregateOrdered { call, order_by, .. } => {
15846 rewrite_expr_clock(call, now);
15847 for o in order_by.iter_mut() {
15848 rewrite_expr_clock(&mut o.expr, now);
15849 }
15850 }
15851 Expr::Binary { lhs, rhs, .. } => {
15852 rewrite_expr_clock(lhs, now);
15853 rewrite_expr_clock(rhs, now);
15854 }
15855 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15856 rewrite_expr_clock(expr, now);
15857 }
15858 Expr::FunctionCall { args, .. } => {
15859 for a in args {
15860 rewrite_expr_clock(a, now);
15861 }
15862 }
15863 Expr::Like { expr, pattern, .. } => {
15864 rewrite_expr_clock(expr, now);
15865 rewrite_expr_clock(pattern, now);
15866 }
15867 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
15868 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
15872 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
15873 Expr::InSubquery { expr, subquery, .. } => {
15874 rewrite_expr_clock(expr, now);
15875 rewrite_select_clock(subquery, now);
15876 }
15877 Expr::WindowFunction {
15880 args,
15881 partition_by,
15882 order_by,
15883 ..
15884 } => {
15885 for a in args {
15886 rewrite_expr_clock(a, now);
15887 }
15888 for p in partition_by {
15889 rewrite_expr_clock(p, now);
15890 }
15891 for (e, _, _) in order_by {
15892 rewrite_expr_clock(e, now);
15893 }
15894 }
15895 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
15896 Expr::Array(items) => {
15897 for elem in items {
15898 rewrite_expr_clock(elem, now);
15899 }
15900 }
15901 Expr::ArraySubscript { target, index } => {
15902 rewrite_expr_clock(target, now);
15903 rewrite_expr_clock(index, now);
15904 }
15905 Expr::AnyAll { expr, array, .. } => {
15906 rewrite_expr_clock(expr, now);
15907 rewrite_expr_clock(array, now);
15908 }
15909 Expr::Case {
15910 operand,
15911 branches,
15912 else_branch,
15913 } => {
15914 if let Some(o) = operand {
15915 rewrite_expr_clock(o, now);
15916 }
15917 for (w, t) in branches {
15918 rewrite_expr_clock(w, now);
15919 rewrite_expr_clock(t, now);
15920 }
15921 if let Some(e) = else_branch {
15922 rewrite_expr_clock(e, now);
15923 }
15924 }
15925 }
15926}
15927
15928fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
15935 let (kind, name) = match e {
15936 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
15937 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
15938 _ => return None,
15939 };
15940 enum ClockShape {
15948 Timestamp,
15949 Date,
15950 UnixSeconds,
15951 }
15952 let shape = match name.len() {
15953 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
15954 Some(ClockShape::Timestamp)
15955 }
15956 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
15957 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
15958 Some(ClockShape::UnixSeconds)
15959 }
15960 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
15961 _ => None,
15962 };
15963 let shape = shape?;
15964 let payload = match shape {
15965 ClockShape::Timestamp => now,
15966 ClockShape::Date => now.div_euclid(86_400_000_000),
15967 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
15968 };
15969 let target = match shape {
15970 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
15971 ClockShape::Date => spg_sql::ast::CastTarget::Date,
15972 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
15973 };
15974 Some(Expr::Cast {
15975 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
15976 target,
15977 })
15978}
15979
15980#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15981enum ClockSite {
15982 Fn,
15983 BareIdent,
15984}
15985
15986fn expand_group_by_all(s: &mut SelectStatement) {
15997 if !s.group_by_all {
15998 for (_, peer) in &mut s.unions {
15999 expand_group_by_all(peer);
16000 }
16001 return;
16002 }
16003 let mut groups: Vec<Expr> = Vec::new();
16004 for item in &s.items {
16005 if let SelectItem::Expr { expr, .. } = item
16006 && !aggregate::contains_aggregate(expr)
16007 {
16008 groups.push(expr.clone());
16009 }
16010 }
16011 s.group_by = Some(groups);
16012 s.group_by_all = false;
16013 for (_, peer) in &mut s.unions {
16014 expand_group_by_all(peer);
16015 }
16016}
16017
16018fn resolve_order_by_position(s: &mut SelectStatement) {
16019 for order in &mut s.order_by {
16024 match &order.expr {
16025 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
16026 if let Ok(idx_one_based) = usize::try_from(*n) {
16027 let idx = idx_one_based - 1;
16028 if idx < s.items.len()
16029 && let SelectItem::Expr { expr, .. } = &s.items[idx]
16030 {
16031 order.expr = expr.clone();
16032 }
16033 }
16034 }
16035 Expr::Column(c) if c.qualifier.is_none() => {
16036 for item in &s.items {
16038 if let SelectItem::Expr {
16039 expr,
16040 alias: Some(a),
16041 } = item
16042 && a == &c.name
16043 {
16044 order.expr = expr.clone();
16045 break;
16046 }
16047 }
16048 }
16049 _ => {}
16050 }
16051 }
16052 for (_, peer) in &mut s.unions {
16053 resolve_order_by_position(peer);
16054 }
16055}
16056
16057fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
16070 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
16071 match keep {
16072 Some(k) if k < tagged.len() && k > 0 => {
16073 let pivot = k - 1;
16074 tagged.select_nth_unstable_by(pivot, cmp);
16075 tagged[..k].sort_by(cmp);
16076 tagged.truncate(k);
16077 }
16078 _ => {
16079 tagged.sort_by(cmp);
16080 }
16081 }
16082}
16083
16084fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
16085 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
16086}
16087
16088fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
16092 use core::cmp::Ordering;
16093 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
16094 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
16095 let ord = if descs.get(i).copied().unwrap_or(false) {
16096 ord.reverse()
16097 } else {
16098 ord
16099 };
16100 if ord != Ordering::Equal {
16101 return ord;
16102 }
16103 }
16104 Ordering::Equal
16105}
16106
16107fn build_order_keys(
16110 order_by: &[OrderBy],
16111 row: &Row,
16112 ctx: &EvalContext,
16113) -> Result<Vec<f64>, EngineError> {
16114 let mut keys = Vec::with_capacity(order_by.len());
16115 for o in order_by {
16116 let v = eval::eval_expr(&o.expr, row, ctx)?;
16117 if matches!(v, Value::Null) {
16124 let nf = o.nulls_first.unwrap_or(o.desc);
16125 keys.push(if nf == o.desc {
16126 f64::INFINITY
16127 } else {
16128 f64::NEG_INFINITY
16129 });
16130 } else {
16131 keys.push(value_to_order_key(&v)?);
16132 }
16133 }
16134 Ok(keys)
16135}
16136
16137fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
16141 if let Some(off) = offset {
16142 let off = off as usize;
16143 if off >= rows.len() {
16144 rows.clear();
16145 } else {
16146 rows.drain(..off);
16147 }
16148 }
16149 if let Some(n) = limit {
16150 rows.truncate(n as usize);
16151 }
16152}
16153
16154fn apply_offset_and_limit_tagged(
16165 tagged: &mut Vec<(Vec<f64>, Row)>,
16166 offset: Option<u32>,
16167 limit: Option<u32>,
16168 with_ties: bool,
16169) {
16170 if let Some(off) = offset {
16171 let off = off as usize;
16172 if off >= tagged.len() {
16173 tagged.clear();
16174 } else {
16175 tagged.drain(..off);
16176 }
16177 }
16178 if let Some(n) = limit {
16179 let n = n as usize;
16180 if with_ties && n > 0 && n < tagged.len() {
16181 let cutoff_key = tagged[n - 1].0.clone();
16182 let mut end = n;
16183 while end < tagged.len() && tagged[end].0 == cutoff_key {
16184 end += 1;
16185 }
16186 tagged.truncate(end);
16187 } else {
16188 tagged.truncate(n);
16189 }
16190 }
16191}
16192
16193fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
16199 if stmt.limit_with_ties && stmt.order_by.is_empty() {
16200 return Err(EngineError::Unsupported(alloc::string::String::from(
16201 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
16202 )));
16203 }
16204 Ok(())
16205}
16206
16207fn resolve_foreign_key(
16221 local_table_name: &str,
16222 local_cols: &[ColumnSchema],
16223 fk: spg_sql::ast::ForeignKeyConstraint,
16224 catalog: &Catalog,
16225) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
16226 let mut local_columns = Vec::with_capacity(fk.columns.len());
16228 for name in &fk.columns {
16229 let pos = local_cols
16230 .iter()
16231 .position(|c| c.name == *name)
16232 .ok_or_else(|| {
16233 EngineError::Unsupported(alloc::format!(
16234 "FOREIGN KEY references unknown local column {name:?}"
16235 ))
16236 })?;
16237 local_columns.push(pos);
16238 }
16239 let is_self_ref = fk.parent_table == local_table_name;
16243 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
16244 (local_cols, local_table_name)
16245 } else {
16246 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
16247 EngineError::Storage(StorageError::TableNotFound {
16248 name: fk.parent_table.clone(),
16249 })
16250 })?;
16251 (
16252 parent_table.schema().columns.as_slice(),
16253 fk.parent_table.as_str(),
16254 )
16255 };
16256 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
16261 if fk.columns.len() != 1 {
16262 return Err(EngineError::Unsupported(
16263 "composite FOREIGN KEY without explicit parent column list is not supported \
16264 — list the parent columns explicitly"
16265 .into(),
16266 ));
16267 }
16268 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
16270 .ok_or_else(|| {
16271 EngineError::Unsupported(alloc::format!(
16272 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
16273 to default the FOREIGN KEY against"
16274 ))
16275 })?;
16276 alloc::vec![pos]
16277 } else {
16278 let mut out = Vec::with_capacity(fk.parent_columns.len());
16279 for name in &fk.parent_columns {
16280 let pos = parent_cols_for_lookup
16281 .iter()
16282 .position(|c| c.name == *name)
16283 .ok_or_else(|| {
16284 EngineError::Unsupported(alloc::format!(
16285 "FOREIGN KEY references unknown parent column \
16286 {name:?} on table {parent_table_str:?}"
16287 ))
16288 })?;
16289 out.push(pos);
16290 }
16291 out
16292 };
16293 if parent_columns.len() != local_columns.len() {
16294 return Err(EngineError::Unsupported(alloc::format!(
16295 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
16296 local_columns.len(),
16297 parent_columns.len()
16298 )));
16299 }
16300 if !is_self_ref {
16310 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
16311 let primary_parent_col = parent_columns[0];
16312 let has_btree = parent_table
16313 .schema()
16314 .columns
16315 .get(primary_parent_col)
16316 .is_some()
16317 && parent_table.indices().iter().any(|idx| {
16318 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16319 && idx.column_position == primary_parent_col
16320 && idx.partial_predicate.is_none()
16321 });
16322 if !has_btree {
16323 return Err(EngineError::Unsupported(alloc::format!(
16324 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
16325 index — create one with `CREATE INDEX ... ON {} ({})` first",
16326 parent_table_str,
16327 parent_table_str,
16328 parent_table.schema().columns[primary_parent_col].name,
16329 )));
16330 }
16331 }
16332 let on_delete = fk_action_sql_to_storage(fk.on_delete);
16333 let on_update = fk_action_sql_to_storage(fk.on_update);
16334 Ok(spg_storage::ForeignKeyConstraint {
16335 name: fk.name,
16336 local_columns,
16337 parent_table: fk.parent_table,
16338 parent_columns,
16339 on_delete,
16340 on_update,
16341 })
16342}
16343
16344fn pick_pk_index_column(
16350 catalog: &Catalog,
16351 parent_name: &str,
16352 is_self_ref: bool,
16353 local_cols: &[ColumnSchema],
16354) -> Option<usize> {
16355 if is_self_ref {
16356 let _ = local_cols;
16360 return Some(0);
16361 }
16362 let parent = catalog.get(parent_name)?;
16363 parent.indices().iter().find_map(|idx| {
16364 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16365 && idx.partial_predicate.is_none()
16366 && idx.included_columns.is_empty()
16367 && idx.expression.is_none()
16368 {
16369 Some(idx.column_position)
16370 } else {
16371 None
16372 }
16373 })
16374}
16375
16376fn resolve_on_conflict_columns(
16387 catalog: &Catalog,
16388 table_name: &str,
16389 target: &[String],
16390) -> Result<(Vec<usize>, bool), EngineError> {
16391 let table = catalog.get(table_name).ok_or_else(|| {
16392 EngineError::Storage(StorageError::TableNotFound {
16393 name: table_name.into(),
16394 })
16395 })?;
16396 if target.is_empty() {
16397 if let Some(uc) = table.schema().uniqueness_constraints.first() {
16407 return Ok((uc.columns.clone(), uc.nulls_not_distinct));
16408 }
16409 let pos = table
16410 .indices()
16411 .iter()
16412 .find_map(|idx| {
16413 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16414 && idx.partial_predicate.is_none()
16415 && idx.included_columns.is_empty()
16416 && idx.expression.is_none()
16417 {
16418 Some(idx.column_position)
16419 } else {
16420 None
16421 }
16422 })
16423 .ok_or_else(|| {
16424 EngineError::Unsupported(alloc::format!(
16425 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
16426 ))
16427 })?;
16428 return Ok((alloc::vec![pos], false));
16429 }
16430 let mut out = Vec::with_capacity(target.len());
16431 for name in target {
16432 let pos = table
16433 .schema()
16434 .columns
16435 .iter()
16436 .position(|c| c.name == *name)
16437 .ok_or_else(|| {
16438 EngineError::Unsupported(alloc::format!(
16439 "ON CONFLICT target column {name:?} not found on {table_name:?}"
16440 ))
16441 })?;
16442 out.push(pos);
16443 }
16444 let mut sorted = out.clone();
16447 sorted.sort_unstable();
16448 let nnd = table.schema().uniqueness_constraints.iter().any(|uc| {
16449 let mut u = uc.columns.clone();
16450 u.sort_unstable();
16451 u == sorted && uc.nulls_not_distinct
16452 });
16453 Ok((out, nnd))
16454}
16455
16456fn on_conflict_key_exists(
16459 catalog: &Catalog,
16460 table_name: &str,
16461 column_pos: usize,
16462 key: &Value,
16463) -> bool {
16464 let Some(table) = catalog.get(table_name) else {
16465 return false;
16466 };
16467 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
16468 return false;
16469 };
16470 table.indices().iter().any(|idx| {
16471 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16472 && idx.column_position == column_pos
16473 && idx.partial_predicate.is_none()
16474 && !idx.lookup_eq(&idx_key).is_empty()
16475 })
16476}
16477
16478fn lookup_row_position_by_keys(
16484 catalog: &Catalog,
16485 table_name: &str,
16486 column_positions: &[usize],
16487 key: &[&Value],
16488) -> Option<usize> {
16489 let table = catalog.get(table_name)?;
16490 table.rows().iter().position(|r| {
16491 column_positions
16492 .iter()
16493 .enumerate()
16494 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
16495 })
16496}
16497
16498fn on_conflict_keys_exist(
16503 catalog: &Catalog,
16504 table_name: &str,
16505 column_positions: &[usize],
16506 key: &[&Value],
16507) -> bool {
16508 if column_positions.len() == 1 {
16509 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
16510 }
16511 let Some(table) = catalog.get(table_name) else {
16512 return false;
16513 };
16514 table.rows().iter().any(|r| {
16515 column_positions
16516 .iter()
16517 .enumerate()
16518 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
16519 })
16520}
16521
16522fn apply_on_conflict_assignments(
16535 catalog: &Catalog,
16536 table_name: &str,
16537 target_pos: usize,
16538 incoming: &[Value],
16539 assignments: &[(String, Expr)],
16540 where_: Option<&Expr>,
16541) -> Result<Option<Vec<Value>>, EngineError> {
16542 let table = catalog.get(table_name).ok_or_else(|| {
16543 EngineError::Storage(StorageError::TableNotFound {
16544 name: table_name.into(),
16545 })
16546 })?;
16547 let schema_cols = table.schema().columns.clone();
16548 let existing = table
16549 .rows()
16550 .get(target_pos)
16551 .ok_or_else(|| {
16552 EngineError::Unsupported(alloc::format!(
16553 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
16554 ))
16555 })?
16556 .clone();
16557 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
16558 if let Some(w) = where_ {
16560 let pred = w.clone();
16561 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
16562 let v = eval::eval_expr(&pred, &existing, &ctx)?;
16563 if !matches!(v, Value::Bool(true)) {
16564 return Ok(None);
16565 }
16566 }
16567 let mut new_values = existing.values.clone();
16568 for (col_name, expr) in assignments {
16569 let target_idx = schema_cols
16570 .iter()
16571 .position(|c| c.name == *col_name)
16572 .ok_or_else(|| {
16573 EngineError::Eval(EvalError::ColumnNotFound {
16574 name: col_name.clone(),
16575 })
16576 })?;
16577 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
16578 let v = eval::eval_expr(&sub, &existing, &ctx)?;
16579 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
16580 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
16581 new_values[target_idx] = coerced;
16582 }
16583 Ok(Some(new_values))
16584}
16585
16586fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
16591 use spg_sql::ast::ColumnName;
16592 match expr {
16593 Expr::Column(ColumnName { qualifier, name })
16594 if qualifier
16595 .as_deref()
16596 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
16597 {
16598 let pos = schema_cols.iter().position(|c| c.name == name);
16599 match pos {
16600 Some(p) => {
16601 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
16602 value_to_literal_expr(v)
16603 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
16604 }
16605 None => Expr::Column(ColumnName { qualifier, name }),
16606 }
16607 }
16608 Expr::Binary { op, lhs, rhs } => Expr::Binary {
16609 op,
16610 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
16611 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
16612 },
16613 Expr::Unary { op, expr } => Expr::Unary {
16614 op,
16615 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
16616 },
16617 Expr::FunctionCall { name, args } => Expr::FunctionCall {
16618 name,
16619 args: args
16620 .into_iter()
16621 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
16622 .collect(),
16623 },
16624 other => other,
16625 }
16626}
16627
16628fn enforce_uniqueness_inserts(
16651 catalog: &Catalog,
16652 child_table: &str,
16653 constraints: &[spg_storage::UniquenessConstraint],
16654 rows: &[Vec<Value>],
16655) -> Result<(), EngineError> {
16656 if constraints.is_empty() {
16657 return Ok(());
16658 }
16659 let table = catalog.get(child_table).ok_or_else(|| {
16660 EngineError::Storage(StorageError::TableNotFound {
16661 name: child_table.into(),
16662 })
16663 })?;
16664 let schema = table.schema();
16665 for uc in constraints {
16675 let fold_key = |values: &[Value]| -> Vec<Value> {
16676 uc.columns
16677 .iter()
16678 .map(|&i| {
16679 let v = values.get(i).cloned().unwrap_or(Value::Null);
16680 collated_key_cell(&v, i, schema)
16681 })
16682 .collect()
16683 };
16684 let mut seen: hashbrown::HashSet<String> =
16685 hashbrown::HashSet::with_capacity(table.rows().len() + rows.len());
16686 for prow in table.rows() {
16687 let key = fold_key(&prow.values);
16688 if key.iter().any(|v| matches!(v, Value::Null)) && !uc.nulls_not_distinct {
16689 continue;
16690 }
16691 seen.insert(aggregate::encode_key(&key));
16692 }
16693 for (batch_idx, row_values) in rows.iter().enumerate() {
16694 let key = fold_key(row_values);
16695 if key.iter().any(|v| matches!(v, Value::Null)) && !uc.nulls_not_distinct {
16696 continue;
16697 }
16698 if !seen.insert(aggregate::encode_key(&key)) {
16699 let kind = if uc.is_primary_key {
16700 "PRIMARY KEY"
16701 } else {
16702 "UNIQUE"
16703 };
16704 let col_names: Vec<String> = uc
16705 .columns
16706 .iter()
16707 .map(|&i| table.schema().columns[i].name.clone())
16708 .collect();
16709 return Err(EngineError::Unsupported(alloc::format!(
16710 "{kind} violation on {child_table:?} columns {col_names:?}: \
16711 row #{batch_idx} duplicates an existing key"
16712 )));
16713 }
16714 }
16715 }
16716 Ok(())
16717}
16718
16719fn collated_key_cell(
16726 v: &spg_storage::Value,
16727 column_position: usize,
16728 schema: &spg_storage::TableSchema,
16729) -> spg_storage::Value {
16730 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
16731 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
16732 spg_storage::Value::Text(s.to_ascii_lowercase())
16733 }
16734 _ => v.clone(),
16735 }
16736}
16737
16738fn predicate_truthy(v: &spg_storage::Value) -> bool {
16746 use spg_storage::Value as V;
16747 match v {
16748 V::Bool(b) => *b,
16749 V::Int(n) => *n != 0,
16750 V::BigInt(n) => *n != 0,
16751 V::SmallInt(n) => *n != 0,
16752 _ => false,
16753 }
16754}
16755
16756fn check_existing_unique_violation(
16761 idx: &spg_storage::Index,
16762 schema: &spg_storage::TableSchema,
16763 rows: &[spg_storage::Row],
16764) -> Result<(), EngineError> {
16765 let predicate_expr = match idx.partial_predicate.as_deref() {
16766 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16767 EngineError::Unsupported(alloc::format!(
16768 "stored partial predicate {s:?} failed to re-parse: {e:?}"
16769 ))
16770 })?),
16771 None => None,
16772 };
16773 let ctx = eval::EvalContext::new(&schema.columns, None);
16774 let key_positions = unique_key_positions(idx);
16775 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
16776 for row in rows {
16777 if let Some(expr) = &predicate_expr {
16778 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
16779 EngineError::Unsupported(alloc::format!(
16780 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
16781 ))
16782 })?;
16783 if !predicate_truthy(&v) {
16784 continue;
16785 }
16786 }
16787 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
16788 .iter()
16789 .map(|&p| {
16790 let v = row
16791 .values
16792 .get(p)
16793 .cloned()
16794 .unwrap_or(spg_storage::Value::Null);
16795 collated_key_cell(&v, p, schema)
16796 })
16797 .collect();
16798 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16799 continue;
16800 }
16801 if seen.iter().any(|other| *other == key) {
16802 return Err(EngineError::Unsupported(alloc::format!(
16803 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
16804 idx.name
16805 )));
16806 }
16807 seen.push(key);
16808 }
16809 Ok(())
16810}
16811
16812fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
16816 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
16817 out.push(idx.column_position);
16818 out.extend_from_slice(&idx.extra_column_positions);
16819 out
16820}
16821
16822fn enforce_unique_index_inserts(
16830 catalog: &Catalog,
16831 table_name: &str,
16832 rows: &[alloc::vec::Vec<spg_storage::Value>],
16833) -> Result<(), EngineError> {
16834 let table = catalog.get(table_name).ok_or_else(|| {
16835 EngineError::Storage(StorageError::TableNotFound {
16836 name: table_name.into(),
16837 })
16838 })?;
16839 let schema = table.schema();
16840 let ctx = eval::EvalContext::new(&schema.columns, None);
16841 for idx in table.indices() {
16842 if !idx.is_unique {
16843 continue;
16844 }
16845 let predicate_expr = match idx.partial_predicate.as_deref() {
16847 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16848 EngineError::Unsupported(alloc::format!(
16849 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
16850 idx.name
16851 ))
16852 })?),
16853 None => None,
16854 };
16855 let key_positions = unique_key_positions(idx);
16856 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
16857 key_positions
16858 .iter()
16859 .map(|&p| {
16860 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
16861 collated_key_cell(&v, p, schema)
16862 })
16863 .collect()
16864 };
16865 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
16866 let Some(expr) = &predicate_expr else {
16867 return Ok(true);
16868 };
16869 let tmp_row = spg_storage::Row {
16870 values: values.to_vec(),
16871 };
16872 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
16873 EngineError::Unsupported(alloc::format!(
16874 "UNIQUE INDEX {:?} predicate eval: {e:?}",
16875 idx.name
16876 ))
16877 })?;
16878 Ok(predicate_truthy(&v))
16879 };
16880 let mut seen: hashbrown::HashSet<String> =
16885 hashbrown::HashSet::with_capacity(table.rows().len() + rows.len());
16886 for prow in table.rows() {
16887 if !participates(&prow.values)? {
16888 continue;
16889 }
16890 let key = key_of(&prow.values);
16891 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16892 continue;
16893 }
16894 seen.insert(aggregate::encode_key(&key));
16895 }
16896 for (batch_idx, row_values) in rows.iter().enumerate() {
16897 if !participates(row_values)? {
16898 continue;
16899 }
16900 let key = key_of(row_values);
16901 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16902 continue;
16903 }
16904 if !seen.insert(aggregate::encode_key(&key)) {
16905 return Err(EngineError::Unsupported(alloc::format!(
16906 "UNIQUE INDEX {:?} violation on {table_name:?}: \
16907 row #{batch_idx} duplicates an existing key",
16908 idx.name
16909 )));
16910 }
16911 }
16912 }
16913 Ok(())
16914}
16915
16916fn any_column_changed(
16924 filter_cols: &[String],
16925 schema_cols: &[ColumnSchema],
16926 old_row: &Row,
16927 new_row: &Row,
16928) -> bool {
16929 for col_name in filter_cols {
16930 let Some(pos) = schema_cols
16931 .iter()
16932 .position(|c| c.name.eq_ignore_ascii_case(col_name))
16933 else {
16934 continue;
16935 };
16936 let old_v = old_row.values.get(pos);
16937 let new_v = new_row.values.get(pos);
16938 if old_v != new_v {
16939 return true;
16940 }
16941 }
16942 false
16943}
16944
16945fn enforce_check_constraints(
16950 catalog: &Catalog,
16951 table_name: &str,
16952 rows: &[alloc::vec::Vec<spg_storage::Value>],
16953) -> Result<(), EngineError> {
16954 let table = catalog.get(table_name).ok_or_else(|| {
16955 EngineError::Storage(StorageError::TableNotFound {
16956 name: table_name.into(),
16957 })
16958 })?;
16959 let schema = table.schema();
16960 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
16964 alloc::vec::Vec::new();
16965 for (idx, col) in schema.columns.iter().enumerate() {
16966 let Some(dname) = &col.user_domain_type else {
16967 continue;
16968 };
16969 let Some(dom) = catalog.domain_types().get(dname) else {
16970 continue;
16971 };
16972 let mut parsed_for_col: alloc::vec::Vec<Expr> =
16973 alloc::vec::Vec::with_capacity(dom.checks.len());
16974 for src in &dom.checks {
16975 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
16976 EngineError::Unsupported(alloc::format!(
16977 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
16978 col.name
16979 ))
16980 })?;
16981 parsed_for_col.push(expr);
16982 }
16983 if !parsed_for_col.is_empty() {
16984 domain_checks_per_col.push((idx, parsed_for_col));
16985 }
16986 }
16987 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
16988 return Ok(());
16989 }
16990 let ctx = eval::EvalContext::new(&schema.columns, None);
16991 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
16992 for (i, src) in schema.checks.iter().enumerate() {
16993 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
16994 EngineError::Unsupported(alloc::format!(
16995 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
16996 ))
16997 })?;
16998 parsed.push((i, expr));
16999 }
17000 for (batch_idx, row_values) in rows.iter().enumerate() {
17001 let tmp_row = spg_storage::Row {
17002 values: row_values.clone(),
17003 };
17004 for (i, expr) in &parsed {
17005 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
17006 EngineError::Unsupported(alloc::format!(
17007 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
17008 ))
17009 })?;
17010 if matches!(v, spg_storage::Value::Bool(false)) {
17012 return Err(EngineError::Unsupported(alloc::format!(
17013 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
17014 schema.checks[*i]
17015 )));
17016 }
17017 }
17018 for (col_idx, checks) in &domain_checks_per_col {
17024 let cell = row_values
17025 .get(*col_idx)
17026 .cloned()
17027 .unwrap_or(spg_storage::Value::Null);
17028 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
17029 "value",
17030 schema.columns[*col_idx].ty,
17031 schema.columns[*col_idx].nullable,
17032 )];
17033 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
17034 let synth_row = spg_storage::Row {
17035 values: alloc::vec![cell],
17036 };
17037 for (ci, expr) in checks.iter().enumerate() {
17038 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
17039 EngineError::Unsupported(alloc::format!(
17040 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
17041 schema.columns[*col_idx].name
17042 ))
17043 })?;
17044 if matches!(v, spg_storage::Value::Bool(false)) {
17045 return Err(EngineError::Unsupported(alloc::format!(
17046 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
17047 schema.columns[*col_idx].name
17048 )));
17049 }
17050 }
17051 }
17052 }
17053 Ok(())
17054}
17055
17056fn enforce_fk_inserts(
17057 catalog: &Catalog,
17058 child_table: &str,
17059 fks: &[spg_storage::ForeignKeyConstraint],
17060 rows: &[Vec<Value>],
17061) -> Result<(), EngineError> {
17062 for fk in fks {
17063 let parent_is_self = fk.parent_table == child_table;
17064 let parent = if parent_is_self {
17065 catalog.get(child_table).ok_or_else(|| {
17068 EngineError::Storage(StorageError::TableNotFound {
17069 name: child_table.into(),
17070 })
17071 })?
17072 } else {
17073 catalog.get(&fk.parent_table).ok_or_else(|| {
17074 EngineError::Storage(StorageError::TableNotFound {
17075 name: fk.parent_table.clone(),
17076 })
17077 })?
17078 };
17079 for (batch_idx, row_values) in rows.iter().enumerate() {
17080 if fk.local_columns.len() == 1 {
17084 let v = &row_values[fk.local_columns[0]];
17085 if matches!(v, Value::Null) {
17086 continue;
17087 }
17088 let parent_col = fk.parent_columns[0];
17089 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
17090 EngineError::Unsupported(alloc::format!(
17091 "FOREIGN KEY column value of type {:?} is not index-eligible",
17092 v.data_type()
17093 ))
17094 })?;
17095 let present_committed = parent.indices().iter().any(|idx| {
17096 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
17097 && idx.column_position == parent_col
17098 && idx.partial_predicate.is_none()
17099 && !idx.lookup_eq(&key).is_empty()
17100 });
17101 let present_in_batch = parent_is_self
17105 && rows[..batch_idx]
17106 .iter()
17107 .any(|earlier| earlier.get(parent_col) == Some(v));
17108 if !(present_committed || present_in_batch) {
17109 return Err(EngineError::Unsupported(alloc::format!(
17110 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
17111 fk.parent_table,
17112 parent
17113 .schema()
17114 .columns
17115 .get(parent_col)
17116 .map_or("?", |c| c.name.as_str()),
17117 v,
17118 )));
17119 }
17120 } else {
17121 if fk
17125 .local_columns
17126 .iter()
17127 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
17128 {
17129 continue;
17130 }
17131 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
17132 let parent_match_committed = parent.rows().iter().any(|prow| {
17133 fk.parent_columns
17134 .iter()
17135 .enumerate()
17136 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
17137 });
17138 let parent_match_in_batch = parent_is_self
17139 && rows[..batch_idx].iter().any(|earlier| {
17140 fk.parent_columns
17141 .iter()
17142 .enumerate()
17143 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
17144 });
17145 if !(parent_match_committed || parent_match_in_batch) {
17146 return Err(EngineError::Unsupported(alloc::format!(
17147 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
17148 fk.parent_table,
17149 )));
17150 }
17151 }
17152 }
17153 }
17154 Ok(())
17155}
17156
17157#[derive(Debug, Clone)]
17161struct FkChildStep {
17162 child_table: String,
17163 action: FkChildAction,
17164}
17165
17166#[derive(Debug, Clone)]
17167enum FkChildAction {
17168 Delete { positions: Vec<usize> },
17170 SetNull {
17174 positions: Vec<usize>,
17175 columns: Vec<usize>,
17176 },
17177 SetDefault {
17181 positions: Vec<usize>,
17182 columns: Vec<usize>,
17183 defaults: Vec<Value>,
17184 },
17185}
17186
17187fn plan_fk_parent_deletions(
17203 catalog: &Catalog,
17204 parent_table_name: &str,
17205 to_delete_positions: &[usize],
17206 to_delete_rows: &[Vec<Value>],
17207) -> Result<Vec<FkChildStep>, EngineError> {
17208 use alloc::collections::{BTreeMap, BTreeSet};
17209 if to_delete_rows.is_empty() {
17210 return Ok(Vec::new());
17211 }
17212 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
17213 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
17215 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17216 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
17217 for &p in to_delete_positions {
17218 visited.insert((parent_table_name.to_string(), p));
17219 }
17220 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
17221 .iter()
17222 .map(|r| (parent_table_name.to_string(), r.clone()))
17223 .collect();
17224 while let Some((cur_parent, parent_row)) = work.pop() {
17225 for child_name in catalog.table_names() {
17226 let child = catalog
17227 .get(&child_name)
17228 .expect("table_names → catalog.get round-trip is total");
17229 for fk in &child.schema().foreign_keys {
17230 if fk.parent_table != cur_parent {
17231 continue;
17232 }
17233 let parent_key: Vec<&Value> = fk
17234 .parent_columns
17235 .iter()
17236 .map(|&pi| &parent_row[pi])
17237 .collect();
17238 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
17239 continue;
17240 }
17241 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
17242 if child_name == cur_parent
17243 && visited.contains(&(child_name.clone(), child_row_idx))
17244 {
17245 continue;
17246 }
17247 let matches_key = fk
17248 .local_columns
17249 .iter()
17250 .enumerate()
17251 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
17252 if !matches_key {
17253 continue;
17254 }
17255 match fk.on_delete {
17256 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
17257 return Err(EngineError::Unsupported(alloc::format!(
17258 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
17259 restricted by FK from {child_name:?}.{:?}",
17260 fk.local_columns,
17261 )));
17262 }
17263 spg_storage::FkAction::Cascade => {
17264 if visited.insert((child_name.clone(), child_row_idx)) {
17265 delete_plan
17266 .entry(child_name.clone())
17267 .or_default()
17268 .insert(child_row_idx);
17269 work.push((child_name.clone(), child_row.values.clone()));
17270 }
17271 }
17272 spg_storage::FkAction::SetNull => {
17273 for &li in &fk.local_columns {
17275 let col = child.schema().columns.get(li).ok_or_else(|| {
17276 EngineError::Unsupported(alloc::format!(
17277 "FK local column {li} missing in {child_name:?}"
17278 ))
17279 })?;
17280 if !col.nullable {
17281 return Err(EngineError::Unsupported(alloc::format!(
17282 "FOREIGN KEY ON DELETE SET NULL: column \
17283 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
17284 col.name,
17285 )));
17286 }
17287 }
17288 let entry = setnull_plan.entry(child_name.clone()).or_default();
17289 for &li in &fk.local_columns {
17290 entry.insert((child_row_idx, li));
17291 }
17292 }
17293 spg_storage::FkAction::SetDefault => {
17294 let entry = setdefault_plan.entry(child_name.clone()).or_default();
17296 for &li in &fk.local_columns {
17297 let col = child.schema().columns.get(li).ok_or_else(|| {
17298 EngineError::Unsupported(alloc::format!(
17299 "FK local column {li} missing in {child_name:?}"
17300 ))
17301 })?;
17302 let default = col.default.clone().ok_or_else(|| {
17303 EngineError::Unsupported(alloc::format!(
17304 "FOREIGN KEY ON DELETE SET DEFAULT: column \
17305 {child_name:?}.{:?} has no DEFAULT declared",
17306 col.name,
17307 ))
17308 })?;
17309 entry.insert((child_row_idx, li), default);
17310 }
17311 }
17312 }
17313 }
17314 }
17315 }
17316 }
17317 let mut steps: Vec<FkChildStep> = Vec::new();
17325 for (child_table, entries) in setnull_plan {
17326 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
17327 steps.push(FkChildStep {
17328 child_table,
17329 action: FkChildAction::SetNull { positions, columns },
17330 });
17331 }
17332 for (child_table, entries) in setdefault_plan {
17333 let mut positions = Vec::with_capacity(entries.len());
17334 let mut columns = Vec::with_capacity(entries.len());
17335 let mut defaults = Vec::with_capacity(entries.len());
17336 for ((p, c), v) in entries {
17337 positions.push(p);
17338 columns.push(c);
17339 defaults.push(v);
17340 }
17341 steps.push(FkChildStep {
17342 child_table,
17343 action: FkChildAction::SetDefault {
17344 positions,
17345 columns,
17346 defaults,
17347 },
17348 });
17349 }
17350 for (child_table, positions) in delete_plan {
17351 steps.push(FkChildStep {
17352 child_table,
17353 action: FkChildAction::Delete {
17354 positions: positions.into_iter().collect(),
17355 },
17356 });
17357 }
17358 Ok(steps)
17359}
17360
17361fn plan_fk_parent_updates(
17378 catalog: &Catalog,
17379 parent_table_name: &str,
17380 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
17381) -> Result<Vec<FkChildStep>, EngineError> {
17382 use alloc::collections::BTreeMap;
17383 if plan_with_old.is_empty() {
17384 return Ok(Vec::new());
17385 }
17386 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
17391 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
17392 BTreeMap::new();
17393 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17394 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17396
17397 for child_name in catalog.table_names() {
17398 let child = catalog
17399 .get(&child_name)
17400 .expect("table_names → catalog.get total");
17401 for fk in &child.schema().foreign_keys {
17402 if fk.parent_table != parent_table_name {
17403 continue;
17404 }
17405 for (_pos, old_row, new_row) in plan_with_old {
17406 let key_changed = fk
17408 .parent_columns
17409 .iter()
17410 .any(|&pi| old_row.get(pi) != new_row.get(pi));
17411 if !key_changed {
17412 continue;
17413 }
17414 let old_key: Vec<&Value> =
17416 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
17417 if old_key.iter().any(|v| matches!(v, Value::Null)) {
17418 continue;
17420 }
17421 let new_key: Vec<&Value> =
17422 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
17423 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
17424 if child_name == parent_table_name
17427 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
17428 {
17429 continue;
17430 }
17431 let matches_key = fk
17432 .local_columns
17433 .iter()
17434 .enumerate()
17435 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
17436 if !matches_key {
17437 continue;
17438 }
17439 match fk.on_update {
17440 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
17441 return Err(EngineError::Unsupported(alloc::format!(
17442 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
17443 restricted by FK from {child_name:?}.{:?}",
17444 fk.local_columns,
17445 )));
17446 }
17447 spg_storage::FkAction::Cascade => {
17448 let entry = cascade_plan.entry(child_name.clone()).or_default();
17450 for (i, &li) in fk.local_columns.iter().enumerate() {
17451 entry.insert((child_row_idx, li), new_key[i].clone());
17452 }
17453 }
17454 spg_storage::FkAction::SetNull => {
17455 for &li in &fk.local_columns {
17456 let col = child.schema().columns.get(li).ok_or_else(|| {
17457 EngineError::Unsupported(alloc::format!(
17458 "FK local column {li} missing in {child_name:?}"
17459 ))
17460 })?;
17461 if !col.nullable {
17462 return Err(EngineError::Unsupported(alloc::format!(
17463 "FOREIGN KEY ON UPDATE SET NULL: column \
17464 {child_name:?}.{:?} is NOT NULL",
17465 col.name,
17466 )));
17467 }
17468 }
17469 let entry = setnull_plan.entry(child_name.clone()).or_default();
17470 for &li in &fk.local_columns {
17471 entry.insert((child_row_idx, li));
17472 }
17473 }
17474 spg_storage::FkAction::SetDefault => {
17475 let entry = setdefault_plan.entry(child_name.clone()).or_default();
17476 for &li in &fk.local_columns {
17477 let col = child.schema().columns.get(li).ok_or_else(|| {
17478 EngineError::Unsupported(alloc::format!(
17479 "FK local column {li} missing in {child_name:?}"
17480 ))
17481 })?;
17482 let default = col.default.clone().ok_or_else(|| {
17483 EngineError::Unsupported(alloc::format!(
17484 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
17485 {child_name:?}.{:?} has no DEFAULT",
17486 col.name,
17487 ))
17488 })?;
17489 entry.insert((child_row_idx, li), default);
17490 }
17491 }
17492 }
17493 }
17494 }
17495 }
17496 }
17497 let mut steps: Vec<FkChildStep> = Vec::new();
17500 for (child_table, entries) in cascade_plan {
17501 let mut positions = Vec::with_capacity(entries.len());
17502 let mut columns = Vec::with_capacity(entries.len());
17503 let mut defaults = Vec::with_capacity(entries.len());
17504 for ((p, c), v) in entries {
17505 positions.push(p);
17506 columns.push(c);
17507 defaults.push(v);
17508 }
17509 steps.push(FkChildStep {
17514 child_table,
17515 action: FkChildAction::SetDefault {
17516 positions,
17517 columns,
17518 defaults,
17519 },
17520 });
17521 }
17522 for (child_table, entries) in setnull_plan {
17523 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
17524 steps.push(FkChildStep {
17525 child_table,
17526 action: FkChildAction::SetNull { positions, columns },
17527 });
17528 }
17529 for (child_table, entries) in setdefault_plan {
17530 let mut positions = Vec::with_capacity(entries.len());
17531 let mut columns = Vec::with_capacity(entries.len());
17532 let mut defaults = Vec::with_capacity(entries.len());
17533 for ((p, c), v) in entries {
17534 positions.push(p);
17535 columns.push(c);
17536 defaults.push(v);
17537 }
17538 steps.push(FkChildStep {
17539 child_table,
17540 action: FkChildAction::SetDefault {
17541 positions,
17542 columns,
17543 defaults,
17544 },
17545 });
17546 }
17547 let _ = delete_plan; Ok(steps)
17549}
17550
17551fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
17555 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
17556 EngineError::Storage(StorageError::TableNotFound {
17557 name: step.child_table.clone(),
17558 })
17559 })?;
17560 match &step.action {
17561 FkChildAction::Delete { positions } => {
17562 let _ = child.delete_rows(positions);
17563 }
17564 FkChildAction::SetNull { positions, columns } => {
17565 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
17566 }
17567 FkChildAction::SetDefault {
17568 positions,
17569 columns,
17570 defaults,
17571 } => {
17572 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
17573 }
17574 }
17575 Ok(())
17576}
17577
17578fn apply_per_cell_writes(
17584 child: &mut spg_storage::Table,
17585 positions: &[usize],
17586 columns: &[usize],
17587 mut value_for: impl FnMut(usize) -> Value,
17588) -> Result<(), EngineError> {
17589 use alloc::collections::BTreeMap;
17590 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
17591 for i in 0..positions.len() {
17592 by_row
17593 .entry(positions[i])
17594 .or_default()
17595 .push((columns[i], value_for(i)));
17596 }
17597 for (pos, mutations) in by_row {
17598 let mut new_values = child.rows()[pos].values.clone();
17599 for (col, v) in mutations {
17600 if let Some(slot) = new_values.get_mut(col) {
17601 *slot = v;
17602 }
17603 }
17604 child
17605 .update_row(pos, new_values)
17606 .map_err(EngineError::Storage)?;
17607 }
17608 Ok(())
17609}
17610
17611fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
17612 match a {
17613 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
17614 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
17615 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
17616 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
17617 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
17618 }
17619}
17620
17621fn resolve_column_default_free(
17627 col: &ColumnSchema,
17628 clock_fn: Option<ClockFn>,
17629) -> Result<Value, EngineError> {
17630 if let Some(rt) = &col.runtime_default {
17631 return eval_runtime_default_free(rt, col.ty, clock_fn);
17632 }
17633 Ok(col.default.clone().unwrap_or(Value::Null))
17634}
17635
17636fn eval_runtime_default_free(
17637 rt: &str,
17638 ty: DataType,
17639 clock_fn: Option<ClockFn>,
17640) -> Result<Value, EngineError> {
17641 let s = rt.trim().to_ascii_lowercase();
17642 let with_no_parens = s.trim_end_matches("()");
17648 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
17649 if with_no_parens.ends_with(')') {
17650 &with_no_parens[..open_idx]
17651 } else {
17652 with_no_parens
17653 }
17654 } else {
17655 with_no_parens
17656 };
17657 let now_us = match clock_fn {
17658 Some(f) => f(),
17659 None => 0,
17660 };
17661 let v = match canonical {
17662 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
17663 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
17664 "current_time" | "localtime" => Value::Timestamp(now_us),
17665 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
17671 other => {
17672 return Err(EngineError::Unsupported(alloc::format!(
17673 "runtime DEFAULT expression {other:?} not supported \
17674 (v7.17.0 whitelist: now() / current_timestamp / \
17675 current_date / current_time / localtimestamp / \
17676 localtime / gen_random_uuid() / \
17677 uuid_generate_v4())"
17678 )));
17679 }
17680 };
17681 coerce_value(v, ty, "DEFAULT", 0)
17682}
17683
17684fn is_runtime_default_expr(expr: &Expr) -> bool {
17690 match expr {
17691 Expr::FunctionCall { .. } => true,
17692 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
17693 _ => false,
17694 }
17695}
17696
17697fn canonicalize_set_value(
17710 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17711 col_idx: usize,
17712 col_name: &str,
17713 value: Value,
17714) -> Result<Value, EngineError> {
17715 let Some(variants) = lookup.get(&col_idx) else {
17716 return Ok(value);
17717 };
17718 match value {
17719 Value::Null => Ok(Value::Null),
17720 Value::Text(s) => {
17721 if s.is_empty() {
17722 return Ok(Value::Text(alloc::string::String::new()));
17723 }
17724 let mut present = alloc::vec![false; variants.len()];
17727 for raw in s.split(',') {
17728 let tok = raw.trim();
17729 if tok.is_empty() {
17730 continue;
17731 }
17732 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
17733 EngineError::Unsupported(alloc::format!(
17734 "column {col_name:?}: invalid SET token {tok:?}; \
17735 allowed: {variants:?}"
17736 ))
17737 })?;
17738 present[idx] = true;
17739 }
17740 let mut out = alloc::string::String::new();
17742 let mut first = true;
17743 for (i, keep) in present.iter().enumerate() {
17744 if !keep {
17745 continue;
17746 }
17747 if !first {
17748 out.push(',');
17749 }
17750 first = false;
17751 out.push_str(&variants[i]);
17752 }
17753 Ok(Value::Text(out))
17754 }
17755 other => Err(EngineError::Unsupported(alloc::format!(
17756 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
17757 other.data_type()
17758 ))),
17759 }
17760}
17761
17762fn enforce_enum_label(
17763 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17764 col_idx: usize,
17765 col_name: &str,
17766 value: &Value,
17767) -> Result<(), EngineError> {
17768 if let Some(labels) = lookup.get(&col_idx) {
17769 match value {
17770 Value::Null => Ok(()),
17771 Value::Text(s) => {
17772 if labels.iter().any(|l| l == s) {
17773 Ok(())
17774 } else {
17775 Err(EngineError::Unsupported(alloc::format!(
17776 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
17777 )))
17778 }
17779 }
17780 other => Err(EngineError::Unsupported(alloc::format!(
17781 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
17782 other.data_type()
17783 ))),
17784 }
17785 } else {
17786 Ok(())
17787 }
17788}
17789
17790fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
17791 let ty = column_type_to_data_type(c.ty);
17792 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
17793 if let Some(name) = c.user_type_ref {
17800 schema.user_enum_type = Some(name);
17801 }
17802 if let Some(expr) = c.on_update_runtime {
17805 schema.on_update_runtime = Some(alloc::format!("{expr}"));
17806 }
17807 schema.collation = match c.collation {
17811 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
17812 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
17813 };
17814 schema.is_unsigned = c.is_unsigned;
17817 schema.inline_enum_variants = c.inline_enum_variants;
17821 schema.inline_set_variants = c.inline_set_variants;
17825 if let Some(default_expr) = c.default {
17826 if is_runtime_default_expr(&default_expr) {
17832 let display = alloc::format!("{default_expr}");
17833 schema = schema.with_runtime_default(display);
17834 } else {
17835 let raw = literal_expr_to_value(default_expr)?;
17836 let coerced = coerce_value(raw, ty, &c.name, 0)?;
17837 schema = schema.with_default(coerced);
17838 }
17839 }
17840 if c.auto_increment {
17841 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
17843 return Err(EngineError::Unsupported(alloc::format!(
17844 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
17845 )));
17846 }
17847 schema = schema.with_auto_increment();
17848 }
17849 Ok(schema)
17850}
17851
17852fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
17857 let s = s.trim();
17858 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
17859 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
17861 if cleaned.len() % 2 != 0 {
17862 return Err("odd-length hex literal");
17863 }
17864 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
17865 let cleaned_bytes = cleaned.as_bytes();
17866 for i in (0..cleaned_bytes.len()).step_by(2) {
17867 let hi = hex_nibble(cleaned_bytes[i])?;
17868 let lo = hex_nibble(cleaned_bytes[i + 1])?;
17869 out.push((hi << 4) | lo);
17870 }
17871 return Ok(out);
17872 }
17873 let bytes = s.as_bytes();
17876 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
17877 let mut i = 0;
17878 while i < bytes.len() {
17879 let b = bytes[i];
17880 if b == b'\\' && i + 1 < bytes.len() {
17881 let n = bytes[i + 1];
17882 if n == b'\\' {
17883 out.push(b'\\');
17884 i += 2;
17885 continue;
17886 }
17887 if n.is_ascii_digit()
17888 && i + 3 < bytes.len()
17889 && bytes[i + 2].is_ascii_digit()
17890 && bytes[i + 3].is_ascii_digit()
17891 {
17892 let oct = |x: u8| (x - b'0') as u32;
17893 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
17894 if v <= 0xFF {
17895 out.push(v as u8);
17896 i += 4;
17897 continue;
17898 }
17899 }
17900 }
17901 out.push(b);
17902 i += 1;
17903 }
17904 Ok(out)
17905}
17906
17907fn hex_nibble(b: u8) -> Result<u8, &'static str> {
17908 match b {
17909 b'0'..=b'9' => Ok(b - b'0'),
17910 b'a'..=b'f' => Ok(b - b'a' + 10),
17911 b'A'..=b'F' => Ok(b - b'A' + 10),
17912 _ => Err("invalid hex digit"),
17913 }
17914}
17915
17916fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
17930 let mut has_text = false;
17931 let mut has_bigint = false;
17932 let mut has_int = false;
17933 for v in &items {
17934 match v {
17935 Value::Null => {}
17936 Value::Text(_) | Value::Json(_) => has_text = true,
17937 Value::BigInt(_) => has_bigint = true,
17938 Value::Int(_) | Value::SmallInt(_) => has_int = true,
17939 _ => has_text = true,
17940 }
17941 }
17942 if has_text || (!has_bigint && !has_int) {
17943 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
17944 .into_iter()
17945 .map(|v| match v {
17946 Value::Null => None,
17947 Value::Text(s) | Value::Json(s) => Some(s),
17948 other => Some(alloc::format!("{other:?}")),
17949 })
17950 .collect();
17951 return Value::TextArray(out);
17952 }
17953 if has_bigint {
17954 let out: alloc::vec::Vec<Option<i64>> = items
17955 .into_iter()
17956 .map(|v| match v {
17957 Value::Null => None,
17958 Value::Int(n) => Some(i64::from(n)),
17959 Value::SmallInt(n) => Some(i64::from(n)),
17960 Value::BigInt(n) => Some(n),
17961 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
17962 })
17963 .collect();
17964 return Value::BigIntArray(out);
17965 }
17966 let out: alloc::vec::Vec<Option<i32>> = items
17967 .into_iter()
17968 .map(|v| match v {
17969 Value::Null => None,
17970 Value::Int(n) => Some(n),
17971 Value::SmallInt(n) => Some(i32::from(n)),
17972 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
17973 })
17974 .collect();
17975 Value::IntArray(out)
17976}
17977
17978fn decode_text_array_literal(
17979 s: &str,
17980) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
17981 let trimmed = s.trim();
17982 let inner = trimmed
17983 .strip_prefix('{')
17984 .and_then(|x| x.strip_suffix('}'))
17985 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
17986 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
17987 if inner.trim().is_empty() {
17988 return Ok(out);
17989 }
17990 let bytes = inner.as_bytes();
17991 let mut i = 0;
17992 while i <= bytes.len() {
17993 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
17995 i += 1;
17996 }
17997 if i < bytes.len() && bytes[i] == b'"' {
17999 i += 1; let mut buf = alloc::string::String::new();
18001 while i < bytes.len() && bytes[i] != b'"' {
18002 if bytes[i] == b'\\' && i + 1 < bytes.len() {
18003 buf.push(bytes[i + 1] as char);
18004 i += 2;
18005 } else {
18006 buf.push(bytes[i] as char);
18007 i += 1;
18008 }
18009 }
18010 if i >= bytes.len() {
18011 return Err("unterminated quoted element");
18012 }
18013 i += 1; out.push(Some(buf));
18015 } else {
18016 let start = i;
18018 while i < bytes.len() && bytes[i] != b',' {
18019 i += 1;
18020 }
18021 let raw = inner[start..i].trim();
18022 if raw.eq_ignore_ascii_case("NULL") {
18023 out.push(None);
18024 } else {
18025 out.push(Some(alloc::string::ToString::to_string(raw)));
18026 }
18027 }
18028 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
18030 i += 1;
18031 }
18032 if i >= bytes.len() {
18033 break;
18034 }
18035 if bytes[i] != b',' {
18036 return Err("expected ',' between TEXT[] elements");
18037 }
18038 i += 1;
18039 }
18040 Ok(out)
18041}
18042
18043fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
18048 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
18049 out.push('{');
18050 for (i, item) in items.iter().enumerate() {
18051 if i > 0 {
18052 out.push(',');
18053 }
18054 match item {
18055 None => out.push_str("NULL"),
18056 Some(s) => {
18057 let needs_quote = s.is_empty()
18058 || s.eq_ignore_ascii_case("NULL")
18059 || s.chars()
18060 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
18061 if needs_quote {
18062 out.push('"');
18063 for c in s.chars() {
18064 if c == '"' || c == '\\' {
18065 out.push('\\');
18066 }
18067 out.push(c);
18068 }
18069 out.push('"');
18070 } else {
18071 out.push_str(s);
18072 }
18073 }
18074 }
18075 }
18076 out.push('}');
18077 out
18078}
18079
18080fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
18084 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
18085 out.push_str("\\x");
18086 for byte in b {
18087 let hi = byte >> 4;
18088 let lo = byte & 0x0F;
18089 out.push(hex_digit(hi));
18090 out.push(hex_digit(lo));
18091 }
18092 out
18093}
18094
18095const fn hex_digit(n: u8) -> char {
18096 match n {
18097 0..=9 => (b'0' + n) as char,
18098 10..=15 => (b'a' + n - 10) as char,
18099 _ => '?',
18100 }
18101}
18102
18103fn parse_hstore_str(
18115 s: &str,
18116) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
18117 let bytes = s.as_bytes();
18118 let mut i = 0;
18119 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
18120 let skip_ws = |bytes: &[u8], i: &mut usize| {
18121 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
18122 *i += 1;
18123 }
18124 };
18125 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
18126 if *i >= bytes.len() {
18127 return None;
18128 }
18129 if bytes[*i] == b'"' {
18130 *i += 1;
18131 let mut out = alloc::string::String::new();
18132 while *i < bytes.len() {
18133 match bytes[*i] {
18134 b'"' => {
18135 *i += 1;
18136 return Some(out);
18137 }
18138 b'\\' if *i + 1 < bytes.len() => {
18139 out.push(bytes[*i + 1] as char);
18140 *i += 2;
18141 }
18142 c => {
18143 out.push(c as char);
18144 *i += 1;
18145 }
18146 }
18147 }
18148 None
18149 } else {
18150 let start = *i;
18151 while *i < bytes.len()
18152 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
18153 {
18154 *i += 1;
18155 }
18156 if *i == start {
18157 return None;
18158 }
18159 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
18160 }
18161 };
18162 skip_ws(bytes, &mut i);
18163 while i < bytes.len() {
18164 let key = parse_token(bytes, &mut i)?;
18165 skip_ws(bytes, &mut i);
18166 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
18167 return None;
18168 }
18169 i += 2;
18170 skip_ws(bytes, &mut i);
18171 let val_token = if i + 4 <= bytes.len()
18173 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
18174 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
18175 {
18176 i += 4;
18177 None
18178 } else {
18179 Some(parse_token(bytes, &mut i)?)
18180 };
18181 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
18183 out[pos] = (key, val_token);
18184 } else {
18185 out.push((key, val_token));
18186 }
18187 skip_ws(bytes, &mut i);
18188 if i >= bytes.len() {
18189 break;
18190 }
18191 if bytes[i] == b',' {
18192 i += 1;
18193 skip_ws(bytes, &mut i);
18194 continue;
18195 }
18196 return None;
18197 }
18198 Some(out)
18199}
18200
18201fn format_hstore_str(
18205 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
18206) -> alloc::string::String {
18207 let mut out = alloc::string::String::new();
18208 for (i, (k, v)) in pairs.iter().enumerate() {
18209 if i > 0 {
18210 out.push_str(", ");
18211 }
18212 out.push('"');
18213 out.push_str(k);
18214 out.push_str("\"=>");
18215 match v {
18216 None => out.push_str("NULL"),
18217 Some(val) => {
18218 out.push('"');
18219 out.push_str(val);
18220 out.push('"');
18221 }
18222 }
18223 }
18224 out
18225}
18226
18227pub fn format_hstore_text(
18230 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
18231) -> alloc::string::String {
18232 format_hstore_str(pairs)
18233}
18234
18235fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
18240 let s = s.trim();
18241 let outer = s
18242 .strip_prefix('{')
18243 .and_then(|x| x.strip_suffix('}'))
18244 .ok_or("missing outer '{...}' braces")?;
18245 let trimmed = outer.trim();
18246 if trimmed.is_empty() {
18247 return Ok(Vec::new());
18248 }
18249 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
18250 let mut i = 0;
18251 let bytes = trimmed.as_bytes();
18252 while i < bytes.len() {
18253 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
18254 i += 1;
18255 }
18256 if i >= bytes.len() {
18257 break;
18258 }
18259 if bytes[i] != b'{' {
18260 return Err("expected '{' opening a row");
18261 }
18262 i += 1;
18263 let row_start = i;
18264 let mut depth = 1;
18265 while i < bytes.len() && depth > 0 {
18266 match bytes[i] {
18267 b'{' => depth += 1,
18268 b'}' => depth -= 1,
18269 _ => {}
18270 }
18271 if depth > 0 {
18272 i += 1;
18273 }
18274 }
18275 if depth != 0 {
18276 return Err("unbalanced '{...}' in row");
18277 }
18278 let row_text = &trimmed[row_start..i];
18279 i += 1;
18280 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
18281 Vec::new()
18282 } else {
18283 row_text.split(',').map(|t| t.trim().to_string()).collect()
18284 };
18285 rows.push(cells);
18286 }
18287 if let Some(first) = rows.first() {
18288 let cols = first.len();
18289 for r in &rows {
18290 if r.len() != cols {
18291 return Err("ragged 2D array (rows have different column counts)");
18292 }
18293 }
18294 }
18295 Ok(rows)
18296}
18297
18298fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
18299 let raw = split_2d_literal(s)?;
18300 raw.into_iter()
18301 .map(|row| {
18302 row.into_iter()
18303 .map(|cell| {
18304 if cell.eq_ignore_ascii_case("NULL") {
18305 Ok(None)
18306 } else {
18307 cell.parse::<i32>()
18308 .map(Some)
18309 .map_err(|_| "invalid int element")
18310 }
18311 })
18312 .collect()
18313 })
18314 .collect()
18315}
18316
18317fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
18318 let raw = split_2d_literal(s)?;
18319 raw.into_iter()
18320 .map(|row| {
18321 row.into_iter()
18322 .map(|cell| {
18323 if cell.eq_ignore_ascii_case("NULL") {
18324 Ok(None)
18325 } else {
18326 cell.parse::<i64>()
18327 .map(Some)
18328 .map_err(|_| "invalid bigint element")
18329 }
18330 })
18331 .collect()
18332 })
18333 .collect()
18334}
18335
18336fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
18337 let raw = split_2d_literal(s)?;
18338 Ok(raw
18339 .into_iter()
18340 .map(|row| {
18341 row.into_iter()
18342 .map(|cell| {
18343 if cell.eq_ignore_ascii_case("NULL") {
18344 None
18345 } else {
18346 Some(cell.trim_matches('"').to_string())
18347 }
18348 })
18349 .collect()
18350 })
18351 .collect())
18352}
18353
18354fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
18355 let mut out = alloc::string::String::from("{");
18356 for (i, row) in rows.iter().enumerate() {
18357 if i > 0 {
18358 out.push(',');
18359 }
18360 out.push('{');
18361 for (j, cell) in row.iter().enumerate() {
18362 if j > 0 {
18363 out.push(',');
18364 }
18365 match cell {
18366 None => out.push_str("NULL"),
18367 Some(n) => out.push_str(&alloc::format!("{n}")),
18368 }
18369 }
18370 out.push('}');
18371 }
18372 out.push('}');
18373 out
18374}
18375
18376fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
18377 let mut out = alloc::string::String::from("{");
18378 for (i, row) in rows.iter().enumerate() {
18379 if i > 0 {
18380 out.push(',');
18381 }
18382 out.push('{');
18383 for (j, cell) in row.iter().enumerate() {
18384 if j > 0 {
18385 out.push(',');
18386 }
18387 match cell {
18388 None => out.push_str("NULL"),
18389 Some(n) => out.push_str(&alloc::format!("{n}")),
18390 }
18391 }
18392 out.push('}');
18393 }
18394 out.push('}');
18395 out
18396}
18397
18398fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
18399 let mut out = alloc::string::String::from("{");
18400 for (i, row) in rows.iter().enumerate() {
18401 if i > 0 {
18402 out.push(',');
18403 }
18404 out.push('{');
18405 for (j, cell) in row.iter().enumerate() {
18406 if j > 0 {
18407 out.push(',');
18408 }
18409 match cell {
18410 None => out.push_str("NULL"),
18411 Some(s) => out.push_str(s),
18412 }
18413 }
18414 out.push('}');
18415 }
18416 out.push('}');
18417 out
18418}
18419
18420pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
18423 format_int_2d_text(rows)
18424}
18425pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
18426 format_bigint_2d_text(rows)
18427}
18428pub fn format_text_2d_text_pub(
18429 rows: &[Vec<Option<alloc::string::String>>],
18430) -> alloc::string::String {
18431 format_text_2d_text(rows)
18432}
18433
18434fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
18439 let s = s.trim();
18440 if s.eq_ignore_ascii_case("empty") {
18441 return Some(Value::Range {
18442 kind,
18443 lower: None,
18444 upper: None,
18445 lower_inc: false,
18446 upper_inc: false,
18447 empty: true,
18448 });
18449 }
18450 let bytes = s.as_bytes();
18451 if bytes.len() < 3 {
18452 return None;
18453 }
18454 let lower_inc = match bytes[0] {
18455 b'[' => true,
18456 b'(' => false,
18457 _ => return None,
18458 };
18459 let upper_inc = match bytes[bytes.len() - 1] {
18460 b']' => true,
18461 b')' => false,
18462 _ => return None,
18463 };
18464 let inner = &s[1..s.len() - 1];
18465 let (lo_text, up_text) = inner.split_once(',')?;
18466 let lower = if lo_text.is_empty() {
18467 None
18468 } else {
18469 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
18470 };
18471 let upper = if up_text.is_empty() {
18472 None
18473 } else {
18474 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
18475 };
18476 Some(Value::Range {
18477 kind,
18478 lower,
18479 upper,
18480 lower_inc,
18481 upper_inc,
18482 empty: false,
18483 })
18484}
18485
18486fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
18489 let text = text.trim().trim_matches('"');
18490 use spg_storage::RangeKind as K;
18491 match kind {
18492 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
18493 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
18494 K::Num => {
18495 let dot = text.find('.');
18498 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
18499 let digits: alloc::string::String = text
18500 .chars()
18501 .filter(|c| *c == '-' || c.is_ascii_digit())
18502 .collect();
18503 let scaled: i128 = digits.parse().ok()?;
18504 Some(Value::Numeric { scaled, scale })
18505 }
18506 K::Ts | K::TsTz => {
18507 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
18512 }
18513 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
18514 }
18515}
18516
18517pub fn format_range_text(v: &Value) -> alloc::string::String {
18521 format_range_str(v)
18522}
18523
18524fn format_range_str(v: &Value) -> alloc::string::String {
18525 let Value::Range {
18526 lower,
18527 upper,
18528 lower_inc,
18529 upper_inc,
18530 empty,
18531 ..
18532 } = v
18533 else {
18534 return alloc::string::String::new();
18535 };
18536 if *empty {
18537 return "empty".into();
18538 }
18539 let mut out = alloc::string::String::new();
18540 out.push(if *lower_inc { '[' } else { '(' });
18541 if let Some(l) = lower {
18542 out.push_str(&format_range_element(l));
18543 }
18544 out.push(',');
18545 if let Some(u) = upper {
18546 out.push_str(&format_range_element(u));
18547 }
18548 out.push(if *upper_inc { ']' } else { ')' });
18549 out
18550}
18551
18552fn format_range_element(v: &Value) -> alloc::string::String {
18553 match v {
18554 Value::Int(n) => alloc::format!("{n}"),
18555 Value::BigInt(n) => alloc::format!("{n}"),
18556 Value::Date(d) => crate::eval::format_date(*d),
18557 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
18558 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
18559 other => alloc::format!("{other:?}"),
18560 }
18561}
18562
18563fn parse_money_str(s: &str) -> Option<i64> {
18574 let s = s.trim();
18575 let (neg, rest) = match s.strip_prefix('-') {
18576 Some(r) => (true, r.trim_start()),
18577 None => (false, s),
18578 };
18579 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
18580 let (int_part, frac_part) = match rest.split_once('.') {
18581 Some((i, f)) => (i, Some(f)),
18582 None => (rest, None),
18583 };
18584 if int_part.is_empty() {
18585 return None;
18586 }
18587 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
18589 for b in int_part.bytes() {
18590 match b {
18591 b',' => {}
18592 b'0'..=b'9' => int_digits.push(b as char),
18593 _ => return None,
18594 }
18595 }
18596 if int_digits.is_empty() {
18597 return None;
18598 }
18599 let dollars: i64 = int_digits.parse().ok()?;
18600 let cents: i64 = match frac_part {
18601 None => 0,
18602 Some(f) => {
18603 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
18604 return None;
18605 }
18606 let padded = if f.len() == 1 {
18607 alloc::format!("{f}0")
18608 } else {
18609 f.to_string()
18610 };
18611 padded.parse().ok()?
18612 }
18613 };
18614 let total = dollars.checked_mul(100)?.checked_add(cents)?;
18615 Some(if neg { -total } else { total })
18616}
18617
18618fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
18629 let s = s.trim();
18630 let bytes = s.as_bytes();
18634 let sign_pos = bytes
18635 .iter()
18636 .enumerate()
18637 .rev()
18638 .find(|&(_, &b)| b == b'+' || b == b'-')
18639 .map(|(i, _)| i)?;
18640 if sign_pos == 0 {
18641 return None; }
18643 let time_part = &s[..sign_pos];
18644 let offset_part = &s[sign_pos..];
18645 let us = parse_time_str(time_part)?;
18646 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
18647 let offset_body = &offset_part[1..];
18648 let (hh_str, mm_str) = match offset_body.split_once(':') {
18649 Some((h, m)) => (h, m),
18650 None => (offset_body, "0"),
18651 };
18652 let hh: i32 = hh_str.parse().ok()?;
18653 let mm: i32 = mm_str.parse().ok()?;
18654 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
18655 return None;
18656 }
18657 let total = sign * (hh * 3600 + mm * 60);
18658 if total.abs() > 50_400 {
18659 return None;
18660 }
18661 Some((us, total))
18662}
18663
18664fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
18669 if n == 0 || (1901..=2155).contains(&n) {
18670 return Ok(Value::Year(n as u16));
18673 }
18674 Err(EngineError::Eval(EvalError::TypeMismatch {
18675 detail: alloc::format!(
18676 "year value out of range: {n} (column `{col_name}`; \
18677 MySQL accepts 0 or 1901..=2155)"
18678 ),
18679 }))
18680}
18681
18682fn parse_time_str(s: &str) -> Option<i64> {
18694 let s = s.trim();
18695 let (hms, frac) = match s.split_once('.') {
18696 Some((h, f)) => (h, Some(f)),
18697 None => (s, None),
18698 };
18699 let mut parts = hms.split(':');
18700 let hh: u32 = parts.next()?.parse().ok()?;
18701 let mm: u32 = parts.next()?.parse().ok()?;
18702 let ss: u32 = parts.next()?.parse().ok()?;
18703 if parts.next().is_some() {
18704 return None;
18705 }
18706 if hh > 23 || mm > 59 || ss > 59 {
18707 return None;
18708 }
18709 let frac_us: i64 = match frac {
18710 None => 0,
18711 Some(f) => {
18712 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
18713 return None;
18714 }
18715 let mut padded = alloc::string::String::with_capacity(6);
18717 padded.push_str(f);
18718 while padded.len() < 6 {
18719 padded.push('0');
18720 }
18721 padded.parse().ok()?
18722 }
18723 };
18724 Some(
18725 i64::from(hh) * 3_600_000_000
18726 + i64::from(mm) * 60_000_000
18727 + i64::from(ss) * 1_000_000
18728 + frac_us,
18729 )
18730}
18731
18732const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
18733 match t {
18734 ColumnTypeName::SmallInt => DataType::SmallInt,
18735 ColumnTypeName::Int => DataType::Int,
18736 ColumnTypeName::BigInt => DataType::BigInt,
18737 ColumnTypeName::Float => DataType::Float,
18738 ColumnTypeName::Text => DataType::Text,
18739 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
18740 ColumnTypeName::Char(n) => DataType::Char(n),
18741 ColumnTypeName::Bool => DataType::Bool,
18742 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
18743 dim,
18744 encoding: match encoding {
18745 SqlVecEncoding::F32 => VecEncoding::F32,
18746 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
18747 SqlVecEncoding::F16 => VecEncoding::F16,
18748 },
18749 },
18750 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
18751 ColumnTypeName::Date => DataType::Date,
18752 ColumnTypeName::Timestamp => DataType::Timestamp,
18753 ColumnTypeName::Timestamptz => DataType::Timestamptz,
18754 ColumnTypeName::Json => DataType::Json,
18755 ColumnTypeName::Jsonb => DataType::Jsonb,
18756 ColumnTypeName::Bytes => DataType::Bytes,
18757 ColumnTypeName::TextArray => DataType::TextArray,
18758 ColumnTypeName::IntArray => DataType::IntArray,
18759 ColumnTypeName::BigIntArray => DataType::BigIntArray,
18760 ColumnTypeName::TsVector => DataType::TsVector,
18761 ColumnTypeName::TsQuery => DataType::TsQuery,
18762 ColumnTypeName::Uuid => DataType::Uuid,
18763 ColumnTypeName::Time => DataType::Time,
18764 ColumnTypeName::Year => DataType::Year,
18765 ColumnTypeName::TimeTz => DataType::TimeTz,
18766 ColumnTypeName::Money => DataType::Money,
18767 ColumnTypeName::Range(k) => DataType::Range(match k {
18768 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
18769 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
18770 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
18771 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
18772 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
18773 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
18774 }),
18775 ColumnTypeName::Hstore => DataType::Hstore,
18776 ColumnTypeName::IntArray2D => DataType::IntArray2D,
18777 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
18778 ColumnTypeName::TextArray2D => DataType::TextArray2D,
18779 }
18780}
18781
18782fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
18786 match expr {
18787 Expr::Literal(l) => Ok(literal_to_value(l)),
18788 Expr::Cast { expr, target } => {
18789 let inner_value = literal_expr_to_value(*expr)?;
18790 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
18791 }
18792 Expr::Unary {
18793 op: UnOp::Neg,
18794 expr,
18795 } => match *expr {
18796 Expr::Literal(Literal::Integer(n)) => {
18797 let neg = n.checked_neg().ok_or_else(|| {
18800 EngineError::Unsupported("integer literal overflow on negation".into())
18801 })?;
18802 Ok(int_value_for(neg))
18803 }
18804 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
18805 other => Err(EngineError::Unsupported(alloc::format!(
18806 "unary minus over non-literal expression: {other:?}"
18807 ))),
18808 },
18809 Expr::Array(items) => {
18817 let mut materialised: alloc::vec::Vec<Value> =
18818 alloc::vec::Vec::with_capacity(items.len());
18819 for elem in items {
18820 materialised.push(literal_expr_to_value(elem)?);
18821 }
18822 Ok(array_literal_widen(materialised))
18823 }
18824 other => {
18837 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
18838 let ctx = EvalContext::new(&empty_schema, None);
18839 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
18840 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
18841 }
18842 }
18843}
18844
18845fn literal_to_value(l: Literal) -> Value {
18846 match l {
18847 Literal::Integer(n) => int_value_for(n),
18848 Literal::Float(x) => Value::Float(x),
18849 Literal::String(s) => Value::Text(s),
18850 Literal::Bool(b) => Value::Bool(b),
18851 Literal::Null => Value::Null,
18852 Literal::Vector(v) => Value::Vector(v),
18853 Literal::TextArray(items) => Value::TextArray(items),
18854 Literal::IntArray(items) => Value::IntArray(items),
18855 Literal::BigIntArray(items) => Value::BigIntArray(items),
18856 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
18857 }
18858}
18859
18860fn int_value_for(n: i64) -> Value {
18864 if let Ok(small) = i32::try_from(n) {
18865 Value::Int(small)
18866 } else {
18867 Value::BigInt(n)
18868 }
18869}
18870
18871#[allow(clippy::too_many_lines)]
18877fn check_unsigned_range(
18882 v: &Value,
18883 schema: &ColumnSchema,
18884 position: usize,
18885) -> Result<(), EngineError> {
18886 if !schema.is_unsigned {
18887 return Ok(());
18888 }
18889 let n = match v {
18890 Value::SmallInt(x) => i64::from(*x),
18891 Value::Int(x) => i64::from(*x),
18892 Value::BigInt(x) => *x,
18893 _ => return Ok(()), };
18895 if n < 0 {
18896 return Err(EngineError::Unsupported(alloc::format!(
18897 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
18898 schema.name
18899 )));
18900 }
18901 Ok(())
18902}
18903
18904fn coerce_value(
18905 v: Value,
18906 expected: DataType,
18907 col_name: &str,
18908 position: usize,
18909) -> Result<Value, EngineError> {
18910 if v.is_null() {
18911 return Ok(Value::Null);
18912 }
18913 let actual = v.data_type().expect("non-null");
18914 if actual == expected {
18915 return Ok(v);
18916 }
18917 let coerced = match (v, expected) {
18918 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
18919 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
18920 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
18921 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18922 i128::from(n),
18923 precision,
18924 scale,
18925 col_name,
18926 )?),
18927 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
18928 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
18929 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
18930 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18931 i128::from(n),
18932 precision,
18933 scale,
18934 col_name,
18935 )?),
18936 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
18937 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
18938 #[allow(clippy::cast_precision_loss)]
18939 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
18940 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18941 i128::from(n),
18942 precision,
18943 scale,
18944 col_name,
18945 )?),
18946 (Value::Float(x), DataType::Numeric { precision, scale }) => {
18947 Some(numeric_from_float(x, precision, scale, col_name)?)
18948 }
18949 (Value::Text(s), DataType::Numeric { precision, scale }) => {
18960 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
18961 return Err(EngineError::Eval(EvalError::TypeMismatch {
18962 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
18963 }));
18964 };
18965 Some(numeric_rescale(
18966 mantissa, src_scale, precision, scale, col_name,
18967 )?)
18968 }
18969 (Value::Text(s), DataType::Date) => {
18971 let d = eval::parse_date_literal(&s).ok_or_else(|| {
18972 EngineError::Eval(EvalError::TypeMismatch {
18973 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
18974 })
18975 })?;
18976 Some(Value::Date(d))
18977 }
18978 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
18985 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
18986 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
18987 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
18988 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
18989 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
18990 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
18991 _ => None,
18992 },
18993 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19002 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19003 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19004 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
19008 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
19009 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
19017 (Value::Text(s), DataType::Bytes) => {
19024 let bytes = decode_bytea_literal(&s).map_err(|e| {
19025 EngineError::Eval(EvalError::TypeMismatch {
19026 detail: alloc::format!(
19027 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
19028 ),
19029 })
19030 })?;
19031 Some(Value::Bytes(bytes))
19032 }
19033 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
19037 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
19045 Some(b) => Some(Value::Uuid(b)),
19046 None => {
19047 return Err(EngineError::Eval(EvalError::TypeMismatch {
19048 detail: alloc::format!(
19049 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
19050 ),
19051 }));
19052 }
19053 },
19054 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
19059 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
19065 Some(us) => Some(Value::Time(us)),
19066 None => {
19067 return Err(EngineError::Eval(EvalError::TypeMismatch {
19068 detail: alloc::format!(
19069 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
19070 ),
19071 }));
19072 }
19073 },
19074 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
19076 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
19081 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
19082 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
19083 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
19087 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
19088 Err(_) => {
19089 return Err(EngineError::Eval(EvalError::TypeMismatch {
19090 detail: alloc::format!(
19091 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
19092 ),
19093 }));
19094 }
19095 },
19096 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
19098 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
19102 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
19103 None => {
19104 return Err(EngineError::Eval(EvalError::TypeMismatch {
19105 detail: alloc::format!(
19106 "invalid input syntax for type time with time zone: \
19107 {s:?} (column `{col_name}`)"
19108 ),
19109 }));
19110 }
19111 },
19112 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
19114 Some(Value::Text(eval::format_timetz(us, offset_secs)))
19115 }
19116 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
19120 Some(c) => Some(Value::Money(c)),
19121 None => {
19122 return Err(EngineError::Eval(EvalError::TypeMismatch {
19123 detail: alloc::format!(
19124 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
19125 ),
19126 }));
19127 }
19128 },
19129 (Value::SmallInt(n), DataType::Money) => {
19133 Some(Value::Money(i64::from(n).saturating_mul(100)))
19134 }
19135 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
19136 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
19137 (Value::Float(x), DataType::Money) => {
19138 let scaled = x * 100.0;
19141 let cents = if scaled >= 0.0 {
19142 (scaled + 0.5) as i64
19143 } else {
19144 (scaled - 0.5) as i64
19145 };
19146 Some(Value::Money(cents))
19147 }
19148 (Value::Numeric { scaled, scale }, DataType::Money) => {
19149 let cents = if scale == 2 {
19152 scaled
19153 } else if scale < 2 {
19154 let mult = 10_i128.pow(u32::from(2 - scale));
19155 scaled.saturating_mul(mult)
19156 } else {
19157 let div = 10_i128.pow(u32::from(scale - 2));
19158 let half = div / 2;
19159 let bias = if scaled >= 0 { half } else { -half };
19160 (scaled + bias) / div
19161 };
19162 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
19163 }
19164 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
19166 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
19170 Some(v) => Some(v),
19171 None => {
19172 return Err(EngineError::Eval(EvalError::TypeMismatch {
19173 detail: alloc::format!(
19174 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
19175 ),
19176 }));
19177 }
19178 },
19179 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
19181 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
19183 Some(pairs) => Some(Value::Hstore(pairs)),
19184 None => {
19185 return Err(EngineError::Eval(EvalError::TypeMismatch {
19186 detail: alloc::format!(
19187 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
19188 ),
19189 }));
19190 }
19191 },
19192 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
19194 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
19197 Ok(m) => Some(Value::IntArray2D(m)),
19198 Err(e) => {
19199 return Err(EngineError::Eval(EvalError::TypeMismatch {
19200 detail: alloc::format!(
19201 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
19202 ),
19203 }));
19204 }
19205 },
19206 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
19207 Ok(m) => Some(Value::BigIntArray2D(m)),
19208 Err(e) => {
19209 return Err(EngineError::Eval(EvalError::TypeMismatch {
19210 detail: alloc::format!(
19211 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
19212 ),
19213 }));
19214 }
19215 },
19216 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
19217 Ok(m) => Some(Value::TextArray2D(m)),
19218 Err(e) => {
19219 return Err(EngineError::Eval(EvalError::TypeMismatch {
19220 detail: alloc::format!(
19221 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
19222 ),
19223 }));
19224 }
19225 },
19226 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
19228 (Value::BigIntArray2D(rows), DataType::Text) => {
19229 Some(Value::Text(format_bigint_2d_text(&rows)))
19230 }
19231 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
19232 (Value::Text(s), DataType::TextArray) => {
19237 let arr = decode_text_array_literal(&s).map_err(|e| {
19238 EngineError::Eval(EvalError::TypeMismatch {
19239 detail: alloc::format!(
19240 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
19241 ),
19242 })
19243 })?;
19244 Some(Value::TextArray(arr))
19245 }
19246 (Value::Text(s), DataType::IntArray) => {
19252 let arr = decode_text_array_literal(&s).map_err(|e| {
19253 EngineError::Eval(EvalError::TypeMismatch {
19254 detail: alloc::format!(
19255 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
19256 ),
19257 })
19258 })?;
19259 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
19260 for elem in arr {
19261 match elem {
19262 None => out.push(None),
19263 Some(t) => {
19264 let n: i32 = t.parse().map_err(|_| {
19265 EngineError::Eval(EvalError::TypeMismatch {
19266 detail: alloc::format!(
19267 "cannot parse {t:?} as INT element for `{col_name}`"
19268 ),
19269 })
19270 })?;
19271 out.push(Some(n));
19272 }
19273 }
19274 }
19275 Some(Value::IntArray(out))
19276 }
19277 (Value::Text(s), DataType::BigIntArray) => {
19278 let arr = decode_text_array_literal(&s).map_err(|e| {
19279 EngineError::Eval(EvalError::TypeMismatch {
19280 detail: alloc::format!(
19281 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
19282 ),
19283 })
19284 })?;
19285 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
19286 for elem in arr {
19287 match elem {
19288 None => out.push(None),
19289 Some(t) => {
19290 let n: i64 = t.parse().map_err(|_| {
19291 EngineError::Eval(EvalError::TypeMismatch {
19292 detail: alloc::format!(
19293 "cannot parse {t:?} as BIGINT element for `{col_name}`"
19294 ),
19295 })
19296 })?;
19297 out.push(Some(n));
19298 }
19299 }
19300 }
19301 Some(Value::BigIntArray(out))
19302 }
19303 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
19307 (Value::Text(s), DataType::Vector { dim, encoding }) => {
19316 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
19317 EngineError::Eval(EvalError::TypeMismatch {
19318 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
19319 })
19320 })?;
19321 if parsed.len() != dim as usize {
19322 return Err(EngineError::Eval(EvalError::TypeMismatch {
19323 detail: alloc::format!(
19324 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
19325 parsed.len()
19326 ),
19327 }));
19328 }
19329 Some(match encoding {
19330 VecEncoding::F32 => Value::Vector(parsed),
19331 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
19332 VecEncoding::F16 => {
19333 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
19334 }
19335 })
19336 }
19337 (Value::Text(s), DataType::TsVector) => {
19347 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
19348 EngineError::Eval(EvalError::TypeMismatch {
19349 detail: alloc::format!(
19350 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
19351 ),
19352 })
19353 })?;
19354 Some(Value::TsVector(lexs))
19355 }
19356 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
19357 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
19358 EngineError::Eval(EvalError::TypeMismatch {
19359 detail: alloc::format!(
19360 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
19361 ),
19362 })
19363 })?;
19364 Some(Value::Timestamp(t))
19365 }
19366 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
19369 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
19370 }
19371 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
19375 (Value::Timestamp(t), DataType::Date) => {
19376 let days = t.div_euclid(86_400_000_000);
19377 i32::try_from(days).ok().map(Value::Date)
19378 }
19379 (
19380 Value::Numeric {
19381 scaled,
19382 scale: src_scale,
19383 },
19384 DataType::Numeric { precision, scale },
19385 ) => Some(numeric_rescale(
19386 scaled, src_scale, precision, scale, col_name,
19387 )?),
19388 #[allow(clippy::cast_precision_loss)]
19389 (Value::Numeric { scaled, scale }, DataType::Float) => {
19390 let mut div = 1.0_f64;
19391 for _ in 0..scale {
19392 div *= 10.0;
19393 }
19394 Some(Value::Float((scaled as f64) / div))
19395 }
19396 (Value::Numeric { scaled, scale }, DataType::Int) => {
19397 let truncated = numeric_truncate_to_integer(scaled, scale);
19398 i32::try_from(truncated).ok().map(Value::Int)
19399 }
19400 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
19401 let truncated = numeric_truncate_to_integer(scaled, scale);
19402 i64::try_from(truncated).ok().map(Value::BigInt)
19403 }
19404 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
19405 let truncated = numeric_truncate_to_integer(scaled, scale);
19406 i16::try_from(truncated).ok().map(Value::SmallInt)
19407 }
19408 (Value::Text(s), DataType::Varchar(max)) => {
19410 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
19411 Some(Value::Text(s))
19412 } else {
19413 return Err(EngineError::Unsupported(alloc::format!(
19414 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
19415 {} chars",
19416 s.chars().count()
19417 )));
19418 }
19419 }
19420 (
19428 Value::Vector(v),
19429 DataType::Vector {
19430 dim,
19431 encoding: VecEncoding::Sq8,
19432 },
19433 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
19434 (
19439 Value::Vector(v),
19440 DataType::Vector {
19441 dim,
19442 encoding: VecEncoding::F16,
19443 },
19444 ) if v.len() == dim as usize => Some(Value::HalfVector(
19445 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
19446 )),
19447 (Value::Text(s), DataType::Char(size)) => {
19451 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
19452 if len > size {
19453 return Err(EngineError::Unsupported(alloc::format!(
19454 "value for CHAR({size}) column `{col_name}` exceeds length: \
19455 {len} chars"
19456 )));
19457 }
19458 let need = (size - len) as usize;
19459 let mut padded = s;
19460 padded.reserve(need);
19461 for _ in 0..need {
19462 padded.push(' ');
19463 }
19464 Some(Value::Text(padded))
19465 }
19466 _ => None,
19467 };
19468 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
19469 column: col_name.into(),
19470 expected,
19471 actual,
19472 position,
19473 }))
19474}
19475
19476fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
19482 use core::fmt::Write;
19483 let mut out = alloc::string::String::from("(");
19484 for (i, a) in args.iter().enumerate() {
19485 if i > 0 {
19486 out.push_str(", ");
19487 }
19488 match a.mode {
19489 spg_sql::ast::FunctionArgMode::In => {}
19490 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
19491 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
19492 }
19493 if let Some(n) = &a.name {
19494 out.push_str(n);
19495 out.push(' ');
19496 }
19497 match &a.ty {
19498 spg_sql::ast::FunctionArgType::Typed(t) => {
19499 let _ = write!(out, "{t}");
19500 }
19501 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
19502 }
19503 }
19504 out.push(')');
19505 out
19506}
19507
19508fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
19517 match expr {
19518 spg_sql::ast::Expr::FunctionCall { name, args } => {
19519 name.eq_ignore_ascii_case("unnest") && args.len() == 1
19520 }
19521 _ => false,
19522 }
19523}
19524
19525fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
19529 match expr {
19530 spg_sql::ast::Expr::FunctionCall { name, args }
19531 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
19532 {
19533 Some(&args[0])
19534 }
19535 _ => None,
19536 }
19537}
19538
19539fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
19544 match v {
19545 Value::Null => Ok(Vec::new()),
19546 Value::TextArray(items) => Ok(items
19547 .iter()
19548 .map(|opt| {
19549 opt.as_ref()
19550 .map(|s| Value::Text(s.clone()))
19551 .unwrap_or(Value::Null)
19552 })
19553 .collect()),
19554 Value::IntArray(items) => Ok(items
19555 .iter()
19556 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
19557 .collect()),
19558 Value::BigIntArray(items) => Ok(items
19559 .iter()
19560 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
19561 .collect()),
19562 other => Err(EngineError::Eval(EvalError::TypeMismatch {
19563 detail: alloc::format!(
19564 "unnest() expects an array argument, got {:?}",
19565 other.data_type()
19566 ),
19567 })),
19568 }
19569}
19570
19571#[cfg(test)]
19572mod tests {
19573 use super::*;
19574 use alloc::vec;
19575
19576 fn unwrap_command_ok(r: &QueryResult) -> usize {
19577 match r {
19578 QueryResult::CommandOk { affected, .. } => *affected,
19579 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
19580 }
19581 }
19582
19583 #[test]
19584 fn update_seek_positions_engages_on_indexed_eq() {
19585 let mut e = Engine::new();
19586 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
19587 .unwrap();
19588 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
19589 for i in 0..100 {
19590 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
19591 .unwrap();
19592 }
19593 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
19594 .expect("parse");
19595 let Statement::Update(u) = stmt else {
19596 panic!("expected Update, got {stmt:?}");
19597 };
19598 let w = u.where_.as_ref().expect("where");
19599 let table = e.catalog().get("b").unwrap();
19600 let schema_cols = table.schema().columns.clone();
19601 let Expr::Binary { lhs, op, rhs } = w else {
19603 panic!("WHERE not Binary: {w:?}");
19604 };
19605 assert_eq!(*op, BinOp::Eq, "op not Eq");
19606 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
19607 assert!(
19608 pair.is_some(),
19609 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
19610 );
19611 let (col_pos, value) = pair.unwrap();
19612 assert!(
19613 table.index_on(col_pos).is_some(),
19614 "no index on col {col_pos}"
19615 );
19616 assert!(
19617 IndexKey::from_value(&value).is_some(),
19618 "IndexKey::from_value None for {value:?}"
19619 );
19620 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
19621 assert_eq!(positions, Some(vec![42]), "seek did not engage");
19622 }
19623
19624 #[test]
19625 fn create_table_registers_schema() {
19626 let mut e = Engine::new();
19627 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
19628 .unwrap();
19629 assert_eq!(e.catalog().table_count(), 1);
19630 let t = e.catalog().get("foo").unwrap();
19631 assert_eq!(t.schema().columns.len(), 2);
19632 assert_eq!(t.schema().columns[0].ty, DataType::Int);
19633 assert!(!t.schema().columns[0].nullable);
19634 assert_eq!(t.schema().columns[1].ty, DataType::Text);
19635 }
19636
19637 #[test]
19638 fn create_table_vector_default_is_f32_encoded() {
19639 let mut e = Engine::new();
19640 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
19641 let t = e.catalog().get("t").unwrap();
19642 assert_eq!(
19643 t.schema().columns[0].ty,
19644 DataType::Vector {
19645 dim: 8,
19646 encoding: VecEncoding::F32,
19647 },
19648 );
19649 }
19650
19651 #[test]
19652 fn create_table_vector_using_sq8_succeeds() {
19653 let mut e = Engine::new();
19657 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
19658 let t = e.catalog().get("t").unwrap();
19659 assert_eq!(
19660 t.schema().columns[0].ty,
19661 DataType::Vector {
19662 dim: 8,
19663 encoding: VecEncoding::Sq8,
19664 },
19665 );
19666 }
19667
19668 #[test]
19669 fn insert_into_sq8_column_quantises_f32_payload() {
19670 let mut e = Engine::new();
19677 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
19678 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19679 .unwrap();
19680 let t = e.catalog().get("t").unwrap();
19681 assert_eq!(t.rows().len(), 1);
19682 match &t.rows()[0].values[0] {
19683 Value::Sq8Vector(q) => {
19684 assert_eq!(q.bytes.len(), 4);
19685 assert!((q.min - 0.0).abs() < 1e-6);
19687 assert!((q.max - 1.0).abs() < 1e-6);
19688 }
19689 other => panic!("expected Sq8Vector cell, got {other:?}"),
19690 }
19691 }
19692
19693 #[test]
19694 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
19695 let mut e = Engine::new();
19702 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
19703 .unwrap();
19704 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19705 .unwrap();
19706 let t = e.catalog().get("t").unwrap();
19707 assert_eq!(t.rows().len(), 1);
19708 match &t.rows()[0].values[0] {
19709 Value::HalfVector(h) => {
19710 assert_eq!(h.dim(), 4);
19711 let back = h.to_f32_vec();
19712 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
19713 for (g, e) in back.iter().zip(expected.iter()) {
19714 assert!(
19715 (g - e).abs() < 1e-6,
19716 "{g} vs {e} should be exact on f16 grid"
19717 );
19718 }
19719 }
19720 other => panic!("expected HalfVector cell, got {other:?}"),
19721 }
19722 }
19723
19724 #[test]
19725 fn alter_index_rebuild_in_place_succeeds() {
19726 let mut e = Engine::new();
19731 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19732 .unwrap();
19733 for i in 0..8_i32 {
19734 #[allow(clippy::cast_precision_loss)]
19735 let base = (i as f32) * 0.1;
19736 e.execute(&alloc::format!(
19737 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
19738 b1 = base + 0.01,
19739 b2 = base + 0.02,
19740 ))
19741 .unwrap();
19742 }
19743 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19744 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
19745 assert_eq!(
19747 e.catalog().get("t").unwrap().schema().columns[1].ty,
19748 DataType::Vector {
19749 dim: 3,
19750 encoding: VecEncoding::F32,
19751 },
19752 );
19753 }
19754
19755 #[test]
19756 fn alter_index_rebuild_with_encoding_switches_cell_type() {
19757 let mut e = Engine::new();
19762 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
19763 .unwrap();
19764 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
19765 .unwrap();
19766 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19767 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
19768 .unwrap();
19769 let t = e.catalog().get("t").unwrap();
19770 assert_eq!(
19771 t.schema().columns[1].ty,
19772 DataType::Vector {
19773 dim: 4,
19774 encoding: VecEncoding::Sq8,
19775 },
19776 );
19777 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
19778 }
19779
19780 #[test]
19781 fn alter_index_rebuild_unknown_index_errors() {
19782 let mut e = Engine::new();
19783 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
19784 assert!(
19785 matches!(
19786 &err,
19787 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
19788 ),
19789 "got: {err}"
19790 );
19791 }
19792
19793 #[test]
19794 fn alter_index_rebuild_on_btree_index_errors() {
19795 let mut e = Engine::new();
19798 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19799 e.execute("INSERT INTO t VALUES (1)").unwrap();
19800 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
19801 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
19802 assert!(
19803 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
19804 "got: {err}"
19805 );
19806 }
19807
19808 #[test]
19809 fn prepared_insert_substitutes_placeholders() {
19810 let mut e = Engine::new();
19816 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
19817 .unwrap();
19818 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
19819 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
19820 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
19821 .unwrap();
19822 }
19823 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
19825 let QueryResult::Rows { rows, .. } = rows_result else {
19826 panic!("expected Rows")
19827 };
19828 assert_eq!(rows.len(), 3);
19829 }
19830
19831 #[test]
19832 fn prepared_select_with_placeholder_filters_rows() {
19833 let mut e = Engine::new();
19834 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
19835 .unwrap();
19836 for i in 0..10_i32 {
19837 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
19838 .unwrap();
19839 }
19840 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
19841 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
19842 else {
19843 panic!("expected Rows")
19844 };
19845 assert_eq!(rows.len(), 1);
19847 assert_eq!(rows[0].values[0], Value::Int(5));
19848 }
19849
19850 #[test]
19851 fn prepared_too_few_params_errors() {
19852 let mut e = Engine::new();
19853 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19854 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
19855 let err = e.execute_prepared(stmt, &[]).unwrap_err();
19856 assert!(
19857 matches!(
19858 &err,
19859 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
19860 ),
19861 "got: {err}"
19862 );
19863 }
19864
19865 #[test]
19866 fn bytea_cast_round_trips_text_input() {
19867 let e = Engine::new();
19870 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
19871 let QueryResult::Rows { rows, .. } = r else {
19872 panic!("expected Rows")
19873 };
19874 assert_eq!(rows.len(), 1);
19875 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
19876 }
19877
19878 #[test]
19879 fn bytea_cast_pg_escape_hex_form() {
19880 let e = Engine::new();
19884 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
19885 let QueryResult::Rows { rows, .. } = r else {
19886 panic!("expected Rows")
19887 };
19888 assert_eq!(
19889 rows[0].values[0],
19890 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
19891 );
19892 }
19893
19894 #[test]
19895 fn bytea_cast_chains_through_octet_length() {
19896 let e = Engine::new();
19900 let r = e
19901 .execute_readonly("SELECT octet_length('hello'::bytea)")
19902 .unwrap();
19903 let QueryResult::Rows { rows, .. } = r else {
19904 panic!("expected Rows")
19905 };
19906 match &rows[0].values[0] {
19907 Value::Int(n) => assert_eq!(*n, 5),
19908 Value::BigInt(n) => assert_eq!(*n, 5),
19909 other => panic!("expected integer length, got {other:?}"),
19910 }
19911 }
19912
19913 #[test]
19914 fn readonly_prepared_on_snapshot_select_with_placeholder() {
19915 let mut e = Engine::new();
19921 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
19922 .unwrap();
19923 for i in 0..10_i32 {
19924 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
19925 .unwrap();
19926 }
19927 let snapshot = e.clone_snapshot();
19928 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
19929 let QueryResult::Rows { rows, .. } =
19930 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
19931 .unwrap()
19932 else {
19933 panic!("expected Rows")
19934 };
19935 assert_eq!(rows.len(), 1);
19936 assert_eq!(rows[0].values[0], Value::Int(5));
19937 }
19938
19939 #[test]
19940 fn readonly_prepared_on_snapshot_rejects_writes() {
19941 let mut e = Engine::new();
19945 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19946 let snapshot = e.clone_snapshot();
19947 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
19948 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
19949 .unwrap_err();
19950 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
19951 }
19952
19953 #[test]
19954 fn readonly_prepared_on_snapshot_frozen_view() {
19955 let mut e = Engine::new();
19961 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19962 e.execute("INSERT INTO t VALUES (1)").unwrap();
19963 let snapshot = e.clone_snapshot();
19964 e.execute("INSERT INTO t VALUES (2)").unwrap();
19965 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
19966 let QueryResult::Rows { rows, .. } =
19967 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
19968 .unwrap()
19969 else {
19970 panic!("expected Rows")
19971 };
19972 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
19973 }
19974
19975 #[test]
19976 fn describe_prepared_on_snapshot_resolves_columns() {
19977 let mut e = Engine::new();
19982 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
19983 .unwrap();
19984 let snapshot = e.clone_snapshot();
19985 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
19986 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
19987 assert_eq!(cols.len(), 2);
19988 assert_eq!(cols[0].name, "id");
19989 assert_eq!(cols[0].ty, DataType::Int);
19990 assert_eq!(cols[1].name, "name");
19991 assert_eq!(cols[1].ty, DataType::Text);
19992 }
19993
19994 #[test]
19995 fn insert_into_half_column_dim_mismatch_errors() {
19996 let mut e = Engine::new();
19997 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
19998 .unwrap();
19999 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
20000 assert!(matches!(
20001 &err,
20002 EngineError::Storage(StorageError::TypeMismatch { .. })
20003 ));
20004 }
20005
20006 #[test]
20007 fn insert_into_sq8_column_dim_mismatch_errors() {
20008 let mut e = Engine::new();
20013 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
20014 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
20015 assert!(
20016 matches!(
20017 &err,
20018 EngineError::Storage(StorageError::TypeMismatch { .. })
20019 ),
20020 "got: {err}",
20021 );
20022 }
20023
20024 #[test]
20025 fn create_table_duplicate_errors() {
20026 let mut e = Engine::new();
20027 e.execute("CREATE TABLE foo (a INT)").unwrap();
20028 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
20029 assert!(matches!(
20030 err,
20031 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
20032 ));
20033 }
20034
20035 #[test]
20036 fn insert_into_unknown_table_errors() {
20037 let mut e = Engine::new();
20038 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
20039 assert!(matches!(
20040 err,
20041 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
20042 ));
20043 }
20044
20045 #[test]
20046 fn insert_happy_path_reports_one_affected() {
20047 let mut e = Engine::new();
20048 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20049 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
20050 assert_eq!(unwrap_command_ok(&r), 1);
20051 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
20052 }
20053
20054 #[test]
20055 fn insert_arity_mismatch_propagates() {
20056 let mut e = Engine::new();
20057 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
20058 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
20059 assert!(matches!(
20060 err,
20061 EngineError::Storage(StorageError::ArityMismatch { .. })
20062 ));
20063 }
20064
20065 #[test]
20066 fn insert_negative_integer_via_unary_minus() {
20067 let mut e = Engine::new();
20068 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20069 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
20070 let rows = e.catalog().get("foo").unwrap().rows();
20071 assert_eq!(rows[0].values[0], Value::Int(-7));
20072 }
20073
20074 #[test]
20075 fn insert_expression_evaluated_against_empty_context() {
20076 let mut e = Engine::new();
20081 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20082 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
20083 let rows = e.catalog().get("foo").unwrap().rows();
20084 assert_eq!(rows[0].values[0], Value::Int(3));
20085 }
20086
20087 #[test]
20088 fn select_star_returns_all_rows_in_insertion_order() {
20089 let mut e = Engine::new();
20090 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
20091 .unwrap();
20092 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
20093 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
20094 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
20095
20096 let r = e.execute("SELECT * FROM foo").unwrap();
20097 let QueryResult::Rows { columns, rows } = r else {
20098 panic!("expected Rows")
20099 };
20100 assert_eq!(columns.len(), 2);
20101 assert_eq!(columns[0].name, "a");
20102 assert_eq!(rows.len(), 3);
20103 assert_eq!(
20104 rows[1].values,
20105 vec![Value::Int(2), Value::Text("two".into())]
20106 );
20107 }
20108
20109 #[test]
20110 fn select_star_on_empty_table_returns_zero_rows() {
20111 let mut e = Engine::new();
20112 e.execute("CREATE TABLE foo (a INT)").unwrap();
20113 let r = e.execute("SELECT * FROM foo").unwrap();
20114 match r {
20115 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
20116 QueryResult::CommandOk { .. } => panic!("expected Rows"),
20117 }
20118 }
20119
20120 fn make_three_row_users(e: &mut Engine) {
20123 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
20124 .unwrap();
20125 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
20126 .unwrap();
20127 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
20128 .unwrap();
20129 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
20130 .unwrap();
20131 }
20132
20133 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
20134 match r {
20135 QueryResult::Rows { columns, rows } => (columns, rows),
20136 QueryResult::CommandOk { .. } => panic!("expected Rows"),
20137 }
20138 }
20139
20140 #[test]
20141 fn where_filter_passes_only_true_rows() {
20142 let mut e = Engine::new();
20143 make_three_row_users(&mut e);
20144 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
20145 let (_, rows) = unwrap_rows(r);
20146 assert_eq!(rows.len(), 2);
20147 assert_eq!(rows[0].values[0], Value::Int(2));
20148 assert_eq!(rows[1].values[0], Value::Int(3));
20149 }
20150
20151 #[test]
20152 fn where_with_null_result_filters_out_row() {
20153 let mut e = Engine::new();
20154 make_three_row_users(&mut e);
20155 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
20157 let (_, rows) = unwrap_rows(r);
20158 assert_eq!(rows.len(), 1);
20159 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
20160 }
20161
20162 #[test]
20163 fn projection_named_columns() {
20164 let mut e = Engine::new();
20165 make_three_row_users(&mut e);
20166 let r = e.execute("SELECT name, score FROM users").unwrap();
20167 let (cols, rows) = unwrap_rows(r);
20168 assert_eq!(cols.len(), 2);
20169 assert_eq!(cols[0].name, "name");
20170 assert_eq!(cols[1].name, "score");
20171 assert_eq!(rows.len(), 3);
20172 assert_eq!(
20173 rows[0].values,
20174 vec![Value::Text("alice".into()), Value::Int(90)]
20175 );
20176 }
20177
20178 #[test]
20179 fn projection_with_column_alias() {
20180 let mut e = Engine::new();
20181 make_three_row_users(&mut e);
20182 let r = e
20183 .execute("SELECT name AS who FROM users WHERE id = 1")
20184 .unwrap();
20185 let (cols, rows) = unwrap_rows(r);
20186 assert_eq!(cols[0].name, "who");
20187 assert_eq!(rows.len(), 1);
20188 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
20189 }
20190
20191 #[test]
20192 fn qualified_column_with_table_alias_resolves() {
20193 let mut e = Engine::new();
20194 make_three_row_users(&mut e);
20195 let r = e
20196 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
20197 .unwrap();
20198 let (cols, rows) = unwrap_rows(r);
20199 assert_eq!(cols.len(), 2);
20200 assert_eq!(rows.len(), 2);
20201 }
20202
20203 #[test]
20204 fn qualified_column_with_wrong_alias_errors() {
20205 let mut e = Engine::new();
20206 make_three_row_users(&mut e);
20207 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
20208 assert!(matches!(
20209 err,
20210 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
20211 ));
20212 }
20213
20214 #[test]
20215 fn select_unknown_column_errors_in_projection() {
20216 let mut e = Engine::new();
20217 make_three_row_users(&mut e);
20218 let err = e.execute("SELECT ghost FROM users").unwrap_err();
20219 assert!(matches!(
20220 err,
20221 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
20222 ));
20223 }
20224
20225 #[test]
20226 fn where_unknown_column_errors() {
20227 let mut e = Engine::new();
20228 make_three_row_users(&mut e);
20229 let err = e
20230 .execute("SELECT * FROM users WHERE ghost = 1")
20231 .unwrap_err();
20232 assert!(matches!(
20233 err,
20234 EngineError::Eval(EvalError::ColumnNotFound { .. })
20235 ));
20236 }
20237
20238 #[test]
20239 fn expression_projection_evaluates_and_renders() {
20240 let mut e = Engine::new();
20243 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
20244 e.execute("INSERT INTO t VALUES (3)").unwrap();
20245 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
20246 assert_eq!(rows.len(), 1);
20247 assert_eq!(rows[0].values[0], Value::Int(3));
20250 }
20251
20252 #[test]
20253 fn select_unknown_table_errors() {
20254 let mut e = Engine::new();
20255 let err = e.execute("SELECT * FROM ghost").unwrap_err();
20256 assert!(matches!(
20257 err,
20258 EngineError::Storage(StorageError::TableNotFound { .. })
20259 ));
20260 }
20261
20262 #[test]
20263 fn invalid_sql_returns_parse_error() {
20264 let mut e = Engine::new();
20267 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
20268 assert!(matches!(err, EngineError::Parse(_)));
20269 }
20270
20271 #[test]
20274 fn create_index_registers_on_table() {
20275 let mut e = Engine::new();
20276 make_three_row_users(&mut e);
20277 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
20278 let t = e.catalog().get("users").unwrap();
20279 assert_eq!(t.indices().len(), 1);
20280 assert_eq!(t.indices()[0].name, "by_name");
20281 }
20282
20283 #[test]
20284 fn create_index_on_unknown_table_errors() {
20285 let mut e = Engine::new();
20286 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
20287 assert!(matches!(
20288 err,
20289 EngineError::Storage(StorageError::TableNotFound { .. })
20290 ));
20291 }
20292
20293 #[test]
20294 fn create_index_on_unknown_column_errors() {
20295 let mut e = Engine::new();
20296 make_three_row_users(&mut e);
20297 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
20298 assert!(matches!(
20299 err,
20300 EngineError::Storage(StorageError::ColumnNotFound { .. })
20301 ));
20302 }
20303
20304 #[test]
20305 fn select_eq_uses_index_returns_same_rows_as_scan() {
20306 let mut without = Engine::new();
20310 make_three_row_users(&mut without);
20311 let mut with = Engine::new();
20312 make_three_row_users(&mut with);
20313 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
20314
20315 let q = "SELECT * FROM users WHERE id = 2";
20316 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
20317 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
20318 assert_eq!(no_idx_rows, idx_rows);
20319 assert_eq!(idx_rows.len(), 1);
20320 }
20321
20322 #[test]
20323 fn select_eq_with_no_matching_index_value_returns_empty() {
20324 let mut e = Engine::new();
20325 make_three_row_users(&mut e);
20326 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
20327 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
20328 assert_eq!(rows.len(), 0);
20329 }
20330
20331 #[test]
20334 fn begin_sets_in_transaction_flag() {
20335 let mut e = Engine::new();
20336 assert!(!e.in_transaction());
20337 e.execute("BEGIN").unwrap();
20338 assert!(e.in_transaction());
20339 }
20340
20341 #[test]
20342 fn double_begin_errors() {
20343 let mut e = Engine::new();
20344 e.execute("BEGIN").unwrap();
20345 let err = e.execute("BEGIN").unwrap_err();
20346 assert_eq!(err, EngineError::TransactionAlreadyOpen);
20347 }
20348
20349 #[test]
20350 fn commit_without_begin_errors() {
20351 let mut e = Engine::new();
20352 let err = e.execute("COMMIT").unwrap_err();
20353 assert_eq!(err, EngineError::NoActiveTransaction);
20354 }
20355
20356 #[test]
20357 fn rollback_without_begin_errors() {
20358 let mut e = Engine::new();
20359 let err = e.execute("ROLLBACK").unwrap_err();
20360 assert_eq!(err, EngineError::NoActiveTransaction);
20361 }
20362
20363 #[test]
20364 fn commit_applies_shadow_to_committed_catalog() {
20365 let mut e = Engine::new();
20366 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20367 e.execute("BEGIN").unwrap();
20368 e.execute("INSERT INTO t VALUES (1)").unwrap();
20369 e.execute("INSERT INTO t VALUES (2)").unwrap();
20370 e.execute("COMMIT").unwrap();
20371 assert!(!e.in_transaction());
20372 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
20373 }
20374
20375 #[test]
20376 fn rollback_discards_shadow() {
20377 let mut e = Engine::new();
20378 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20379 e.execute("BEGIN").unwrap();
20380 e.execute("INSERT INTO t VALUES (1)").unwrap();
20381 e.execute("INSERT INTO t VALUES (2)").unwrap();
20382 e.execute("ROLLBACK").unwrap();
20383 assert!(!e.in_transaction());
20384 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
20385 }
20386
20387 #[test]
20388 fn select_during_tx_sees_uncommitted_writes_own_session() {
20389 let mut e = Engine::new();
20392 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20393 e.execute("BEGIN").unwrap();
20394 e.execute("INSERT INTO t VALUES (42)").unwrap();
20395 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
20396 assert_eq!(rows.len(), 1);
20397 assert_eq!(rows[0].values[0], Value::Int(42));
20398 }
20399
20400 #[test]
20401 fn snapshot_with_no_users_is_bare_catalog_format() {
20402 let mut e = Engine::new();
20403 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20404 let bytes = e.snapshot();
20405 assert_eq!(
20406 &bytes[..8],
20407 b"SPGDB001",
20408 "must be the bare v3.x catalog magic"
20409 );
20410 let e2 = Engine::restore_envelope(&bytes).unwrap();
20411 assert!(e2.users().is_empty());
20412 assert_eq!(e2.catalog().table_count(), 1);
20413 }
20414
20415 #[test]
20416 fn snapshot_with_users_round_trips_both_via_envelope() {
20417 let mut e = Engine::new();
20418 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20419 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
20420 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
20421 .unwrap();
20422 let bytes = e.snapshot();
20423 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
20424 let e2 = Engine::restore_envelope(&bytes).unwrap();
20425 assert_eq!(e2.users().len(), 2);
20426 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
20427 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
20428 assert_eq!(e2.verify_user("alice", "wrong"), None);
20429 assert_eq!(e2.catalog().table_count(), 1);
20430 }
20431
20432 #[test]
20433 fn ddl_inside_tx_also_rolled_back() {
20434 let mut e = Engine::new();
20435 e.execute("BEGIN").unwrap();
20436 e.execute("CREATE TABLE t (v INT)").unwrap();
20437 e.execute("SELECT * FROM t").unwrap();
20439 e.execute("ROLLBACK").unwrap();
20440 let err = e.execute("SELECT * FROM t").unwrap_err();
20442 assert!(matches!(
20443 err,
20444 EngineError::Storage(StorageError::TableNotFound { .. })
20445 ));
20446 }
20447
20448 #[test]
20451 fn create_publication_lands_in_catalog() {
20452 let mut e = Engine::new();
20453 assert!(e.publications().is_empty());
20454 e.execute("CREATE PUBLICATION pub_a").unwrap();
20455 assert_eq!(e.publications().len(), 1);
20456 assert!(e.publications().contains("pub_a"));
20457 }
20458
20459 #[test]
20460 fn create_publication_duplicate_errors() {
20461 let mut e = Engine::new();
20462 e.execute("CREATE PUBLICATION pub_a").unwrap();
20463 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
20464 assert!(
20465 alloc::format!("{err:?}").contains("DuplicateName"),
20466 "got {err:?}"
20467 );
20468 }
20469
20470 #[test]
20471 fn drop_publication_silent_when_absent() {
20472 let mut e = Engine::new();
20473 let r = e.execute("DROP PUBLICATION nope").unwrap();
20476 match r {
20477 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20478 other => panic!("expected CommandOk, got {other:?}"),
20479 }
20480 }
20481
20482 #[test]
20483 fn drop_publication_present_reports_one_affected() {
20484 let mut e = Engine::new();
20485 e.execute("CREATE PUBLICATION pub_a").unwrap();
20486 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
20487 match r {
20488 QueryResult::CommandOk {
20489 affected,
20490 modified_catalog,
20491 } => {
20492 assert_eq!(affected, 1);
20493 assert!(modified_catalog);
20494 }
20495 other => panic!("expected CommandOk, got {other:?}"),
20496 }
20497 assert!(e.publications().is_empty());
20498 }
20499
20500 #[test]
20501 fn publications_persist_across_snapshot_restore() {
20502 let mut e = Engine::new();
20507 e.execute("CREATE PUBLICATION pub_a").unwrap();
20508 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
20509 .unwrap();
20510 let snap = e.snapshot();
20511 let e2 = Engine::restore_envelope(&snap).unwrap();
20512 assert_eq!(e2.publications().len(), 2);
20513 assert!(e2.publications().contains("pub_a"));
20514 assert!(e2.publications().contains("pub_b"));
20515 }
20516
20517 #[test]
20518 fn create_publication_allowed_inside_transaction() {
20519 let mut e = Engine::new();
20523 e.execute("BEGIN").unwrap();
20524 e.execute("CREATE PUBLICATION pub_a").unwrap();
20525 e.execute("COMMIT").unwrap();
20526 assert!(e.publications().contains("pub_a"));
20527 }
20528
20529 #[test]
20532 fn create_publication_for_table_list_lands_with_scope() {
20533 let mut e = Engine::new();
20534 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
20535 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
20536 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
20537 .unwrap();
20538 let scope = e.publications().get("pub_a").cloned();
20539 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
20540 panic!("expected ForTables scope, got {scope:?}")
20541 };
20542 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20543 }
20544
20545 #[test]
20546 fn create_publication_all_tables_except_lands_with_scope() {
20547 let mut e = Engine::new();
20548 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
20549 .unwrap();
20550 let scope = e.publications().get("pub_a").cloned();
20551 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
20552 panic!("expected AllTablesExcept scope, got {scope:?}")
20553 };
20554 assert_eq!(ts, alloc::vec!["t3".to_string()]);
20555 }
20556
20557 #[test]
20558 fn show_publications_empty_returns_zero_rows() {
20559 let e = Engine::new();
20560 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
20561 let QueryResult::Rows { rows, columns } = r else {
20562 panic!()
20563 };
20564 assert!(rows.is_empty());
20565 assert_eq!(columns.len(), 3);
20566 assert_eq!(columns[0].name, "name");
20567 assert_eq!(columns[1].name, "scope");
20568 assert_eq!(columns[2].name, "table_count");
20569 }
20570
20571 #[test]
20572 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
20573 let mut e = Engine::new();
20574 e.execute("CREATE PUBLICATION z_pub").unwrap();
20575 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
20576 .unwrap();
20577 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
20578 .unwrap();
20579 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
20580 let QueryResult::Rows { rows, .. } = r else {
20581 panic!()
20582 };
20583 assert_eq!(rows.len(), 3);
20584 let names: Vec<&str> = rows
20586 .iter()
20587 .map(|r| {
20588 if let Value::Text(s) = &r.values[0] {
20589 s.as_str()
20590 } else {
20591 panic!()
20592 }
20593 })
20594 .collect();
20595 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
20596 match &rows[0].values[1] {
20598 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
20599 other => panic!("expected Text, got {other:?}"),
20600 }
20601 assert_eq!(rows[0].values[2], Value::Int(2));
20602 match &rows[1].values[1] {
20604 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
20605 other => panic!("expected Text, got {other:?}"),
20606 }
20607 assert_eq!(rows[1].values[2], Value::Int(1));
20608 match &rows[2].values[1] {
20610 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
20611 other => panic!("expected Text, got {other:?}"),
20612 }
20613 assert_eq!(rows[2].values[2], Value::Null);
20614 }
20615
20616 #[test]
20617 fn for_list_scopes_persist_across_snapshot() {
20618 let mut e = Engine::new();
20621 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
20622 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
20623 .unwrap();
20624 let snap = e.snapshot();
20625 let e2 = Engine::restore_envelope(&snap).unwrap();
20626 assert_eq!(e2.publications().len(), 2);
20627 let p1 = e2.publications().get("p1").cloned();
20628 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
20629 panic!("p1 scope lost: {p1:?}")
20630 };
20631 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20632 let p2 = e2.publications().get("p2").cloned();
20633 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
20634 panic!("p2 scope lost: {p2:?}")
20635 };
20636 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
20637 }
20638
20639 #[test]
20642 fn create_subscription_lands_in_catalog_with_defaults() {
20643 let mut e = Engine::new();
20644 e.execute(
20645 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
20646 )
20647 .unwrap();
20648 let s = e.subscriptions().get("sub_a").cloned().expect("present");
20649 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
20650 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
20651 assert!(s.enabled);
20652 assert_eq!(s.last_received_pos, 0);
20653 }
20654
20655 #[test]
20656 fn create_subscription_duplicate_name_errors() {
20657 let mut e = Engine::new();
20658 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
20659 .unwrap();
20660 let err = e
20661 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
20662 .unwrap_err();
20663 assert!(
20664 alloc::format!("{err:?}").contains("DuplicateName"),
20665 "got {err:?}"
20666 );
20667 }
20668
20669 #[test]
20670 fn drop_subscription_silent_when_absent() {
20671 let mut e = Engine::new();
20672 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
20673 match r {
20674 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20675 other => panic!("expected CommandOk, got {other:?}"),
20676 }
20677 }
20678
20679 #[test]
20680 fn subscription_advance_updates_last_pos_monotone() {
20681 let mut e = Engine::new();
20682 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20683 .unwrap();
20684 assert!(e.subscription_advance("s", 100));
20685 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20686 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20688 assert!(e.subscription_advance("s", 200));
20689 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
20690 assert!(!e.subscription_advance("missing", 1));
20691 }
20692
20693 #[test]
20694 fn show_subscriptions_returns_rows_ordered_by_name() {
20695 let mut e = Engine::new();
20696 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
20697 .unwrap();
20698 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
20699 .unwrap();
20700 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
20701 let QueryResult::Rows { rows, columns } = r else {
20702 panic!()
20703 };
20704 assert_eq!(rows.len(), 2);
20705 assert_eq!(columns.len(), 5);
20706 assert_eq!(columns[0].name, "name");
20707 assert_eq!(columns[4].name, "last_received_pos");
20708 let names: Vec<&str> = rows
20710 .iter()
20711 .map(|r| {
20712 if let Value::Text(s) = &r.values[0] {
20713 s.as_str()
20714 } else {
20715 panic!()
20716 }
20717 })
20718 .collect();
20719 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
20720 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
20722 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
20723 assert_eq!(rows[0].values[3], Value::Bool(true));
20724 assert_eq!(rows[0].values[4], Value::BigInt(0));
20725 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
20727 }
20728
20729 #[test]
20730 fn subscriptions_persist_across_snapshot_envelope_v4() {
20731 let mut e = Engine::new();
20732 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
20733 .unwrap();
20734 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
20735 .unwrap();
20736 e.subscription_advance("s2", 42);
20737 let snap = e.snapshot();
20738 let e2 = Engine::restore_envelope(&snap).unwrap();
20739 assert_eq!(e2.subscriptions().len(), 2);
20740 let s1 = e2.subscriptions().get("s1").unwrap();
20741 assert_eq!(s1.conn_str, "h=A");
20742 assert_eq!(
20743 s1.publications,
20744 alloc::vec!["p1".to_string(), "p2".to_string()]
20745 );
20746 assert_eq!(s1.last_received_pos, 0);
20747 let s2 = e2.subscriptions().get("s2").unwrap();
20748 assert_eq!(s2.last_received_pos, 42);
20749 }
20750
20751 #[test]
20752 fn v3_envelope_loads_with_empty_subscriptions() {
20753 let mut e = Engine::new();
20757 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
20758 let catalog = e.catalog.serialize();
20759 let users = crate::users::serialize_users(&e.users);
20760 let pubs = e.publications.serialize();
20761 let mut buf = Vec::new();
20762 buf.extend_from_slice(b"SPGENV01");
20763 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
20765 buf.extend_from_slice(&catalog);
20766 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
20767 buf.extend_from_slice(&users);
20768 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
20769 buf.extend_from_slice(&pubs);
20770 let crc = spg_crypto::crc32::crc32(&buf);
20771 buf.extend_from_slice(&crc.to_le_bytes());
20772
20773 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
20774 assert!(e2.subscriptions().is_empty());
20775 assert!(e2.publications().contains("pub_legacy"));
20776 }
20777
20778 #[test]
20779 fn create_subscription_allowed_inside_transaction() {
20780 let mut e = Engine::new();
20781 e.execute("BEGIN").unwrap();
20782 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20783 .unwrap();
20784 e.execute("COMMIT").unwrap();
20785 assert!(e.subscriptions().contains("s"));
20786 }
20787
20788 #[test]
20790 fn analyze_populates_histogram_bounds() {
20791 let mut e = Engine::new();
20792 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
20793 .unwrap();
20794 for i in 0..50 {
20795 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
20796 .unwrap();
20797 }
20798 e.execute("ANALYZE t").unwrap();
20799 let stats = e.statistics();
20800 let id_stats = stats.get("t", "id").unwrap();
20801 assert!(id_stats.histogram_bounds.len() >= 2);
20802 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
20803 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
20804 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
20805 assert_eq!(id_stats.n_distinct, 50);
20806 }
20807
20808 #[test]
20809 fn reanalyze_overwrites_prior_stats() {
20810 let mut e = Engine::new();
20811 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20812 for i in 0..10 {
20813 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20814 .unwrap();
20815 }
20816 e.execute("ANALYZE t").unwrap();
20817 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
20818 assert_eq!(n1, 10);
20819 for i in 10..30 {
20820 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20821 .unwrap();
20822 }
20823 e.execute("ANALYZE t").unwrap();
20824 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
20825 assert_eq!(n2, 30);
20826 }
20827
20828 #[test]
20829 fn analyze_unknown_table_errors() {
20830 let mut e = Engine::new();
20831 let err = e.execute("ANALYZE nonexistent").unwrap_err();
20832 assert!(matches!(
20833 err,
20834 EngineError::Storage(StorageError::TableNotFound { .. })
20835 ));
20836 }
20837
20838 #[test]
20839 fn bare_analyze_covers_all_user_tables() {
20840 let mut e = Engine::new();
20841 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
20842 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
20843 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
20844 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
20845 let r = e.execute("ANALYZE").unwrap();
20846 match r {
20847 QueryResult::CommandOk {
20848 affected,
20849 modified_catalog,
20850 } => {
20851 assert_eq!(affected, 2);
20852 assert!(modified_catalog);
20853 }
20854 other => panic!("expected CommandOk, got {other:?}"),
20855 }
20856 assert!(e.statistics().get("t1", "id").is_some());
20857 assert!(e.statistics().get("t2", "name").is_some());
20858 }
20859
20860 #[test]
20861 fn select_from_spg_statistic_returns_rows_per_column() {
20862 let mut e = Engine::new();
20863 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
20864 .unwrap();
20865 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
20866 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
20867 e.execute("ANALYZE t").unwrap();
20868 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
20869 let QueryResult::Rows { rows, columns } = r else {
20870 panic!()
20871 };
20872 assert_eq!(columns.len(), 6);
20874 assert_eq!(columns[0].name, "table_name");
20875 assert_eq!(columns[4].name, "histogram_bounds");
20876 assert_eq!(columns[5].name, "cold_row_count");
20877 assert_eq!(rows.len(), 2, "one row per column of t");
20878 match (&rows[0].values[0], &rows[0].values[1]) {
20880 (Value::Text(t), Value::Text(c)) => {
20881 assert_eq!(t, "t");
20882 assert_eq!(c, "id");
20884 }
20885 _ => panic!(),
20886 }
20887 }
20888
20889 #[test]
20890 fn analyze_skips_vector_columns() {
20891 let mut e = Engine::new();
20894 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
20895 .unwrap();
20896 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
20897 e.execute("ANALYZE t").unwrap();
20898 assert!(e.statistics().get("t", "id").is_some());
20899 assert!(e.statistics().get("t", "v").is_none());
20900 }
20901
20902 #[test]
20903 fn statistics_persist_across_envelope_v5_round_trip() {
20904 let mut e = Engine::new();
20905 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20906 for i in 0..20 {
20907 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20908 .unwrap();
20909 }
20910 e.execute("ANALYZE").unwrap();
20911 let snap = e.snapshot();
20912 let e2 = Engine::restore_envelope(&snap).unwrap();
20913 let s = e2.statistics().get("t", "id").unwrap();
20914 assert_eq!(s.n_distinct, 20);
20915 }
20916
20917 #[test]
20920 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
20921 let mut e = Engine::new();
20925 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20926 for i in 0..9 {
20927 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20928 .unwrap();
20929 }
20930 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
20931 e.execute("INSERT INTO t VALUES (9)").unwrap();
20932 let needs = e.tables_needing_analyze();
20933 assert_eq!(needs, alloc::vec!["t".to_string()]);
20934 }
20935
20936 #[test]
20937 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
20938 let mut e = Engine::new();
20944 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20945 for i in 0..1000 {
20946 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20947 .unwrap();
20948 }
20949 e.execute("ANALYZE t").unwrap();
20950 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
20951 for i in 1000..1050 {
20952 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20953 .unwrap();
20954 }
20955 assert!(
20956 e.tables_needing_analyze().is_empty(),
20957 "50 inserts < threshold of ~105"
20958 );
20959 for i in 1050..1200 {
20960 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20961 .unwrap();
20962 }
20963 assert_eq!(
20964 e.tables_needing_analyze(),
20965 alloc::vec!["t".to_string()],
20966 "200 inserts > 0.1 × 1200 threshold"
20967 );
20968 }
20969
20970 #[test]
20971 fn auto_analyze_threshold_resets_after_analyze() {
20972 let mut e = Engine::new();
20973 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20974 for i in 0..200 {
20975 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20976 .unwrap();
20977 }
20978 assert!(!e.tables_needing_analyze().is_empty());
20979 e.execute("ANALYZE").unwrap();
20980 assert!(
20981 e.tables_needing_analyze().is_empty(),
20982 "ANALYZE must reset the counter"
20983 );
20984 }
20985
20986 #[test]
20987 fn auto_analyze_threshold_tracks_updates_and_deletes() {
20988 let mut e = Engine::new();
20989 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
20990 .unwrap();
20991 for i in 0..50 {
20992 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
20993 .unwrap();
20994 }
20995 e.execute("ANALYZE t").unwrap();
20996 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
20999 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
21000 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
21001 }
21002
21003 #[test]
21004 fn v4_envelope_loads_with_empty_statistics() {
21005 let mut e = Engine::new();
21009 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
21010 .unwrap();
21011 let catalog = e.catalog.serialize();
21012 let users = crate::users::serialize_users(&e.users);
21013 let pubs = e.publications.serialize();
21014 let subs = e.subscriptions.serialize();
21015 let mut buf = Vec::new();
21016 buf.extend_from_slice(b"SPGENV01");
21017 buf.push(4u8);
21018 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
21019 buf.extend_from_slice(&catalog);
21020 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
21021 buf.extend_from_slice(&users);
21022 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
21023 buf.extend_from_slice(&pubs);
21024 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
21025 buf.extend_from_slice(&subs);
21026 let crc = spg_crypto::crc32::crc32(&buf);
21027 buf.extend_from_slice(&crc.to_le_bytes());
21028 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
21029 assert!(e2.statistics().is_empty());
21030 }
21031
21032 #[test]
21033 fn v1_v2_envelope_loads_with_empty_publications() {
21034 let mut e = Engine::new();
21041 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
21044 .unwrap();
21045
21046 let catalog = e.catalog.serialize();
21048 let users = crate::users::serialize_users(&e.users);
21049 let mut buf = Vec::new();
21050 buf.extend_from_slice(b"SPGENV01");
21051 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
21053 buf.extend_from_slice(&catalog);
21054 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
21055 buf.extend_from_slice(&users);
21056 let crc = spg_crypto::crc32::crc32(&buf);
21057 buf.extend_from_slice(&crc.to_le_bytes());
21058
21059 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
21060 assert!(e2.publications().is_empty());
21061 }
21062}