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 plan_hit = m.expr_plans.contains_key(&key);
11230 let mut subs: Vec<&SelectStatement> = Vec::new();
11231 if !plan_hit {
11232 collect_scalar_subqueries(expr, &mut subs);
11233 }
11234 if !plan_hit && !subs.is_empty() {
11235 let mut plan: Vec<Option<alloc::rc::Rc<memoize::GroupMap>>> =
11236 Vec::with_capacity(subs.len());
11237 for sub in &subs {
11238 let repr = alloc::format!("{sub}");
11239 if !m.group_maps.contains_key(&repr) {
11240 let built = self
11241 .try_batch_correlated_scalar(sub, cancel)?
11242 .map(alloc::rc::Rc::new);
11243 m.group_maps.insert(repr.clone(), built);
11244 }
11245 plan.push(m.group_maps.get(&repr).cloned().flatten());
11246 }
11247 let mut template = expr.clone();
11248 hollow_scalar_subqueries(&mut template);
11249 m.expr_plans.insert(key, (subs.len(), plan, template));
11250 }
11251 if let Some((_, plan, template)) = m.expr_plans.get(&key)
11252 && !plan.is_empty()
11253 && plan.iter().all(|p| p.is_some())
11254 {
11255 let plan = plan.clone();
11262 let mut e = template.clone();
11263 let mut idx = 0usize;
11264 let ok = splice_planned_subqueries(&mut e, &plan, &mut idx, row, ctx)?;
11265 if ok {
11266 if expr_has_subquery(&e) {
11267 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11268 }
11269 return eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval);
11270 }
11271 }
11272 }
11273 let mut e = expr.clone();
11274 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11275 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
11276 }
11277
11278 fn resolve_correlated_in_expr(
11279 &self,
11280 e: &mut Expr,
11281 row: &Row,
11282 ctx: &EvalContext<'_>,
11283 cancel: CancelToken<'_>,
11284 mut memo: Option<&mut memoize::MemoizeCache>,
11285 ) -> Result<(), EngineError> {
11286 match e {
11287 Expr::AggregateOrdered { call, order_by, .. } => {
11288 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
11289 for o in order_by.iter_mut() {
11290 self.resolve_correlated_in_expr(
11291 &mut o.expr,
11292 row,
11293 ctx,
11294 cancel,
11295 memo.as_deref_mut(),
11296 )?;
11297 }
11298 }
11299 Expr::ScalarSubquery(inner) => {
11300 if memo.is_some() {
11307 let repr = alloc::format!("{}", **inner);
11308 let entry_known = memo
11309 .as_ref()
11310 .is_some_and(|m| m.group_maps.contains_key(&repr));
11311 if !entry_known {
11312 let built = self
11313 .try_batch_correlated_scalar(inner, cancel)?
11314 .map(alloc::rc::Rc::new);
11315 if let Some(m) = memo.as_deref_mut() {
11316 m.group_maps.insert(repr.clone(), built);
11317 }
11318 }
11319 if let Some(m) = memo.as_deref_mut()
11320 && let Some(Some(gm)) = m.group_maps.get(&repr)
11321 {
11322 let (outer_col, map) = gm.as_ref();
11323 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
11324 .map_err(EngineError::Eval)?;
11325 let v = if matches!(key_v, Value::Null) {
11326 Value::Null
11327 } else {
11328 map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
11329 .cloned()
11330 .unwrap_or(Value::Null)
11331 };
11332 *e = value_to_literal_expr(v)?;
11333 return Ok(());
11334 }
11335 }
11336 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
11341 subquery_repr: alloc::format!("{}", **inner),
11342 outer_values: row.values.clone(),
11343 });
11344 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
11345 && let Some(cached) = cache.get(k)
11346 {
11347 *e = value_to_literal_expr(cached)?;
11348 return Ok(());
11349 }
11350 let mut s = (**inner).clone();
11351 substitute_outer_columns(&mut s, row, ctx);
11352 let r = self.exec_select_cancel(&s, cancel)?;
11353 let QueryResult::Rows { rows, .. } = r else {
11354 return Err(EngineError::Unsupported(
11355 "scalar subquery: inner did not return rows".into(),
11356 ));
11357 };
11358 let value = match rows.as_slice() {
11359 [] => Value::Null,
11360 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
11361 _ => {
11362 return Err(EngineError::Unsupported(alloc::format!(
11363 "scalar subquery returned {} rows; expected 0 or 1",
11364 rows.len()
11365 )));
11366 }
11367 };
11368 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
11369 cache.insert(k, value.clone());
11370 }
11371 *e = value_to_literal_expr(value)?;
11372 }
11373 Expr::Exists { subquery, negated } => {
11374 let mut s = (**subquery).clone();
11375 substitute_outer_columns(&mut s, row, ctx);
11376 let r = self.exec_select_cancel(&s, cancel)?;
11377 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
11378 let bit = if *negated { !exists } else { exists };
11379 *e = Expr::Literal(Literal::Bool(bit));
11380 }
11381 Expr::InSubquery {
11382 expr: lhs,
11383 subquery,
11384 negated,
11385 } => {
11386 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11387 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
11388 let mut s = (**subquery).clone();
11389 substitute_outer_columns(&mut s, row, ctx);
11390 let r = self.exec_select_cancel(&s, cancel)?;
11391 let QueryResult::Rows { columns, rows, .. } = r else {
11392 return Err(EngineError::Unsupported(
11393 "IN-subquery: inner did not return rows".into(),
11394 ));
11395 };
11396 if columns.len() != 1 {
11397 return Err(EngineError::Unsupported(alloc::format!(
11398 "IN-subquery must project exactly one column; got {}",
11399 columns.len()
11400 )));
11401 }
11402 let mut found = false;
11403 let mut any_null = false;
11404 for r0 in rows {
11405 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
11406 if v.is_null() {
11407 any_null = true;
11408 continue;
11409 }
11410 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
11411 found = true;
11412 break;
11413 }
11414 }
11415 let bit = if found {
11416 !*negated
11417 } else if any_null {
11418 return Err(EngineError::Unsupported(
11419 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
11420 ));
11421 } else {
11422 *negated
11423 };
11424 *e = Expr::Literal(Literal::Bool(bit));
11425 }
11426 Expr::Binary { lhs, rhs, .. } => {
11427 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11428 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
11429 }
11430 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11431 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11432 }
11433 Expr::Like { expr, pattern, .. } => {
11434 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11435 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
11436 }
11437 Expr::FunctionCall { args, .. } => {
11438 for a in args {
11439 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
11440 }
11441 }
11442 Expr::Extract { source, .. } => {
11443 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
11444 }
11445 Expr::WindowFunction { .. }
11446 | Expr::Literal(_)
11447 | Expr::Placeholder(_)
11448 | Expr::Column(_) => {}
11449 Expr::Array(items) => {
11451 for elem in items {
11452 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
11453 }
11454 }
11455 Expr::ArraySubscript { target, index } => {
11456 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
11457 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
11458 }
11459 Expr::AnyAll { expr, array, .. } => {
11460 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11461 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
11462 }
11463 Expr::Case {
11464 operand,
11465 branches,
11466 else_branch,
11467 } => {
11468 if let Some(o) = operand {
11469 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
11470 }
11471 for (w, t) in branches {
11472 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
11473 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
11474 }
11475 if let Some(e) = else_branch {
11476 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
11477 }
11478 }
11479 }
11480 Ok(())
11481 }
11482
11483 fn subquery_replacement(
11484 &self,
11485 e: &Expr,
11486 cancel: CancelToken<'_>,
11487 ) -> Result<Option<Expr>, EngineError> {
11488 match e {
11489 Expr::ScalarSubquery(inner) => {
11490 let mut s = (**inner).clone();
11491 self.resolve_select_subqueries(&mut s, cancel)?;
11494 let r = match self.exec_bare_select_cancel(&s, cancel) {
11495 Ok(r) => r,
11496 Err(e) if is_correlation_error(&e) => return Ok(None),
11497 Err(e) => return Err(e),
11498 };
11499 let QueryResult::Rows { rows, .. } = r else {
11500 return Err(EngineError::Unsupported(
11501 "scalar subquery: inner statement did not return rows".into(),
11502 ));
11503 };
11504 let value = match rows.as_slice() {
11505 [] => Value::Null,
11506 [row] => row.values.first().cloned().unwrap_or(Value::Null),
11507 _ => {
11508 return Err(EngineError::Unsupported(alloc::format!(
11509 "scalar subquery returned {} rows; expected 0 or 1",
11510 rows.len()
11511 )));
11512 }
11513 };
11514 Ok(Some(value_to_literal_expr(value)?))
11515 }
11516 Expr::Exists { subquery, negated } => {
11517 let mut s = (**subquery).clone();
11518 self.resolve_select_subqueries(&mut s, cancel)?;
11519 let r = match self.exec_bare_select_cancel(&s, cancel) {
11520 Ok(r) => r,
11521 Err(e) if is_correlation_error(&e) => return Ok(None),
11522 Err(e) => return Err(e),
11523 };
11524 let exists = match r {
11525 QueryResult::Rows { rows, .. } => !rows.is_empty(),
11526 QueryResult::CommandOk { .. } => false,
11527 };
11528 let bit = if *negated { !exists } else { exists };
11529 Ok(Some(Expr::Literal(Literal::Bool(bit))))
11530 }
11531 Expr::InSubquery {
11532 expr,
11533 subquery,
11534 negated,
11535 } => {
11536 let mut s = (**subquery).clone();
11537 self.resolve_select_subqueries(&mut s, cancel)?;
11538 let r = match self.exec_bare_select_cancel(&s, cancel) {
11539 Ok(r) => r,
11540 Err(e) if is_correlation_error(&e) => return Ok(None),
11541 Err(e) => return Err(e),
11542 };
11543 let QueryResult::Rows { columns, rows, .. } = r else {
11544 return Err(EngineError::Unsupported(
11545 "IN-subquery: inner statement did not return rows".into(),
11546 ));
11547 };
11548 if columns.len() != 1 {
11549 return Err(EngineError::Unsupported(alloc::format!(
11550 "IN-subquery must project exactly one column; got {}",
11551 columns.len()
11552 )));
11553 }
11554 let mut acc: Option<Expr> = None;
11557 for row in rows {
11558 let v = row.values.into_iter().next().unwrap_or(Value::Null);
11559 let lit = value_to_literal_expr(v)?;
11560 let cmp = Expr::Binary {
11561 lhs: expr.clone(),
11562 op: BinOp::Eq,
11563 rhs: Box::new(lit),
11564 };
11565 acc = Some(match acc {
11566 None => cmp,
11567 Some(prev) => Expr::Binary {
11568 lhs: Box::new(prev),
11569 op: BinOp::Or,
11570 rhs: Box::new(cmp),
11571 },
11572 });
11573 }
11574 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
11575 let final_expr = if *negated {
11576 Expr::Unary {
11577 op: UnOp::Not,
11578 expr: Box::new(combined),
11579 }
11580 } else {
11581 combined
11582 };
11583 Ok(Some(final_expr))
11584 }
11585 _ => Ok(None),
11586 }
11587 }
11588}
11589
11590fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
11602 if let Some(from) = &stmt.from
11603 && from_refers_to(from, target)
11604 {
11605 return true;
11606 }
11607 for (_, peer) in &stmt.unions {
11608 if select_refers_to(peer, target) {
11609 return true;
11610 }
11611 }
11612 for item in &stmt.items {
11613 if let SelectItem::Expr { expr, .. } = item
11614 && expr_refers_to(expr, target)
11615 {
11616 return true;
11617 }
11618 }
11619 if let Some(w) = &stmt.where_
11620 && expr_refers_to(w, target)
11621 {
11622 return true;
11623 }
11624 false
11625}
11626
11627fn from_refers_to(from: &FromClause, target: &str) -> bool {
11628 if from.primary.name.eq_ignore_ascii_case(target) {
11629 return true;
11630 }
11631 from.joins
11632 .iter()
11633 .any(|j| j.table.name.eq_ignore_ascii_case(target))
11634}
11635
11636fn collect_qualified_refs(
11641 stmt: &SelectStatement,
11642 out: &mut alloc::collections::BTreeSet<(String, String)>,
11643) -> Option<()> {
11644 for item in &stmt.items {
11645 match item {
11646 SelectItem::Wildcard => return None,
11647 SelectItem::Expr { expr, .. } => collect_qualified_refs_expr(expr, out)?,
11648 }
11649 }
11650 if let Some(w) = &stmt.where_ {
11651 collect_qualified_refs_expr(w, out)?;
11652 }
11653 if let Some(from) = &stmt.from {
11654 for j in &from.joins {
11655 if let Some(on) = &j.on {
11656 collect_qualified_refs_expr(on, out)?;
11657 }
11658 if j.table.lateral_subquery.is_some() {
11659 return None;
11660 }
11661 }
11662 }
11663 if let Some(gs) = &stmt.group_by {
11664 for g in gs {
11665 collect_qualified_refs_expr(g, out)?;
11666 }
11667 }
11668 if let Some(h) = &stmt.having {
11669 collect_qualified_refs_expr(h, out)?;
11670 }
11671 for o in &stmt.order_by {
11672 collect_qualified_refs_expr(&o.expr, out)?;
11673 }
11674 for (_, peer) in &stmt.unions {
11675 collect_qualified_refs(peer, out)?;
11676 }
11677 for cte in &stmt.ctes {
11678 collect_qualified_refs(&cte.body, out)?;
11679 }
11680 Some(())
11681}
11682
11683fn collect_qualified_refs_expr(
11684 e: &Expr,
11685 out: &mut alloc::collections::BTreeSet<(String, String)>,
11686) -> Option<()> {
11687 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
11690 let mut subs: Vec<&SelectStatement> = Vec::new();
11691 visit_expr_columns_and_subqueries(
11692 e,
11693 &mut |c: &spg_sql::ast::ColumnName| cols.push(c.clone()),
11694 &mut |sub| subs.push(sub),
11695 );
11696 for c in cols {
11697 match c.qualifier {
11698 Some(q) => {
11699 out.insert((q, c.name));
11700 }
11701 None => return None,
11702 }
11703 }
11704 for sub in subs {
11705 collect_qualified_refs(sub, out)?;
11706 }
11707 Some(())
11708}
11709
11710fn visit_expr_columns_and_subqueries<'a>(
11713 e: &'a Expr,
11714 on_col: &mut impl FnMut(&'a spg_sql::ast::ColumnName),
11715 on_sub: &mut impl FnMut(&'a SelectStatement),
11716) {
11717 match e {
11718 Expr::Column(c) => on_col(c),
11719 Expr::ScalarSubquery(s) => on_sub(s),
11720 Expr::Exists { subquery, .. } => on_sub(subquery),
11721 Expr::InSubquery { expr, subquery, .. } => {
11722 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11723 on_sub(subquery);
11724 }
11725 Expr::Binary { lhs, rhs, .. } => {
11726 visit_expr_columns_and_subqueries(lhs, on_col, on_sub);
11727 visit_expr_columns_and_subqueries(rhs, on_col, on_sub);
11728 }
11729 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11730 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11731 }
11732 Expr::Like { expr, pattern, .. } => {
11733 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11734 visit_expr_columns_and_subqueries(pattern, on_col, on_sub);
11735 }
11736 Expr::FunctionCall { args, .. } => {
11737 for a in args {
11738 visit_expr_columns_and_subqueries(a, on_col, on_sub);
11739 }
11740 }
11741 Expr::AggregateOrdered { call, order_by, .. } => {
11742 visit_expr_columns_and_subqueries(call, on_col, on_sub);
11743 for o in order_by {
11744 visit_expr_columns_and_subqueries(&o.expr, on_col, on_sub);
11745 }
11746 }
11747 Expr::Case {
11748 operand,
11749 branches,
11750 else_branch,
11751 } => {
11752 if let Some(op) = operand {
11753 visit_expr_columns_and_subqueries(op, on_col, on_sub);
11754 }
11755 for (w, t) in branches {
11756 visit_expr_columns_and_subqueries(w, on_col, on_sub);
11757 visit_expr_columns_and_subqueries(t, on_col, on_sub);
11758 }
11759 if let Some(eb) = else_branch {
11760 visit_expr_columns_and_subqueries(eb, on_col, on_sub);
11761 }
11762 }
11763 Expr::ArraySubscript { target, index } => {
11764 visit_expr_columns_and_subqueries(target, on_col, on_sub);
11765 visit_expr_columns_and_subqueries(index, on_col, on_sub);
11766 }
11767 Expr::Literal(_) | Expr::Placeholder(_) => {}
11768 _ => {
11773 static BAIL: spg_sql::ast::ColumnName = spg_sql::ast::ColumnName {
11776 qualifier: None,
11777 name: String::new(),
11778 };
11779 on_col(&BAIL);
11780 }
11781 }
11782}
11783
11784fn collect_column_qualifiers<'e>(e: &'e Expr, out: &mut Vec<&'e str>, all_qualified: &mut bool) {
11788 if let Expr::Column(c) = e {
11789 match &c.qualifier {
11790 Some(q) => out.push(q.as_str()),
11791 None => *all_qualified = false,
11792 }
11793 return;
11794 }
11795 match e {
11798 Expr::Binary { lhs, rhs, .. } => {
11799 collect_column_qualifiers(lhs, out, all_qualified);
11800 collect_column_qualifiers(rhs, out, all_qualified);
11801 }
11802 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11803 collect_column_qualifiers(expr, out, all_qualified);
11804 }
11805 Expr::Like { expr, pattern, .. } => {
11806 collect_column_qualifiers(expr, out, all_qualified);
11807 collect_column_qualifiers(pattern, out, all_qualified);
11808 }
11809 Expr::FunctionCall { args, .. } => {
11810 for a in args {
11811 collect_column_qualifiers(a, out, all_qualified);
11812 }
11813 }
11814 Expr::Literal(_) | Expr::Placeholder(_) => {}
11815 _ => *all_qualified = false,
11818 }
11819}
11820
11821fn expr_refers_to(e: &Expr, target: &str) -> bool {
11822 match e {
11823 Expr::AggregateOrdered { call, order_by, .. } => {
11824 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
11825 }
11826 Expr::ScalarSubquery(s) => select_refers_to(s, target),
11827 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
11828 select_refers_to(subquery, target)
11829 }
11830 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
11831 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11832 expr_refers_to(expr, target)
11833 }
11834 Expr::Like { expr, pattern, .. } => {
11835 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
11836 }
11837 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
11838 Expr::Extract { source, .. } => expr_refers_to(source, target),
11839 Expr::WindowFunction {
11840 args,
11841 partition_by,
11842 order_by,
11843 ..
11844 } => {
11845 args.iter().any(|a| expr_refers_to(a, target))
11846 || partition_by.iter().any(|p| expr_refers_to(p, target))
11847 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
11848 }
11849 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
11850 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
11851 Expr::ArraySubscript { target: t, index } => {
11852 expr_refers_to(t, target) || expr_refers_to(index, target)
11853 }
11854 Expr::AnyAll { expr, array, .. } => {
11855 expr_refers_to(expr, target) || expr_refers_to(array, target)
11856 }
11857 Expr::Case {
11858 operand,
11859 branches,
11860 else_branch,
11861 } => {
11862 operand
11863 .as_deref()
11864 .is_some_and(|o| expr_refers_to(o, target))
11865 || branches
11866 .iter()
11867 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
11868 || else_branch
11869 .as_deref()
11870 .is_some_and(|e| expr_refers_to(e, target))
11871 }
11872 }
11873}
11874
11875fn pg_data_type_text(ty: DataType) -> alloc::string::String {
11886 let s = match ty {
11887 DataType::Int => "integer",
11888 DataType::BigInt => "bigint",
11889 DataType::SmallInt => "smallint",
11890 DataType::Float => "double precision",
11891 DataType::Bool => "boolean",
11892 DataType::Text => "text",
11893 DataType::Varchar(_) => "character varying",
11894 DataType::Date => "date",
11895 DataType::Timestamp => "timestamp without time zone",
11896 DataType::Timestamptz => "timestamp with time zone",
11897 DataType::Json => "jsonb",
11898 DataType::Bytes => "bytea",
11899 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
11900 DataType::TsVector => "tsvector",
11901 DataType::TsQuery => "tsquery",
11902 DataType::Vector { .. } => "USER-DEFINED",
11903 _ => "USER-DEFINED",
11906 };
11907 alloc::string::String::from(s)
11908}
11909
11910fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11917 let schema = alloc::vec![
11918 ColumnSchema::new("table_catalog", DataType::Text, false),
11919 ColumnSchema::new("table_schema", DataType::Text, false),
11920 ColumnSchema::new("table_name", DataType::Text, false),
11921 ColumnSchema::new("column_name", DataType::Text, false),
11922 ColumnSchema::new("ordinal_position", DataType::Int, false),
11923 ColumnSchema::new("is_nullable", DataType::Text, false),
11924 ColumnSchema::new("data_type", DataType::Text, false),
11925 ];
11926 let mut rows: Vec<Row> = Vec::new();
11927 for tname in cat.table_names() {
11928 let Some(t) = cat.get(&tname) else { continue };
11929 for (i, col) in t.schema().columns.iter().enumerate() {
11930 #[allow(clippy::cast_possible_wrap)]
11931 let ordinal = (i + 1) as i32;
11932 rows.push(Row::new(alloc::vec![
11933 Value::Text("spg".into()),
11934 Value::Text("public".into()),
11935 Value::Text(tname.clone()),
11936 Value::Text(col.name.clone()),
11937 Value::Int(ordinal),
11938 Value::Text(if col.nullable {
11939 "YES".into()
11940 } else {
11941 "NO".into()
11942 }),
11943 Value::Text(pg_data_type_text(col.ty)),
11944 ]));
11945 }
11946 }
11947 (schema, rows)
11948}
11949
11950fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11952 let schema = alloc::vec![
11953 ColumnSchema::new("table_catalog", DataType::Text, false),
11954 ColumnSchema::new("table_schema", DataType::Text, false),
11955 ColumnSchema::new("table_name", DataType::Text, false),
11956 ColumnSchema::new("table_type", DataType::Text, false),
11957 ];
11958 let mut rows: Vec<Row> = Vec::new();
11959 for tname in cat.table_names() {
11960 rows.push(Row::new(alloc::vec![
11961 Value::Text("spg".into()),
11962 Value::Text("public".into()),
11963 Value::Text(tname.clone()),
11964 Value::Text("BASE TABLE".into()),
11965 ]));
11966 }
11967 (schema, rows)
11968}
11969
11970fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11974 let schema = alloc::vec![
11975 ColumnSchema::new("relname", DataType::Text, false),
11976 ColumnSchema::new("relkind", DataType::Text, false),
11977 ColumnSchema::new("relnamespace", DataType::BigInt, false),
11978 ];
11979 let mut rows: Vec<Row> = Vec::new();
11980 for tname in cat.table_names() {
11981 rows.push(Row::new(alloc::vec![
11982 Value::Text(tname.clone()),
11983 Value::Text("r".into()),
11984 Value::BigInt(2200), ]));
11986 }
11987 (schema, rows)
11988}
11989
11990fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11994 let schema = alloc::vec![
11995 ColumnSchema::new("attrelid", DataType::Text, false),
11996 ColumnSchema::new("attname", DataType::Text, false),
11997 ColumnSchema::new("attnum", DataType::Int, false),
11998 ColumnSchema::new("atttypid", DataType::Text, false),
11999 ColumnSchema::new("attnotnull", DataType::Bool, false),
12000 ];
12001 let mut rows: Vec<Row> = Vec::new();
12002 for tname in cat.table_names() {
12003 let Some(t) = cat.get(&tname) else { continue };
12004 for (i, col) in t.schema().columns.iter().enumerate() {
12005 #[allow(clippy::cast_possible_wrap)]
12006 let ordinal = (i + 1) as i32;
12007 rows.push(Row::new(alloc::vec![
12008 Value::Text(tname.clone()),
12009 Value::Text(col.name.clone()),
12010 Value::Int(ordinal),
12011 Value::Text(pg_data_type_text(col.ty)),
12012 Value::Bool(!col.nullable),
12013 ]));
12014 }
12015 }
12016 (schema, rows)
12017}
12018
12019fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12036 let schema = alloc::vec![
12037 ColumnSchema::new("oid", DataType::BigInt, false),
12038 ColumnSchema::new("typname", DataType::Text, false),
12039 ColumnSchema::new("typlen", DataType::SmallInt, false),
12040 ColumnSchema::new("typtype", DataType::Text, false),
12041 ColumnSchema::new("typcategory", DataType::Text, false),
12042 ColumnSchema::new("typelem", DataType::BigInt, false),
12043 ColumnSchema::new("typarray", DataType::BigInt, false),
12044 ColumnSchema::new("typnamespace", DataType::BigInt, false),
12045 ];
12046 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
12049 (16, "bool", 1, "b", "B", 0, 1000),
12051 (17, "bytea", -1, "b", "U", 0, 1001),
12052 (18, "char", 1, "b", "S", 0, 1002),
12053 (19, "name", 64, "b", "S", 0, 1003),
12054 (20, "int8", 8, "b", "N", 0, 1016),
12055 (21, "int2", 2, "b", "N", 0, 1005),
12056 (23, "int4", 4, "b", "N", 0, 1007),
12057 (24, "regproc", 4, "b", "N", 0, 1008),
12058 (25, "text", -1, "b", "S", 0, 1009),
12059 (26, "oid", 4, "b", "N", 0, 1028),
12060 (114, "json", -1, "b", "U", 0, 199),
12061 (142, "xml", -1, "b", "U", 0, 143),
12062 (700, "float4", 4, "b", "N", 0, 1021),
12063 (701, "float8", 8, "b", "N", 0, 1022),
12064 (650, "cidr", -1, "b", "I", 0, 651),
12065 (869, "inet", -1, "b", "I", 0, 1041),
12066 (829, "macaddr", 6, "b", "U", 0, 1040),
12067 (1042, "bpchar", -1, "b", "S", 0, 1014),
12068 (1043, "varchar", -1, "b", "S", 0, 1015),
12069 (1082, "date", 4, "b", "D", 0, 1182),
12070 (1083, "time", 8, "b", "D", 0, 1183),
12071 (1114, "timestamp", 8, "b", "D", 0, 1115),
12072 (1184, "timestamptz", 8, "b", "D", 0, 1185),
12073 (1186, "interval", 16, "b", "T", 0, 1187),
12074 (1266, "timetz", 12, "b", "D", 0, 1270),
12075 (1700, "numeric", -1, "b", "N", 0, 1231),
12076 (790, "money", 8, "b", "N", 0, 791),
12077 (2950, "uuid", 16, "b", "U", 0, 2951),
12078 (3802, "jsonb", -1, "b", "U", 0, 3807),
12079 (3614, "tsvector", -1, "b", "U", 0, 3643),
12080 (3615, "tsquery", -1, "b", "U", 0, 3645),
12081 (3908, "tstzrange", -1, "r", "R", 0, 3909),
12083 (3910, "tsrange", -1, "r", "R", 0, 3911),
12084 (3904, "int4range", -1, "r", "R", 0, 3905),
12085 (3926, "int8range", -1, "r", "R", 0, 3927),
12086 (3906, "numrange", -1, "r", "R", 0, 3907),
12087 (3912, "daterange", -1, "r", "R", 0, 3913),
12088 ];
12089 let arrays: &[(i64, &str, i64)] = &[
12092 (1000, "_bool", 16),
12093 (1001, "_bytea", 17),
12094 (1002, "_char", 18),
12095 (1003, "_name", 19),
12096 (1016, "_int8", 20),
12097 (1005, "_int2", 21),
12098 (1007, "_int4", 23),
12099 (1008, "_regproc", 24),
12100 (1009, "_text", 25),
12101 (1028, "_oid", 26),
12102 (199, "_json", 114),
12103 (143, "_xml", 142),
12104 (1021, "_float4", 700),
12105 (1022, "_float8", 701),
12106 (651, "_cidr", 650),
12107 (1041, "_inet", 869),
12108 (1040, "_macaddr", 829),
12109 (1014, "_bpchar", 1042),
12110 (1015, "_varchar", 1043),
12111 (1182, "_date", 1082),
12112 (1183, "_time", 1083),
12113 (1115, "_timestamp", 1114),
12114 (1185, "_timestamptz", 1184),
12115 (1187, "_interval", 1186),
12116 (1270, "_timetz", 1266),
12117 (1231, "_numeric", 1700),
12118 (791, "_money", 790),
12119 (2951, "_uuid", 2950),
12120 (3807, "_jsonb", 3802),
12121 (3643, "_tsvector", 3614),
12122 (3645, "_tsquery", 3615),
12123 ];
12124 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
12125 for &(oid, name, len, ty, cat, elem, arr) in scalars {
12126 rows.push(Row::new(alloc::vec![
12127 Value::BigInt(oid),
12128 Value::Text(name.into()),
12129 Value::SmallInt(len),
12130 Value::Text(ty.into()),
12131 Value::Text(cat.into()),
12132 Value::BigInt(elem),
12133 Value::BigInt(arr),
12134 Value::BigInt(2200),
12135 ]));
12136 }
12137 for &(oid, name, elem) in arrays {
12138 rows.push(Row::new(alloc::vec![
12139 Value::BigInt(oid),
12140 Value::Text(name.into()),
12141 Value::SmallInt(-1),
12142 Value::Text("b".into()),
12143 Value::Text("A".into()),
12144 Value::BigInt(elem),
12145 Value::BigInt(0),
12146 Value::BigInt(2200),
12147 ]));
12148 }
12149 (schema, rows)
12150}
12151
12152fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12171 let schema = alloc::vec![
12172 ColumnSchema::new("tgname", DataType::Text, false),
12173 ColumnSchema::new("relname", DataType::Text, false),
12174 ColumnSchema::new("tgenabled", DataType::Text, false),
12175 ColumnSchema::new("timing", DataType::Text, false),
12176 ColumnSchema::new("events", DataType::Text, false),
12177 ColumnSchema::new("function", DataType::Text, false),
12178 ];
12179 let rows: Vec<Row> = cat
12180 .triggers()
12181 .iter()
12182 .map(|t| {
12183 Row::new(alloc::vec![
12184 Value::Text(t.name.clone()),
12185 Value::Text(t.table.clone()),
12186 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
12187 Value::Text(t.timing.clone()),
12188 Value::Text(t.events.join(" OR ")),
12189 Value::Text(t.function.clone()),
12190 ])
12191 })
12192 .collect();
12193 (schema, rows)
12194}
12195
12196fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12197 let schema = alloc::vec![
12198 ColumnSchema::new("oid", DataType::BigInt, false),
12199 ColumnSchema::new("proname", DataType::Text, false),
12200 ColumnSchema::new("pronamespace", DataType::BigInt, false),
12201 ColumnSchema::new("prokind", DataType::Text, false),
12202 ColumnSchema::new("pronargs", DataType::Int, false),
12203 ColumnSchema::new("prorettype", DataType::BigInt, false),
12204 ];
12205 let funcs: &[(i64, &str, &str, i32, i64)] = &[
12208 (1318, "length", "f", 1, 23),
12210 (871, "upper", "f", 1, 25),
12211 (870, "lower", "f", 1, 25),
12212 (936, "substring", "f", 3, 25),
12213 (937, "substring", "f", 2, 25),
12214 (3055, "btrim", "f", 1, 25),
12215 (885, "btrim", "f", 2, 25),
12216 (3056, "ltrim", "f", 1, 25),
12217 (875, "ltrim", "f", 2, 25),
12218 (3057, "rtrim", "f", 1, 25),
12219 (876, "rtrim", "f", 2, 25),
12220 (1397, "abs", "f", 1, 23),
12221 (1396, "abs", "f", 1, 20),
12222 (1606, "round", "f", 1, 1700),
12223 (1707, "round", "f", 2, 1700),
12224 (2308, "ceil", "f", 1, 701),
12225 (2309, "ceiling", "f", 1, 701),
12226 (2310, "floor", "f", 1, 701),
12227 (1376, "sqrt", "f", 1, 701),
12228 (1369, "ln", "f", 1, 701),
12229 (1373, "exp", "f", 1, 701),
12230 (1368, "power", "f", 2, 701),
12231 (2228, "random", "f", 0, 701),
12232 (1299, "now", "f", 0, 1184),
12234 (1274, "current_timestamp", "f", 0, 1184),
12235 (1140, "current_date", "f", 0, 1082),
12236 (2050, "current_time", "f", 0, 1083),
12237 (1158, "date_trunc", "f", 2, 1184),
12238 (1171, "date_part", "f", 2, 701),
12239 (1172, "age", "f", 1, 1186),
12240 (936, "to_char", "f", 2, 25),
12241 (861, "current_database", "f", 0, 19),
12243 (745, "current_user", "f", 0, 19),
12244 (745, "session_user", "f", 0, 19),
12245 (1402, "current_schema", "f", 0, 19),
12246 (3058, "concat", "f", -1, 25),
12248 (3059, "concat_ws", "f", -1, 25),
12249 (3539, "format", "f", -1, 25),
12250 (2877, "pg_typeof", "f", 1, 2206),
12252 (3198, "json_build_object", "f", -1, 114),
12254 (3199, "jsonb_build_object", "f", -1, 3802),
12255 (3271, "json_build_array", "f", -1, 114),
12256 (3272, "jsonb_build_array", "f", -1, 3802),
12257 (3253, "gen_random_uuid", "f", 0, 2950),
12259 (3252, "uuid_generate_v4", "f", 0, 2950),
12260 (2147, "count", "a", 0, 20),
12262 (2803, "count", "a", -1, 20),
12263 (2116, "max", "a", 1, 23),
12264 (2132, "min", "a", 1, 23),
12265 (2108, "sum", "a", 1, 20),
12266 (2100, "avg", "a", 1, 1700),
12267 (2517, "string_agg", "a", 2, 25),
12268 (2747, "array_agg", "a", 1, 1009),
12269 (2517, "bool_and", "a", 1, 16),
12270 (2518, "bool_or", "a", 1, 16),
12271 (2519, "every", "a", 1, 16),
12272 (3100, "row_number", "w", 0, 20),
12274 (3101, "rank", "w", 0, 20),
12275 (3102, "dense_rank", "w", 0, 20),
12276 (3103, "percent_rank", "w", 0, 701),
12277 (3104, "cume_dist", "w", 0, 701),
12278 (3105, "lag", "w", -1, 2283),
12279 (3106, "lead", "w", -1, 2283),
12280 (3107, "first_value", "w", 1, 2283),
12281 (3108, "last_value", "w", 1, 2283),
12282 (3109, "nth_value", "w", 2, 2283),
12283 ];
12284 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
12285 for &(oid, name, kind, nargs, rettype) in funcs {
12286 rows.push(Row::new(alloc::vec![
12287 Value::BigInt(oid),
12288 Value::Text(name.into()),
12289 Value::BigInt(11),
12290 Value::Text(kind.into()),
12291 Value::Int(nargs),
12292 Value::BigInt(rettype),
12293 ]));
12294 }
12295 (schema, rows)
12296}
12297
12298fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12304 let schema = alloc::vec![
12305 ColumnSchema::new("user", DataType::Text, false),
12306 ColumnSchema::new("host", DataType::Text, false),
12307 ColumnSchema::new("select_priv", DataType::Text, false),
12308 ];
12309 let mut rows: Vec<Row> = Vec::new();
12310 rows.push(Row::new(alloc::vec![
12311 Value::Text("root".into()),
12312 Value::Text("localhost".into()),
12313 Value::Text("Y".into()),
12314 ]));
12315 for (name, _) in engine.users.iter() {
12316 if name != "root" {
12317 rows.push(Row::new(alloc::vec![
12318 Value::Text(name.to_string()),
12319 Value::Text("%".into()),
12320 Value::Text("Y".into()),
12321 ]));
12322 }
12323 }
12324 (schema, rows)
12325}
12326
12327fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
12332 let schema = alloc::vec![
12333 ColumnSchema::new("host", DataType::Text, false),
12334 ColumnSchema::new("db", DataType::Text, false),
12335 ColumnSchema::new("user", DataType::Text, false),
12336 ColumnSchema::new("select_priv", DataType::Text, false),
12337 ];
12338 let rows = alloc::vec![Row::new(alloc::vec![
12339 Value::Text("localhost".into()),
12340 Value::Text("postgres".into()),
12341 Value::Text("root".into()),
12342 Value::Text("Y".into()),
12343 ])];
12344 (schema, rows)
12345}
12346
12347fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12360 let schema = alloc::vec![
12361 ColumnSchema::new("constraint_name", DataType::Text, false),
12362 ColumnSchema::new("table_name", DataType::Text, false),
12363 ColumnSchema::new("column_name", DataType::Text, false),
12364 ColumnSchema::new("ordinal_position", DataType::Int, false),
12365 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12366 ColumnSchema::new("referenced_column_name", DataType::Text, false),
12367 ];
12368 let mut rows: Vec<Row> = Vec::new();
12369 for tname in cat.table_names() {
12370 let Some(t) = cat.get(&tname) else { continue };
12371 let cols = &t.schema().columns;
12372 let col_name_at = |pos: usize| -> String {
12373 cols.get(pos)
12374 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12375 };
12376 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12378 let conname = fk
12379 .name
12380 .clone()
12381 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12382 for (i, (&local, &parent)) in fk
12383 .local_columns
12384 .iter()
12385 .zip(fk.parent_columns.iter())
12386 .enumerate()
12387 {
12388 let parent_name = cat
12389 .get(&fk.parent_table)
12390 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
12391 .unwrap_or_else(|| alloc::format!("col{parent}"));
12392 #[allow(clippy::cast_possible_wrap)]
12393 let ordinal = (i + 1) as i32;
12394 rows.push(Row::new(alloc::vec![
12395 Value::Text(conname.clone()),
12396 Value::Text(tname.clone()),
12397 Value::Text(col_name_at(local)),
12398 Value::Int(ordinal),
12399 Value::Text(fk.parent_table.clone()),
12400 Value::Text(parent_name),
12401 ]));
12402 }
12403 }
12404 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12406 let conname = if uc.is_primary_key {
12407 alloc::format!("{}_pkey", tname)
12408 } else {
12409 alloc::format!("{}_uniq{ci}", tname)
12410 };
12411 for (i, &local) in uc.columns.iter().enumerate() {
12412 #[allow(clippy::cast_possible_wrap)]
12413 let ordinal = (i + 1) as i32;
12414 rows.push(Row::new(alloc::vec![
12415 Value::Text(conname.clone()),
12416 Value::Text(tname.clone()),
12417 Value::Text(col_name_at(local)),
12418 Value::Int(ordinal),
12419 Value::Text(String::new()),
12420 Value::Text(String::new()),
12421 ]));
12422 }
12423 }
12424 }
12425 (schema, rows)
12426}
12427
12428fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12431 let schema = alloc::vec![
12432 ColumnSchema::new("constraint_name", DataType::Text, false),
12433 ColumnSchema::new("table_name", DataType::Text, false),
12434 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12435 ColumnSchema::new("update_rule", DataType::Text, false),
12436 ColumnSchema::new("delete_rule", DataType::Text, false),
12437 ];
12438 fn rule_name(a: spg_storage::FkAction) -> &'static str {
12439 match a {
12440 spg_storage::FkAction::Cascade => "CASCADE",
12441 spg_storage::FkAction::SetNull => "SET NULL",
12442 spg_storage::FkAction::SetDefault => "SET DEFAULT",
12443 spg_storage::FkAction::Restrict => "RESTRICT",
12444 spg_storage::FkAction::NoAction => "NO ACTION",
12445 }
12446 }
12447 let mut rows: Vec<Row> = Vec::new();
12448 for tname in cat.table_names() {
12449 let Some(t) = cat.get(&tname) else { continue };
12450 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12451 let conname = fk
12452 .name
12453 .clone()
12454 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12455 rows.push(Row::new(alloc::vec![
12456 Value::Text(conname),
12457 Value::Text(tname.clone()),
12458 Value::Text(fk.parent_table.clone()),
12459 Value::Text(rule_name(fk.on_update).into()),
12460 Value::Text(rule_name(fk.on_delete).into()),
12461 ]));
12462 }
12463 }
12464 (schema, rows)
12465}
12466
12467fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12471 let schema = alloc::vec![
12472 ColumnSchema::new("table_name", DataType::Text, false),
12473 ColumnSchema::new("index_name", DataType::Text, false),
12474 ColumnSchema::new("column_name", DataType::Text, false),
12475 ColumnSchema::new("seq_in_index", DataType::Int, false),
12476 ColumnSchema::new("non_unique", DataType::Int, false),
12477 ColumnSchema::new("index_type", DataType::Text, false),
12478 ];
12479 let mut rows: Vec<Row> = Vec::new();
12480 for tname in cat.table_names() {
12481 let Some(t) = cat.get(&tname) else { continue };
12482 for idx in t.indices() {
12483 let col = t
12484 .schema()
12485 .columns
12486 .get(idx.column_position)
12487 .map_or("?".into(), |c| c.name.clone());
12488 rows.push(Row::new(alloc::vec![
12489 Value::Text(tname.clone()),
12490 Value::Text(idx.name.clone()),
12491 Value::Text(col),
12492 Value::Int(1),
12493 Value::Int(i32::from(!idx.is_unique)),
12494 Value::Text("BTREE".into()),
12495 ]));
12496 }
12497 }
12498 (schema, rows)
12499}
12500
12501fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
12505 let schema = alloc::vec![
12506 ColumnSchema::new("routine_name", DataType::Text, false),
12507 ColumnSchema::new("routine_type", DataType::Text, false),
12508 ColumnSchema::new("data_type", DataType::Text, false),
12509 ];
12510 (schema, Vec::new())
12511}
12512
12513fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12528 let schema = alloc::vec![
12529 ColumnSchema::new("conname", DataType::Text, false),
12530 ColumnSchema::new("contype", DataType::Text, false),
12531 ColumnSchema::new("conrelid", DataType::Text, false),
12532 ColumnSchema::new("confrelid", DataType::Text, false),
12533 ColumnSchema::new("conkey", DataType::Text, false),
12534 ColumnSchema::new("confkey", DataType::Text, false),
12535 ];
12536 let mut rows: Vec<Row> = Vec::new();
12537 for tname in cat.table_names() {
12538 let Some(t) = cat.get(&tname) else { continue };
12539 let cols = &t.schema().columns;
12540 let col_name_at = |pos: usize| -> String {
12541 cols.get(pos)
12542 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12543 };
12544 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12546 let kind = if uc.is_primary_key { "p" } else { "u" };
12547 let conname = if uc.is_primary_key {
12548 alloc::format!("{}_pkey", tname)
12549 } else {
12550 alloc::format!("{}_uniq{ci}", tname)
12551 };
12552 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
12553 rows.push(Row::new(alloc::vec![
12554 Value::Text(conname),
12555 Value::Text(kind.into()),
12556 Value::Text(tname.clone()),
12557 Value::Text(String::new()),
12558 Value::Text(conkey.join(",")),
12559 Value::Text(String::new()),
12560 ]));
12561 }
12562 for idx in t.indices() {
12567 if !idx.is_unique {
12568 continue;
12569 }
12570 let is_primary = idx.name.ends_with("_pkey");
12571 let conname = idx.name.clone();
12572 let kind = if is_primary { "p" } else { "u" };
12573 let col_name = col_name_at(idx.column_position);
12574 let already = t
12577 .schema()
12578 .uniqueness_constraints
12579 .iter()
12580 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
12581 if already {
12582 continue;
12583 }
12584 rows.push(Row::new(alloc::vec![
12585 Value::Text(conname),
12586 Value::Text(kind.into()),
12587 Value::Text(tname.clone()),
12588 Value::Text(String::new()),
12589 Value::Text(col_name),
12590 Value::Text(String::new()),
12591 ]));
12592 }
12593 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12595 let conname = fk
12596 .name
12597 .clone()
12598 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12599 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
12600 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
12603 fk.parent_columns
12604 .iter()
12605 .map(|&p| {
12606 parent
12607 .schema()
12608 .columns
12609 .get(p)
12610 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
12611 })
12612 .collect()
12613 } else {
12614 fk.parent_columns
12615 .iter()
12616 .map(|p| alloc::format!("col{p}"))
12617 .collect()
12618 };
12619 rows.push(Row::new(alloc::vec![
12620 Value::Text(conname),
12621 Value::Text("f".into()),
12622 Value::Text(tname.clone()),
12623 Value::Text(fk.parent_table.clone()),
12624 Value::Text(conkey.join(",")),
12625 Value::Text(confkey.join(",")),
12626 ]));
12627 }
12628 }
12629 (schema, rows)
12630}
12631
12632fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12637 let schema = alloc::vec![
12638 ColumnSchema::new("oid", DataType::BigInt, false),
12639 ColumnSchema::new("datname", DataType::Text, false),
12640 ColumnSchema::new("datdba", DataType::BigInt, false),
12641 ColumnSchema::new("encoding", DataType::Int, false),
12642 ColumnSchema::new("datcollate", DataType::Text, false),
12643 ];
12644 let rows = alloc::vec![Row::new(alloc::vec![
12645 Value::BigInt(16384),
12646 Value::Text("postgres".into()),
12647 Value::BigInt(10),
12648 Value::Int(6), Value::Text("en_US.UTF-8".into()),
12650 ])];
12651 (schema, rows)
12652}
12653
12654fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12659 let schema = alloc::vec![
12660 ColumnSchema::new("oid", DataType::BigInt, false),
12661 ColumnSchema::new("rolname", DataType::Text, false),
12662 ColumnSchema::new("rolsuper", DataType::Bool, false),
12663 ColumnSchema::new("rolinherit", DataType::Bool, false),
12664 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
12665 ];
12666 let mut rows: Vec<Row> = Vec::new();
12667 let oid: i64 = 10;
12668 for (i, (name, _)) in engine.users.iter().enumerate() {
12669 rows.push(Row::new(alloc::vec![
12670 Value::BigInt(oid + (i as i64) + 1),
12671 Value::Text(name.to_string()),
12672 Value::Bool(false),
12673 Value::Bool(true),
12674 Value::Bool(true),
12675 ]));
12676 }
12677 if !rows
12680 .iter()
12681 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
12682 {
12683 rows.insert(
12684 0,
12685 Row::new(alloc::vec![
12686 Value::BigInt(10),
12687 Value::Text("postgres".into()),
12688 Value::Bool(true),
12689 Value::Bool(true),
12690 Value::Bool(true),
12691 ]),
12692 );
12693 }
12694 (schema, rows)
12695}
12696
12697fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
12706 let schema = alloc::vec![
12707 ColumnSchema::new("oid", DataType::BigInt, false),
12708 ColumnSchema::new("extname", DataType::Text, false),
12709 ColumnSchema::new("extversion", DataType::Text, false),
12710 ColumnSchema::new("extnamespace", DataType::Text, false),
12711 ];
12712 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
12713 let rows = exts
12714 .iter()
12715 .enumerate()
12716 .map(|(i, (name, ver))| {
12717 Row::new(alloc::vec![
12718 Value::BigInt(16384 + i as i64),
12719 Value::Text((*name).into()),
12720 Value::Text((*ver).into()),
12721 Value::Text("pg_catalog".into()),
12722 ])
12723 })
12724 .collect();
12725 (schema, rows)
12726}
12727
12728fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12729 let schema = alloc::vec![
12730 ColumnSchema::new("schemaname", DataType::Text, false),
12731 ColumnSchema::new("viewname", DataType::Text, false),
12732 ColumnSchema::new("definition", DataType::Text, false),
12733 ];
12734 let mut rows: Vec<Row> = Vec::new();
12735 for (name, def) in cat.views() {
12736 rows.push(Row::new(alloc::vec![
12737 Value::Text("public".into()),
12738 Value::Text(name.clone()),
12739 Value::Text(def.body.clone()),
12740 ]));
12741 }
12742 (schema, rows)
12743}
12744
12745fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12751 let schema = alloc::vec![
12752 ColumnSchema::new("name", DataType::Text, false),
12753 ColumnSchema::new("setting", DataType::Text, false),
12754 ColumnSchema::new("category", DataType::Text, false),
12755 ];
12756 let mut rows: Vec<Row> = Vec::new();
12757 let defaults: &[(&str, &str, &str)] = &[
12759 ("server_version", "16.0 (spg)", "Preset Options"),
12760 ("server_encoding", "UTF8", "Client Connection Defaults"),
12761 ("client_encoding", "UTF8", "Client Connection Defaults"),
12762 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
12763 ("TimeZone", "UTC", "Client Connection Defaults"),
12764 ("standard_conforming_strings", "on", "Compatibility"),
12765 ("integer_datetimes", "on", "Compatibility"),
12766 ("max_connections", "100", "Connections and Authentication"),
12767 ];
12768 for &(name, val, cat) in defaults {
12769 rows.push(Row::new(alloc::vec![
12770 Value::Text(name.into()),
12771 Value::Text(val.into()),
12772 Value::Text(cat.into()),
12773 ]));
12774 }
12775 for (k, v) in &engine.session_params {
12777 if !defaults
12778 .iter()
12779 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
12780 {
12781 rows.push(Row::new(alloc::vec![
12782 Value::Text(k.clone()),
12783 Value::Text(v.clone()),
12784 Value::Text("Session".into()),
12785 ]));
12786 }
12787 }
12788 (schema, rows)
12789}
12790
12791fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12802 let schema = alloc::vec![
12803 ColumnSchema::new("schemaname", DataType::Text, false),
12804 ColumnSchema::new("tablename", DataType::Text, false),
12805 ColumnSchema::new("indexname", DataType::Text, false),
12806 ColumnSchema::new("indexdef", DataType::Text, false),
12807 ];
12808 let mut rows: Vec<Row> = Vec::new();
12809 for tname in cat.table_names() {
12810 let Some(t) = cat.get(&tname) else { continue };
12811 for idx in t.indices() {
12812 let col_name = t
12813 .schema()
12814 .columns
12815 .get(idx.column_position)
12816 .map_or("?".into(), |c| c.name.clone());
12817 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
12818 let indexdef = alloc::format!(
12819 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
12820 idx.name,
12821 tname,
12822 col_name
12823 );
12824 rows.push(Row::new(alloc::vec![
12825 Value::Text("public".into()),
12826 Value::Text(tname.clone()),
12827 Value::Text(idx.name.clone()),
12828 Value::Text(indexdef),
12829 ]));
12830 }
12831 }
12832 (schema, rows)
12833}
12834
12835fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12847 let schema = alloc::vec![
12848 ColumnSchema::new("indexrelid", DataType::BigInt, false),
12849 ColumnSchema::new("indrelid", DataType::BigInt, false),
12850 ColumnSchema::new("indnatts", DataType::Int, false),
12851 ColumnSchema::new("indisunique", DataType::Bool, false),
12852 ColumnSchema::new("indisprimary", DataType::Bool, false),
12853 ];
12854 let mut rows: Vec<Row> = Vec::new();
12855 let mut idx_oid: i64 = 100_000;
12856 for (table_idx, tname) in cat.table_names().iter().enumerate() {
12857 let Some(t) = cat.get(tname) else { continue };
12858 for idx in t.indices() {
12859 idx_oid += 1;
12860 #[allow(clippy::cast_possible_wrap)]
12861 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
12862 let is_primary = idx.name.ends_with("_pkey");
12865 rows.push(Row::new(alloc::vec![
12866 Value::BigInt(idx_oid),
12867 Value::BigInt((table_idx + 1) as i64),
12868 Value::Int(nattrs),
12869 Value::Bool(idx.is_unique),
12870 Value::Bool(is_primary),
12871 ]));
12872 }
12873 }
12874 (schema, rows)
12875}
12876
12877fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12882 let schema = alloc::vec![
12883 ColumnSchema::new("oid", DataType::BigInt, false),
12884 ColumnSchema::new("nspname", DataType::Text, false),
12885 ColumnSchema::new("nspowner", DataType::BigInt, false),
12886 ];
12887 let rows = alloc::vec![
12888 Row::new(alloc::vec![
12889 Value::BigInt(11),
12890 Value::Text("pg_catalog".into()),
12891 Value::BigInt(10),
12892 ]),
12893 Row::new(alloc::vec![
12894 Value::BigInt(2200),
12895 Value::Text("public".into()),
12896 Value::BigInt(10),
12897 ]),
12898 Row::new(alloc::vec![
12899 Value::BigInt(13000),
12900 Value::Text("information_schema".into()),
12901 Value::BigInt(10),
12902 ]),
12903 ];
12904 (schema, rows)
12905}
12906
12907fn materialise_meta_view(
12910 catalog: &mut Catalog,
12911 name: &str,
12912 columns: Vec<ColumnSchema>,
12913 rows: Vec<Row>,
12914) -> Result<(), EngineError> {
12915 let schema = TableSchema::new(name.to_string(), columns);
12916 catalog.create_table(schema).map_err(EngineError::Storage)?;
12917 let table = catalog
12918 .get_mut(name)
12919 .expect("just-created meta view must exist");
12920 for row in rows {
12921 table.insert(row).map_err(EngineError::Storage)?;
12922 }
12923 Ok(())
12924}
12925
12926fn collect_view_refs(
12939 tref: &spg_sql::ast::TableRef,
12940 cat: &spg_storage::Catalog,
12941 into: &mut Vec<String>,
12942) {
12943 if cat.views().contains_key(&tref.name)
12944 && cat.get(&tref.name).is_none()
12945 && !into.iter().any(|n| n == &tref.name)
12946 {
12947 into.push(tref.name.clone());
12948 }
12949}
12950
12951fn select_references_meta_view(stmt: &SelectStatement) -> bool {
12952 fn is_meta(name: &str) -> bool {
12953 name.starts_with("__spg_info_")
12954 || name.starts_with("__spg_pg_")
12955 || name.starts_with("__spg_mysql_")
12956 }
12957 if let Some(from) = &stmt.from {
12958 if is_meta(&from.primary.name) {
12959 return true;
12960 }
12961 for j in &from.joins {
12962 if is_meta(&j.table.name) {
12963 return true;
12964 }
12965 }
12966 }
12967 for cte in &stmt.ctes {
12968 if select_references_meta_view(&cte.body) {
12969 return true;
12970 }
12971 }
12972 false
12973}
12974
12975fn collect_meta_view_names(
12980 stmt: &SelectStatement,
12981 into: &mut alloc::collections::BTreeSet<String>,
12982) {
12983 fn is_meta(name: &str) -> bool {
12984 name.starts_with("__spg_info_")
12985 || name.starts_with("__spg_pg_")
12986 || name.starts_with("__spg_mysql_")
12987 }
12988 if let Some(from) = &stmt.from {
12989 if is_meta(&from.primary.name) {
12990 into.insert(from.primary.name.clone());
12991 }
12992 for j in &from.joins {
12993 if is_meta(&j.table.name) {
12994 into.insert(j.table.name.clone());
12995 }
12996 }
12997 }
12998 for cte in &stmt.ctes {
12999 collect_meta_view_names(&cte.body, into);
13000 }
13001}
13002
13003fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
13004 let mut out = columns.to_vec();
13005 for (col_idx, col) in out.iter_mut().enumerate() {
13006 if col.ty != DataType::Text {
13007 continue;
13008 }
13009 let mut inferred: Option<DataType> = None;
13010 let mut all_null = true;
13011 for row in rows {
13012 let Some(v) = row.values.get(col_idx) else {
13013 continue;
13014 };
13015 let ty = match v {
13016 Value::Null => continue,
13017 Value::SmallInt(_) => DataType::SmallInt,
13018 Value::Int(_) => DataType::Int,
13019 Value::BigInt(_) => DataType::BigInt,
13020 Value::Float(_) => DataType::Float,
13021 Value::Bool(_) => DataType::Bool,
13022 Value::Vector(_) => DataType::Vector {
13023 dim: 0,
13024 encoding: VecEncoding::F32,
13025 },
13026 _ => DataType::Text,
13027 };
13028 all_null = false;
13029 inferred = Some(match inferred {
13030 None => ty,
13031 Some(prev) if prev == ty => prev,
13032 Some(_) => DataType::Text,
13033 });
13034 }
13035 if let Some(t) = inferred {
13036 col.ty = t;
13037 col.nullable = true;
13038 } else if all_null {
13039 col.nullable = true;
13040 }
13041 }
13042 out
13043}
13044
13045#[allow(clippy::too_many_lines, clippy::format_push_string)]
13050fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
13067 use alloc::collections::BTreeSet;
13068 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
13069 let mut out: Vec<String> = Vec::new();
13070 let cat = engine.active_catalog();
13071 let Some(from) = &stmt.from else {
13075 return out;
13076 };
13077 let mut tables: Vec<String> = Vec::new();
13078 tables.push(from.primary.name.clone());
13079 for j in &from.joins {
13080 tables.push(j.table.name.clone());
13081 }
13082 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
13085 if let Some(w) = &stmt.where_ {
13086 collect_column_refs(w, &mut col_refs);
13087 }
13088 for j in &from.joins {
13089 if let Some(on) = &j.on {
13090 collect_column_refs(on, &mut col_refs);
13091 }
13092 }
13093 for cn in &col_refs {
13094 let owner: Option<String> = if let Some(q) = &cn.qualifier {
13097 tables.iter().find(|t| t == &q).cloned()
13098 } else {
13099 tables.iter().find_map(|t| {
13100 cat.get(t).and_then(|tbl| {
13101 if tbl.schema().column_position(&cn.name).is_some() {
13102 Some(t.clone())
13103 } else {
13104 None
13105 }
13106 })
13107 })
13108 };
13109 let Some(owner) = owner else {
13110 continue;
13111 };
13112 let Some(tbl) = cat.get(&owner) else {
13113 continue;
13114 };
13115 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
13116 continue;
13117 };
13118 let already_indexed = tbl.indices().iter().any(|i| {
13121 matches!(i.kind, spg_storage::IndexKind::BTree(_))
13122 && i.column_position == col_pos
13123 && i.expression.is_none()
13124 && i.partial_predicate.is_none()
13125 });
13126 if already_indexed {
13127 continue;
13128 }
13129 if seen.insert((owner.clone(), cn.name.clone())) {
13130 out.push(alloc::format!(
13131 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
13132 owner,
13133 cn.name,
13134 owner,
13135 cn.name
13136 ));
13137 }
13138 }
13139 out
13140}
13141
13142fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
13145 match expr {
13146 Expr::Column(cn) => out.push(cn.clone()),
13147 Expr::FunctionCall { args, .. } => {
13148 for a in args {
13149 collect_column_refs(a, out);
13150 }
13151 }
13152 Expr::Binary { lhs, rhs, .. } => {
13153 collect_column_refs(lhs, out);
13154 collect_column_refs(rhs, out);
13155 }
13156 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
13157 _ => {}
13158 }
13159}
13160
13161fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
13162 let catalog = engine.active_catalog();
13163 let cold_ids = catalog.cold_segment_ids_global();
13164 let any_cold = !cold_ids.is_empty();
13165 let cold_ids_repr = if any_cold {
13166 let mut s = alloc::string::String::from("[");
13167 for (i, id) in cold_ids.iter().enumerate() {
13168 if i > 0 {
13169 s.push(',');
13170 }
13171 s.push_str(&alloc::format!("{id}"));
13172 }
13173 s.push(']');
13174 s
13175 } else {
13176 alloc::string::String::new()
13177 };
13178 for (idx, line) in lines.iter_mut().enumerate() {
13179 let trimmed = line.trim_start();
13180 let is_top_level = idx == 0;
13181 if is_top_level {
13182 line.push_str(&alloc::format!(" (rows={total_rows})"));
13183 continue;
13184 }
13185 if let Some(rest) = trimmed.strip_prefix("From: ") {
13186 let (name, scan_kind) = match rest.split_once(" [") {
13187 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
13188 None => (rest.trim(), ""),
13189 };
13190 let bare = name.split_whitespace().next().unwrap_or(name);
13191 let hot = catalog.get(bare).map(|t| t.rows().len());
13192 let annot = match (hot, scan_kind) {
13197 (Some(h), "full scan") => {
13198 let mut s = alloc::format!(" (hot_rows={h}");
13199 if any_cold {
13200 s.push_str(&alloc::format!(
13201 ", cold_tier=present, cold_segments={cold_ids_repr}"
13202 ));
13203 }
13204 s.push(')');
13205 s
13206 }
13207 (Some(h), "index seek") => {
13208 let mut s = alloc::format!(" (hot_rows≤{h}");
13209 if any_cold {
13210 s.push_str(&alloc::format!(
13211 ", cold_tier=present, cold_segments={cold_ids_repr}"
13212 ));
13213 }
13214 s.push(')');
13215 s
13216 }
13217 _ => " (rows=—)".to_string(),
13218 };
13219 line.push_str(&annot);
13220 continue;
13221 }
13222 line.push_str(" (rows=—)");
13224 }
13225}
13226
13227fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
13228 let pad = " ".repeat(depth);
13229 let top = if !stmt.ctes.is_empty() {
13231 if stmt.ctes.iter().any(|c| c.recursive) {
13232 "CTEScan (WITH RECURSIVE)"
13233 } else {
13234 "CTEScan (WITH)"
13235 }
13236 } else if !stmt.unions.is_empty() {
13237 "UnionScan"
13238 } else if select_has_window(stmt) {
13239 "WindowAgg"
13240 } else if aggregate::uses_aggregate(stmt) {
13241 "Aggregate"
13242 } else if stmt.distinct {
13243 "Distinct"
13244 } else if stmt.from.is_some() {
13245 "TableScan"
13246 } else {
13247 "Result"
13248 };
13249 out.push(alloc::format!("{pad}{top}"));
13250 let child = " ".repeat(depth + 1);
13251 for cte in &stmt.ctes {
13253 let head = if cte.recursive {
13254 alloc::format!("{child}CTE (recursive): {}", cte.name)
13255 } else {
13256 alloc::format!("{child}CTE: {}", cte.name)
13257 };
13258 out.push(head);
13259 explain_select(&cte.body, engine, depth + 2, out);
13260 }
13261 if let Some(from) = &stmt.from {
13263 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
13264 if let Some(alias) = &from.primary.alias {
13265 tag.push_str(&alloc::format!(" AS {alias}"));
13266 }
13267 if let Some(w) = &stmt.where_
13270 && let Some(table) = engine.active_catalog().get(&from.primary.name)
13271 {
13272 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
13273 let cols = &table.schema().columns;
13274 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
13275 tag.push_str(" [index seek]");
13276 } else {
13277 tag.push_str(" [full scan]");
13278 }
13279 } else {
13280 tag.push_str(" [full scan]");
13281 }
13282 out.push(tag);
13283 for j in &from.joins {
13284 let kind = match j.kind {
13285 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
13286 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
13287 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
13288 };
13289 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
13290 if let Some(alias) = &j.table.alias {
13291 s.push_str(&alloc::format!(" AS {alias}"));
13292 }
13293 if j.on.is_some() {
13294 s.push_str(" (ON …)");
13295 }
13296 out.push(s);
13297 }
13298 }
13299 if let Some(w) = &stmt.where_ {
13301 let mut s = alloc::format!("{child}Filter: {w}");
13302 if expr_has_subquery(w) {
13303 s.push_str(" [subquery]");
13304 }
13305 out.push(s);
13306 }
13307 if let Some(gs) = &stmt.group_by {
13308 let mut parts = Vec::new();
13309 for g in gs {
13310 parts.push(alloc::format!("{g}"));
13311 }
13312 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
13313 }
13314 if let Some(h) = &stmt.having {
13315 out.push(alloc::format!("{child}Having: {h}"));
13316 }
13317 for o in &stmt.order_by {
13318 let dir = if o.desc { "DESC" } else { "ASC" };
13319 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
13320 }
13321 if let Some(lim) = stmt.limit {
13322 out.push(alloc::format!("{child}Limit: {lim}"));
13323 }
13324 if let Some(off) = stmt.offset {
13325 out.push(alloc::format!("{child}Offset: {off}"));
13326 }
13327 if stmt
13329 .items
13330 .iter()
13331 .any(|it| matches!(it, SelectItem::Wildcard))
13332 {
13333 out.push(alloc::format!("{child}Project: *"));
13334 } else {
13335 out.push(alloc::format!(
13336 "{child}Project: {} item(s)",
13337 stmt.items.len()
13338 ));
13339 }
13340 for (kind, peer) in &stmt.unions {
13342 let label = match kind {
13343 UnionKind::All => "UNION ALL",
13344 UnionKind::Distinct => "UNION",
13345 };
13346 out.push(alloc::format!("{child}{label}"));
13347 explain_select(peer, engine, depth + 2, out);
13348 }
13349}
13350
13351fn is_correlation_error(e: &EngineError) -> bool {
13356 matches!(
13357 e,
13358 EngineError::Eval(
13359 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
13360 )
13361 )
13362}
13363
13364struct JoinedPeer<'a> {
13375 eager_rows: Option<Vec<Row>>,
13376 cols: Vec<ColumnSchema>,
13377 alias: String,
13378 kind: JoinKind,
13379 on: Option<&'a Expr>,
13380 lateral: Option<&'a SelectStatement>,
13381 join_table: Option<String>,
13384}
13385
13386fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
13393 match expr {
13394 Expr::Column(c) => c.name.clone(),
13396 Expr::FunctionCall { name, .. } => name.clone(),
13399 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
13401 _ => alloc::format!("column{}", idx + 1),
13403 }
13404}
13405
13406fn substitute_outer_columns_multi(
13413 stmt: &mut SelectStatement,
13414 outer_row: &Row,
13415 outer_schema: &[ColumnSchema],
13416) {
13417 substitute_outer_in_select(stmt, outer_row, outer_schema);
13418}
13419
13420fn substitute_outer_in_select(
13421 stmt: &mut SelectStatement,
13422 outer_row: &Row,
13423 outer_schema: &[ColumnSchema],
13424) {
13425 for item in &mut stmt.items {
13426 if let SelectItem::Expr { expr, .. } = item {
13427 substitute_outer_in_expr(expr, outer_row, outer_schema);
13428 }
13429 }
13430 if let Some(w) = &mut stmt.where_ {
13431 substitute_outer_in_expr(w, outer_row, outer_schema);
13432 }
13433 if let Some(gs) = &mut stmt.group_by {
13434 for g in gs {
13435 substitute_outer_in_expr(g, outer_row, outer_schema);
13436 }
13437 }
13438 if let Some(h) = &mut stmt.having {
13439 substitute_outer_in_expr(h, outer_row, outer_schema);
13440 }
13441 for o in &mut stmt.order_by {
13442 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
13443 }
13444 for (_, peer) in &mut stmt.unions {
13445 substitute_outer_in_select(peer, outer_row, outer_schema);
13446 }
13447}
13448
13449fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
13450 if let Expr::Column(c) = e
13451 && let Some(qual) = &c.qualifier
13452 {
13453 let composite = alloc::format!("{qual}.{}", c.name);
13454 if let Some(idx) = outer_schema
13455 .iter()
13456 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13457 {
13458 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
13459 if let Ok(lit) = value_to_literal_expr(v) {
13460 *e = lit;
13461 return;
13462 }
13463 }
13464 }
13465 match e {
13466 Expr::Binary { lhs, rhs, .. } => {
13467 substitute_outer_in_expr(lhs, outer_row, outer_schema);
13468 substitute_outer_in_expr(rhs, outer_row, outer_schema);
13469 }
13470 Expr::Unary { expr: inner, .. } => {
13471 substitute_outer_in_expr(inner, outer_row, outer_schema);
13472 }
13473 Expr::FunctionCall { args, .. } => {
13474 for a in args {
13475 substitute_outer_in_expr(a, outer_row, outer_schema);
13476 }
13477 }
13478 Expr::Cast { expr: inner, .. } => {
13479 substitute_outer_in_expr(inner, outer_row, outer_schema);
13480 }
13481 Expr::Case {
13482 operand,
13483 branches,
13484 else_branch,
13485 } => {
13486 if let Some(op) = operand {
13487 substitute_outer_in_expr(op, outer_row, outer_schema);
13488 }
13489 for (cond, val) in branches {
13490 substitute_outer_in_expr(cond, outer_row, outer_schema);
13491 substitute_outer_in_expr(val, outer_row, outer_schema);
13492 }
13493 if let Some(e) = else_branch {
13494 substitute_outer_in_expr(e, outer_row, outer_schema);
13495 }
13496 }
13497 _ => {}
13498 }
13499}
13500
13501impl Engine {
13502 fn try_batch_correlated_scalar(
13511 &self,
13512 inner: &SelectStatement,
13513 cancel: CancelToken<'_>,
13514 ) -> Result<Option<memoize::GroupMap>, EngineError> {
13515 use spg_sql::ast::{BinOp, SelectItem as SI};
13516 if !inner.ctes.is_empty()
13517 || !inner.unions.is_empty()
13518 || inner.group_by.is_some()
13519 || inner.having.is_some()
13520 || inner.distinct
13521 || inner.items.len() != 1
13522 || inner.order_by.len() > 1
13523 || inner.offset.is_some()
13524 {
13525 return Ok(None);
13526 }
13527 if let Some(le) = inner.limit
13529 && le.as_literal() != Some(1)
13530 {
13531 return Ok(None);
13532 }
13533 let Some(from) = &inner.from else {
13534 return Ok(None);
13535 };
13536 if from.primary.lateral_subquery.is_some() || from.primary.unnest_expr.is_some() {
13537 return Ok(None);
13538 }
13539 let mut inner_aliases: Vec<String> = Vec::new();
13541 inner_aliases.push(
13542 from.primary
13543 .alias
13544 .clone()
13545 .unwrap_or_else(|| from.primary.name.clone()),
13546 );
13547 for j in &from.joins {
13548 if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
13549 return Ok(None);
13550 }
13551 inner_aliases.push(
13552 j.table
13553 .alias
13554 .clone()
13555 .unwrap_or_else(|| j.table.name.clone()),
13556 );
13557 }
13558 let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
13559 match &c.qualifier {
13560 Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
13561 None => false,
13562 }
13563 };
13564 let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
13565 match &c.qualifier {
13566 Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
13567 None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
13570 }
13571 };
13572 let all_inner = |e: &Expr| -> bool {
13575 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
13576 let mut subs: Vec<&SelectStatement> = Vec::new();
13577 visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
13578 subs.push(sub)
13579 });
13580 subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
13581 };
13582 let Some(w) = &inner.where_ else {
13583 return Ok(None);
13584 };
13585 let conjuncts = reorder::split_and_conjunctions(w);
13586 let mut corr: Option<(spg_sql::ast::ColumnName, spg_sql::ast::ColumnName)> = None; let mut rest: Vec<&Expr> = Vec::new();
13588 for c in conjuncts {
13589 if let Expr::Binary {
13590 lhs,
13591 op: BinOp::Eq,
13592 rhs,
13593 } = c
13594 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
13595 {
13596 let pair = if is_inner(a) && is_outer(b) {
13597 Some((a.clone(), b.clone()))
13598 } else if is_inner(b) && is_outer(a) {
13599 Some((b.clone(), a.clone()))
13600 } else {
13601 None
13602 };
13603 if let Some(p) = pair {
13604 if corr.is_some() {
13605 return Ok(None); }
13607 corr = Some(p);
13608 continue;
13609 }
13610 }
13611 if !all_inner(c) {
13612 return Ok(None);
13613 }
13614 rest.push(c);
13615 }
13616 let Some((inner_col, outer_col)) = corr else {
13617 return Ok(None);
13618 };
13619 let SI::Expr { expr: out_expr, .. } = &inner.items[0] else {
13620 return Ok(None);
13621 };
13622 if !all_inner(out_expr) {
13623 return Ok(None);
13624 }
13625 let order = inner.order_by.first();
13626 if let Some(o) = order
13627 && !all_inner(&o.expr)
13628 {
13629 return Ok(None);
13630 }
13631 let mut batch = inner.clone();
13634 batch.limit = None;
13635 batch.offset = None;
13636 batch.order_by = Vec::new();
13637 batch.where_ = rest
13638 .iter()
13639 .map(|e| (*e).clone())
13640 .reduce(|a, b| Expr::Binary {
13641 lhs: alloc::boxed::Box::new(a),
13642 op: BinOp::And,
13643 rhs: alloc::boxed::Box::new(b),
13644 });
13645 let mut items: Vec<SI> = alloc::vec![SI::Expr {
13646 expr: Expr::Column(inner_col),
13647 alias: None,
13648 }];
13649 if let Some(o) = order {
13650 items.push(SI::Expr {
13651 expr: o.expr.clone(),
13652 alias: None,
13653 });
13654 }
13655 items.push(SI::Expr {
13656 expr: out_expr.clone(),
13657 alias: None,
13658 });
13659 batch.items = items;
13660 let r = self.exec_select_cancel(&batch, cancel)?;
13661 let QueryResult::Rows { rows, .. } = r else {
13662 return Ok(None);
13663 };
13664 let has_order = order.is_some();
13665 let (desc, nf) = order
13666 .map(|o| (o.desc, o.nulls_first))
13667 .unwrap_or((false, None));
13668 let mut best: alloc::collections::BTreeMap<String, (Option<Value>, Value)> =
13669 alloc::collections::BTreeMap::new();
13670 for row in rows {
13671 let key_v = row.values.first().cloned().unwrap_or(Value::Null);
13672 if matches!(key_v, Value::Null) {
13673 continue;
13674 }
13675 let key = aggregate::encode_key(core::slice::from_ref(&key_v));
13676 let (ord_v, out_v) = if has_order {
13677 (
13678 Some(row.values.get(1).cloned().unwrap_or(Value::Null)),
13679 row.values.get(2).cloned().unwrap_or(Value::Null),
13680 )
13681 } else {
13682 (None, row.values.get(1).cloned().unwrap_or(Value::Null))
13683 };
13684 match best.get(&key) {
13685 None => {
13686 best.insert(key, (ord_v, out_v));
13687 }
13688 Some((cur_ord, _)) if has_order => {
13689 let cand = ord_v.clone().unwrap_or(Value::Null);
13693 let cur = cur_ord.clone().unwrap_or(Value::Null);
13694 if order_by_value_cmp(desc, nf, &cand, &cur) == core::cmp::Ordering::Less {
13695 best.insert(key, (ord_v, out_v));
13696 }
13697 }
13698 Some(_) => {} }
13700 }
13701 let map = best.into_iter().map(|(k, (_, v))| (k, v)).collect();
13702 Ok(Some((outer_col, map)))
13703 }
13704}
13705
13706fn collect_scalar_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
13710 match e {
13711 Expr::ScalarSubquery(s) => out.push(s),
13712 Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13713 Expr::Binary { lhs, rhs, .. } => {
13714 collect_scalar_subqueries(lhs, out);
13715 collect_scalar_subqueries(rhs, out);
13716 }
13717 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13718 collect_scalar_subqueries(expr, out);
13719 }
13720 Expr::Like { expr, pattern, .. } => {
13721 collect_scalar_subqueries(expr, out);
13722 collect_scalar_subqueries(pattern, out);
13723 }
13724 Expr::FunctionCall { args, .. } => {
13725 for a in args {
13726 collect_scalar_subqueries(a, out);
13727 }
13728 }
13729 Expr::AggregateOrdered { call, order_by, .. } => {
13730 collect_scalar_subqueries(call, out);
13731 for o in order_by {
13732 collect_scalar_subqueries(&o.expr, out);
13733 }
13734 }
13735 Expr::Case {
13736 operand,
13737 branches,
13738 else_branch,
13739 } => {
13740 if let Some(op) = operand {
13741 collect_scalar_subqueries(op, out);
13742 }
13743 for (w, t) in branches {
13744 collect_scalar_subqueries(w, out);
13745 collect_scalar_subqueries(t, out);
13746 }
13747 if let Some(eb) = else_branch {
13748 collect_scalar_subqueries(eb, out);
13749 }
13750 }
13751 Expr::ArraySubscript { target, index } => {
13752 collect_scalar_subqueries(target, out);
13753 collect_scalar_subqueries(index, out);
13754 }
13755 _ => {}
13756 }
13757}
13758
13759fn hollow_scalar_subqueries(e: &mut Expr) {
13762 match e {
13763 Expr::ScalarSubquery(s) => {
13764 let hollow = SelectStatement {
13765 items: Vec::new(),
13766 ..SelectStatement::default()
13767 };
13768 **s = hollow;
13769 }
13770 Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13771 Expr::Binary { lhs, rhs, .. } => {
13772 hollow_scalar_subqueries(lhs);
13773 hollow_scalar_subqueries(rhs);
13774 }
13775 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13776 hollow_scalar_subqueries(expr);
13777 }
13778 Expr::Like { expr, pattern, .. } => {
13779 hollow_scalar_subqueries(expr);
13780 hollow_scalar_subqueries(pattern);
13781 }
13782 Expr::FunctionCall { args, .. } => {
13783 for a in args.iter_mut() {
13784 hollow_scalar_subqueries(a);
13785 }
13786 }
13787 Expr::AggregateOrdered { call, order_by, .. } => {
13788 hollow_scalar_subqueries(call);
13789 for o in order_by.iter_mut() {
13790 hollow_scalar_subqueries(&mut o.expr);
13791 }
13792 }
13793 Expr::Case {
13794 operand,
13795 branches,
13796 else_branch,
13797 } => {
13798 if let Some(op) = operand {
13799 hollow_scalar_subqueries(op);
13800 }
13801 for (w, t) in branches.iter_mut() {
13802 hollow_scalar_subqueries(w);
13803 hollow_scalar_subqueries(t);
13804 }
13805 if let Some(eb) = else_branch {
13806 hollow_scalar_subqueries(eb);
13807 }
13808 }
13809 Expr::ArraySubscript { target, index } => {
13810 hollow_scalar_subqueries(target);
13811 hollow_scalar_subqueries(index);
13812 }
13813 _ => {}
13814 }
13815}
13816
13817fn splice_planned_subqueries(
13822 e: &mut Expr,
13823 plan: &[Option<alloc::rc::Rc<memoize::GroupMap>>],
13824 idx: &mut usize,
13825 row: &Row,
13826 ctx: &EvalContext<'_>,
13827) -> Result<bool, EngineError> {
13828 match e {
13829 Expr::ScalarSubquery(_) => {
13830 let Some(Some(gm)) = plan.get(*idx) else {
13831 return Ok(false);
13832 };
13833 *idx += 1;
13834 let (outer_col, map) = gm.as_ref();
13835 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
13836 .map_err(EngineError::Eval)?;
13837 let v = if matches!(key_v, Value::Null) {
13838 Value::Null
13839 } else {
13840 map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
13841 .cloned()
13842 .unwrap_or(Value::Null)
13843 };
13844 *e = value_to_literal_expr(v)?;
13845 Ok(true)
13846 }
13847 Expr::Exists { .. } | Expr::InSubquery { .. } => Ok(true),
13848 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_subqueries(lhs, plan, idx, row, ctx)?
13849 && splice_planned_subqueries(rhs, plan, idx, row, ctx)?),
13850 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13851 splice_planned_subqueries(expr, plan, idx, row, ctx)
13852 }
13853 Expr::Like { expr, pattern, .. } => {
13854 Ok(splice_planned_subqueries(expr, plan, idx, row, ctx)?
13855 && splice_planned_subqueries(pattern, plan, idx, row, ctx)?)
13856 }
13857 Expr::FunctionCall { args, .. } => {
13858 for a in args.iter_mut() {
13859 if !splice_planned_subqueries(a, plan, idx, row, ctx)? {
13860 return Ok(false);
13861 }
13862 }
13863 Ok(true)
13864 }
13865 Expr::AggregateOrdered { call, order_by, .. } => {
13866 if !splice_planned_subqueries(call, plan, idx, row, ctx)? {
13867 return Ok(false);
13868 }
13869 for o in order_by.iter_mut() {
13870 if !splice_planned_subqueries(&mut o.expr, plan, idx, row, ctx)? {
13871 return Ok(false);
13872 }
13873 }
13874 Ok(true)
13875 }
13876 Expr::Case {
13877 operand,
13878 branches,
13879 else_branch,
13880 } => {
13881 if let Some(op) = operand {
13882 if !splice_planned_subqueries(op, plan, idx, row, ctx)? {
13883 return Ok(false);
13884 }
13885 }
13886 for (w, t) in branches.iter_mut() {
13887 if !splice_planned_subqueries(w, plan, idx, row, ctx)?
13888 || !splice_planned_subqueries(t, plan, idx, row, ctx)?
13889 {
13890 return Ok(false);
13891 }
13892 }
13893 if let Some(eb) = else_branch {
13894 if !splice_planned_subqueries(eb, plan, idx, row, ctx)? {
13895 return Ok(false);
13896 }
13897 }
13898 Ok(true)
13899 }
13900 Expr::ArraySubscript { target, index } => {
13901 Ok(splice_planned_subqueries(target, plan, idx, row, ctx)?
13902 && splice_planned_subqueries(index, plan, idx, row, ctx)?)
13903 }
13904 _ => Ok(true),
13905 }
13906}
13907
13908fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
13909 let outer_alias = ctx.table_alias.unwrap_or("");
13916 substitute_in_select(stmt, row, ctx, outer_alias);
13917}
13918
13919fn substitute_in_select(
13920 stmt: &mut SelectStatement,
13921 row: &Row,
13922 ctx: &EvalContext<'_>,
13923 outer_alias: &str,
13924) {
13925 for item in &mut stmt.items {
13926 if let SelectItem::Expr { expr, .. } = item {
13927 substitute_in_expr(expr, row, ctx, outer_alias);
13928 }
13929 }
13930 if let Some(w) = &mut stmt.where_ {
13931 substitute_in_expr(w, row, ctx, outer_alias);
13932 }
13933 if let Some(gs) = &mut stmt.group_by {
13934 for g in gs {
13935 substitute_in_expr(g, row, ctx, outer_alias);
13936 }
13937 }
13938 if let Some(h) = &mut stmt.having {
13939 substitute_in_expr(h, row, ctx, outer_alias);
13940 }
13941 for o in &mut stmt.order_by {
13942 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
13943 }
13944 for (_, peer) in &mut stmt.unions {
13945 substitute_in_select(peer, row, ctx, outer_alias);
13946 }
13947}
13948
13949fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
13950 if let Expr::Column(c) = e
13956 && c.qualifier.is_none()
13957 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
13958 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
13959 {
13960 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
13961 if let Ok(lit) = value_to_literal_expr(v) {
13962 *e = lit;
13963 return;
13964 }
13965 }
13966 if let Expr::Column(c) = e
13967 && let Some(qual) = &c.qualifier
13968 {
13969 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
13973 ctx.columns
13974 .iter()
13975 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
13976 } else {
13977 None
13978 }
13979 .or_else(|| {
13980 let composite = alloc::format!("{qual}.{name}", name = c.name);
13981 ctx.columns
13982 .iter()
13983 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13984 });
13985 if let Some(idx) = idx {
13986 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
13987 if let Ok(lit) = value_to_literal_expr(v) {
13988 *e = lit;
13989 return;
13990 }
13991 }
13992 }
13993 match e {
13994 Expr::AggregateOrdered { call, order_by, .. } => {
13995 substitute_in_expr(call, row, ctx, outer_alias);
13996 for o in order_by.iter_mut() {
13997 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
13998 }
13999 }
14000 Expr::Binary { lhs, rhs, .. } => {
14001 substitute_in_expr(lhs, row, ctx, outer_alias);
14002 substitute_in_expr(rhs, row, ctx, outer_alias);
14003 }
14004 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14005 substitute_in_expr(expr, row, ctx, outer_alias);
14006 }
14007 Expr::Like { expr, pattern, .. } => {
14008 substitute_in_expr(expr, row, ctx, outer_alias);
14009 substitute_in_expr(pattern, row, ctx, outer_alias);
14010 }
14011 Expr::FunctionCall { args, .. } => {
14012 for a in args {
14013 substitute_in_expr(a, row, ctx, outer_alias);
14014 }
14015 }
14016 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
14017 Expr::WindowFunction {
14018 args,
14019 partition_by,
14020 order_by,
14021 ..
14022 } => {
14023 for a in args {
14024 substitute_in_expr(a, row, ctx, outer_alias);
14025 }
14026 for p in partition_by {
14027 substitute_in_expr(p, row, ctx, outer_alias);
14028 }
14029 for (o, _, _) in order_by {
14030 substitute_in_expr(o, row, ctx, outer_alias);
14031 }
14032 }
14033 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
14034 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
14035 substitute_in_select(subquery, row, ctx, outer_alias);
14036 }
14037 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14038 Expr::Array(items) => {
14039 for elem in items {
14040 substitute_in_expr(elem, row, ctx, outer_alias);
14041 }
14042 }
14043 Expr::ArraySubscript { target, index } => {
14044 substitute_in_expr(target, row, ctx, outer_alias);
14045 substitute_in_expr(index, row, ctx, outer_alias);
14046 }
14047 Expr::AnyAll { expr, array, .. } => {
14048 substitute_in_expr(expr, row, ctx, outer_alias);
14049 substitute_in_expr(array, row, ctx, outer_alias);
14050 }
14051 Expr::Case {
14052 operand,
14053 branches,
14054 else_branch,
14055 } => {
14056 if let Some(o) = operand {
14057 substitute_in_expr(o, row, ctx, outer_alias);
14058 }
14059 for (w, t) in branches {
14060 substitute_in_expr(w, row, ctx, outer_alias);
14061 substitute_in_expr(t, row, ctx, outer_alias);
14062 }
14063 if let Some(e) = else_branch {
14064 substitute_in_expr(e, row, ctx, outer_alias);
14065 }
14066 }
14067 }
14068}
14069
14070fn encode_row_key(row: &Row) -> Vec<u8> {
14074 let mut out = Vec::new();
14075 for v in &row.values {
14076 let s = alloc::format!("{v:?}|");
14077 out.extend_from_slice(s.as_bytes());
14078 }
14079 out
14080}
14081
14082fn select_has_window(stmt: &SelectStatement) -> bool {
14083 for item in &stmt.items {
14084 if let SelectItem::Expr { expr, .. } = item
14085 && expr_has_window(expr)
14086 {
14087 return true;
14088 }
14089 }
14090 false
14091}
14092
14093fn expr_has_window(e: &Expr) -> bool {
14094 match e {
14095 Expr::WindowFunction { .. } => true,
14096 Expr::AggregateOrdered { call, order_by, .. } => {
14097 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
14098 }
14099 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
14100 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14101 expr_has_window(expr)
14102 }
14103 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
14104 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
14105 Expr::Extract { source, .. } => expr_has_window(source),
14106 Expr::ScalarSubquery(_)
14107 | Expr::Exists { .. }
14108 | Expr::InSubquery { .. }
14109 | Expr::Literal(_)
14110 | Expr::Placeholder(_)
14111 | Expr::Column(_) => false,
14112 Expr::Array(items) => items.iter().any(expr_has_window),
14113 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
14114 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
14115 Expr::Case {
14116 operand,
14117 branches,
14118 else_branch,
14119 } => {
14120 operand.as_deref().is_some_and(expr_has_window)
14121 || branches
14122 .iter()
14123 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
14124 || else_branch.as_deref().is_some_and(expr_has_window)
14125 }
14126 }
14127}
14128
14129fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
14130 if let Expr::WindowFunction { .. } = e {
14131 if !out.iter().any(|x| x == e) {
14136 out.push(e.clone());
14137 }
14138 return;
14139 }
14140 match e {
14141 Expr::WindowFunction { .. } => unreachable!(),
14143 Expr::Binary { lhs, rhs, .. } => {
14144 collect_window_nodes(lhs, out);
14145 collect_window_nodes(rhs, out);
14146 }
14147 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14148 collect_window_nodes(expr, out);
14149 }
14150 Expr::FunctionCall { args, .. } => {
14151 for a in args {
14152 collect_window_nodes(a, out);
14153 }
14154 }
14155 Expr::Like { expr, pattern, .. } => {
14156 collect_window_nodes(expr, out);
14157 collect_window_nodes(pattern, out);
14158 }
14159 Expr::Extract { source, .. } => collect_window_nodes(source, out),
14160 _ => {}
14161 }
14162}
14163
14164fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
14165 if let Expr::WindowFunction { .. } = e
14166 && let Some(idx) = window_nodes.iter().position(|w| w == e)
14167 {
14168 *e = Expr::Column(spg_sql::ast::ColumnName {
14169 qualifier: None,
14170 name: alloc::format!("__win_{idx}"),
14171 });
14172 return;
14173 }
14174 match e {
14175 Expr::Binary { lhs, rhs, .. } => {
14176 rewrite_window_to_columns(lhs, window_nodes);
14177 rewrite_window_to_columns(rhs, window_nodes);
14178 }
14179 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14180 rewrite_window_to_columns(expr, window_nodes);
14181 }
14182 Expr::FunctionCall { args, .. } => {
14183 for a in args {
14184 rewrite_window_to_columns(a, window_nodes);
14185 }
14186 }
14187 Expr::Like { expr, pattern, .. } => {
14188 rewrite_window_to_columns(expr, window_nodes);
14189 rewrite_window_to_columns(pattern, window_nodes);
14190 }
14191 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
14192 _ => {}
14193 }
14194}
14195
14196fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
14200 for (x, y) in a.iter().zip(b.iter()) {
14201 let c = value_cmp(x, y);
14202 if c != core::cmp::Ordering::Equal {
14203 return c;
14204 }
14205 }
14206 a.len().cmp(&b.len())
14207}
14208
14209fn order_key_cmp(
14210 a: &[(Value, bool, Option<bool>)],
14211 b: &[(Value, bool, Option<bool>)],
14212) -> core::cmp::Ordering {
14213 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
14216 let c = order_by_value_cmp(*desc, *nf, va, vb);
14217 if c != core::cmp::Ordering::Equal {
14218 return c;
14219 }
14220 }
14221 a.len().cmp(&b.len())
14222}
14223
14224const fn value_is_integer(v: &Value) -> bool {
14230 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
14231}
14232
14233const fn value_to_i64(v: &Value) -> i64 {
14237 match v {
14238 Value::SmallInt(n) => *n as i64,
14239 Value::Int(n) => *n as i64,
14240 Value::BigInt(n) => *n,
14241 _ => panic!("value_to_i64 called on non-integer Value"),
14242 }
14243}
14244
14245fn generate_series_integers(
14251 start: i64,
14252 stop: i64,
14253 step: i64,
14254 cancel: &CancelToken<'_>,
14255) -> Result<alloc::vec::Vec<Row>, EngineError> {
14256 if step == 0 {
14257 return Err(EngineError::Unsupported(
14258 "generate_series(): step argument cannot be zero".into(),
14259 ));
14260 }
14261 let mut out = alloc::vec::Vec::new();
14262 let mut cur = start;
14263 const MAX_ROWS: usize = 10_000_000;
14267 loop {
14268 cancel.check()?;
14269 if step > 0 && cur > stop {
14270 break;
14271 }
14272 if step < 0 && cur < stop {
14273 break;
14274 }
14275 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
14276 if out.len() > MAX_ROWS {
14277 return Err(EngineError::Unsupported(alloc::format!(
14278 "generate_series(): exceeded {MAX_ROWS} rows; \
14279 narrow start/stop or use a larger step"
14280 )));
14281 }
14282 cur = match cur.checked_add(step) {
14283 Some(n) => n,
14284 None => break,
14285 };
14286 }
14287 Ok(out)
14288}
14289
14290fn generate_series_timestamps(
14295 start: i64,
14296 stop: i64,
14297 step: Value,
14298 cancel: &CancelToken<'_>,
14299) -> Result<alloc::vec::Vec<Row>, EngineError> {
14300 let (months, micros) = match &step {
14301 Value::Interval { months, micros } => (*months, *micros),
14302 _ => unreachable!("caller guards step.is_interval"),
14303 };
14304 if months == 0 && micros == 0 {
14305 return Err(EngineError::Unsupported(
14306 "generate_series(): INTERVAL step cannot be zero".into(),
14307 ));
14308 }
14309 let ascending = months > 0 || micros > 0;
14310 let mut out = alloc::vec::Vec::new();
14311 let mut cur = Value::Timestamp(start);
14312 const MAX_ROWS: usize = 10_000_000;
14313 loop {
14314 cancel.check()?;
14315 let cur_t = match cur {
14316 Value::Timestamp(t) => t,
14317 _ => unreachable!("loop invariant: cur is Timestamp"),
14318 };
14319 if ascending && cur_t > stop {
14320 break;
14321 }
14322 if !ascending && cur_t < stop {
14323 break;
14324 }
14325 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
14326 if out.len() > MAX_ROWS {
14327 return Err(EngineError::Unsupported(alloc::format!(
14328 "generate_series(): exceeded {MAX_ROWS} rows; \
14329 narrow start/stop or use a larger step"
14330 )));
14331 }
14332 let next = eval::apply_binary_interval(
14333 spg_sql::ast::BinOp::Add,
14334 &cur,
14335 &Value::Interval { months, micros },
14336 )
14337 .map_err(EngineError::Eval)?;
14338 cur = match next {
14339 Some(v) => v,
14340 None => break,
14341 };
14342 }
14343 Ok(out)
14344}
14345
14346#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
14352 desc: bool,
14353 nulls_first: Option<bool>,
14354 a: &Value,
14355 b: &Value,
14356) -> core::cmp::Ordering {
14357 use core::cmp::Ordering;
14358 let nf = nulls_first.unwrap_or(desc);
14359 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
14360 (true, true) => Ordering::Equal,
14361 (true, false) => {
14362 if nf {
14363 Ordering::Less
14364 } else {
14365 Ordering::Greater
14366 }
14367 }
14368 (false, true) => {
14369 if nf {
14370 Ordering::Greater
14371 } else {
14372 Ordering::Less
14373 }
14374 }
14375 (false, false) => {
14376 let c = value_cmp(a, b);
14377 if desc { c.reverse() } else { c }
14378 }
14379 }
14380}
14381
14382fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
14383 use core::cmp::Ordering;
14384 match (a, b) {
14385 (Value::Null, Value::Null) => Ordering::Equal,
14386 (Value::Null, _) => Ordering::Less,
14387 (_, Value::Null) => Ordering::Greater,
14388 (Value::Int(x), Value::Int(y)) => x.cmp(y),
14389 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
14390 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
14391 (Value::Text(x), Value::Text(y)) => x.cmp(y),
14392 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
14393 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
14394 (Value::Date(x), Value::Date(y)) => x.cmp(y),
14395 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
14396 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
14399 }
14400}
14401
14402#[allow(
14408 clippy::too_many_arguments,
14409 clippy::cast_possible_truncation,
14410 clippy::cast_possible_wrap,
14411 clippy::cast_precision_loss,
14412 clippy::cast_sign_loss,
14413 clippy::doc_markdown,
14414 clippy::too_many_lines,
14415 clippy::type_complexity,
14416 clippy::match_same_arms
14417)]
14418fn compute_window_partition(
14419 name: &str,
14420 args: &[Expr],
14421 ordered: bool,
14422 frame: Option<&WindowFrame>,
14423 null_treatment: spg_sql::ast::NullTreatment,
14424 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14425 filtered_rows: &[&Row],
14426 ctx: &EvalContext<'_>,
14427 out_vals: &mut [Value],
14428) -> Result<(), EngineError> {
14429 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
14430 let lower = name.to_ascii_lowercase();
14431 match lower.as_str() {
14432 "row_number" => {
14433 for (rank, (_, _, idx)) in slice.iter().enumerate() {
14434 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
14435 }
14436 Ok(())
14437 }
14438 "rank" => {
14439 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14440 let mut current_rank: i64 = 1;
14441 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14442 if let Some(p) = prev_key
14443 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14444 {
14445 current_rank = (i + 1) as i64;
14446 }
14447 if prev_key.is_none() {
14448 current_rank = 1;
14449 }
14450 out_vals[*idx] = Value::BigInt(current_rank);
14451 prev_key = Some(okey.as_slice());
14452 }
14453 Ok(())
14454 }
14455 "dense_rank" => {
14456 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14457 let mut current_rank: i64 = 0;
14458 for (_, okey, idx) in slice {
14459 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
14460 current_rank += 1;
14461 }
14462 out_vals[*idx] = Value::BigInt(current_rank);
14463 prev_key = Some(okey.as_slice());
14464 }
14465 Ok(())
14466 }
14467 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
14468 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
14471 slice.iter().map(|_| Value::Null).collect()
14472 } else {
14473 slice
14474 .iter()
14475 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14476 .collect::<Result<_, _>>()
14477 .map_err(EngineError::Eval)?
14478 };
14479 let eff = effective_frame(frame, ordered)?;
14483 #[allow(clippy::needless_range_loop)]
14484 for i in 0..slice.len() {
14485 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14486 let mut sum: f64 = 0.0;
14487 let mut count: i64 = 0;
14488 let mut min_v: Option<f64> = None;
14489 let mut max_v: Option<f64> = None;
14490 let mut row_count: i64 = 0;
14491 if lo <= hi {
14492 for j in lo..=hi {
14493 let v = &arg_values[j];
14494 match lower.as_str() {
14495 "count_star" => row_count += 1,
14496 "count" => {
14497 if !v.is_null() {
14498 count += 1;
14499 }
14500 }
14501 _ => {
14502 if let Some(x) = value_to_f64(v) {
14503 sum += x;
14504 count += 1;
14505 min_v = Some(min_v.map_or(x, |m| m.min(x)));
14506 max_v = Some(max_v.map_or(x, |m| m.max(x)));
14507 }
14508 }
14509 }
14510 }
14511 }
14512 let value = match lower.as_str() {
14513 "count_star" => Value::BigInt(row_count),
14514 "count" => Value::BigInt(count),
14515 "sum" => Value::Float(sum),
14516 "avg" => {
14517 if count == 0 {
14518 Value::Null
14519 } else {
14520 Value::Float(sum / count as f64)
14521 }
14522 }
14523 "min" => min_v.map_or(Value::Null, Value::Float),
14524 "max" => max_v.map_or(Value::Null, Value::Float),
14525 _ => unreachable!(),
14526 };
14527 let (_, _, idx) = &slice[i];
14528 out_vals[*idx] = value;
14529 }
14530 Ok(())
14531 }
14532 "lag" | "lead" => {
14533 if args.is_empty() {
14536 return Err(EngineError::Unsupported(alloc::format!(
14537 "{lower}() requires at least one argument"
14538 )));
14539 }
14540 let offset: i64 = if args.len() >= 2 {
14541 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
14542 .map_err(EngineError::Eval)?;
14543 match v {
14544 Value::SmallInt(n) => i64::from(n),
14545 Value::Int(n) => i64::from(n),
14546 Value::BigInt(n) => n,
14547 _ => {
14548 return Err(EngineError::Unsupported(alloc::format!(
14549 "{lower}() offset must be integer"
14550 )));
14551 }
14552 }
14553 } else {
14554 1
14555 };
14556 let default: Value = if args.len() >= 3 {
14557 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
14558 .map_err(EngineError::Eval)?
14559 } else {
14560 Value::Null
14561 };
14562 let values: Vec<Value> = slice
14563 .iter()
14564 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14565 .collect::<Result<_, _>>()
14566 .map_err(EngineError::Eval)?;
14567 let n = slice.len();
14568 for (i, (_, _, idx)) in slice.iter().enumerate() {
14569 let signed_offset = if lower == "lag" { -offset } else { offset };
14570 let v = if ignore_nulls {
14571 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
14575 let needed: i64 = signed_offset.abs();
14576 if needed == 0 {
14577 values[i].clone()
14578 } else {
14579 let mut j: i64 = i as i64;
14580 let mut hits: i64 = 0;
14581 let mut found: Option<Value> = None;
14582 loop {
14583 j += step;
14584 if j < 0 || j >= n as i64 {
14585 break;
14586 }
14587 #[allow(clippy::cast_sign_loss)]
14588 let v = &values[j as usize];
14589 if !v.is_null() {
14590 hits += 1;
14591 if hits == needed {
14592 found = Some(v.clone());
14593 break;
14594 }
14595 }
14596 }
14597 found.unwrap_or_else(|| default.clone())
14598 }
14599 } else {
14600 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
14601 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
14602 default.clone()
14603 } else {
14604 #[allow(clippy::cast_sign_loss)]
14605 {
14606 values[target_signed as usize].clone()
14607 }
14608 }
14609 };
14610 out_vals[*idx] = v;
14611 }
14612 Ok(())
14613 }
14614 "first_value" | "last_value" | "nth_value" => {
14615 if args.is_empty() {
14616 return Err(EngineError::Unsupported(alloc::format!(
14617 "{lower}() requires at least one argument"
14618 )));
14619 }
14620 let values: Vec<Value> = slice
14621 .iter()
14622 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14623 .collect::<Result<_, _>>()
14624 .map_err(EngineError::Eval)?;
14625 let nth: usize = if lower == "nth_value" {
14626 if args.len() < 2 {
14627 return Err(EngineError::Unsupported(
14628 "nth_value() requires (expr, n)".into(),
14629 ));
14630 }
14631 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
14632 .map_err(EngineError::Eval)?;
14633 let raw = match v {
14634 Value::SmallInt(n) => i64::from(n),
14635 Value::Int(n) => i64::from(n),
14636 Value::BigInt(n) => n,
14637 _ => {
14638 return Err(EngineError::Unsupported(
14639 "nth_value() n must be integer".into(),
14640 ));
14641 }
14642 };
14643 if raw < 1 {
14644 return Err(EngineError::Unsupported(
14645 "nth_value() n must be >= 1".into(),
14646 ));
14647 }
14648 #[allow(clippy::cast_sign_loss)]
14649 {
14650 raw as usize
14651 }
14652 } else {
14653 0
14654 };
14655 let eff = effective_frame(frame, ordered)?;
14656 for i in 0..slice.len() {
14657 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14658 let (_, _, idx) = &slice[i];
14659 let v = if lo > hi {
14660 Value::Null
14661 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
14662 if lower == "first_value" {
14665 (lo..=hi)
14666 .find_map(|j| {
14667 let v = &values[j];
14668 (!v.is_null()).then(|| v.clone())
14669 })
14670 .unwrap_or(Value::Null)
14671 } else {
14672 (lo..=hi)
14673 .rev()
14674 .find_map(|j| {
14675 let v = &values[j];
14676 (!v.is_null()).then(|| v.clone())
14677 })
14678 .unwrap_or(Value::Null)
14679 }
14680 } else {
14681 match lower.as_str() {
14682 "first_value" => values[lo].clone(),
14683 "last_value" => values[hi].clone(),
14684 "nth_value" => {
14685 let pos = lo + nth - 1;
14686 if pos > hi {
14687 Value::Null
14688 } else {
14689 values[pos].clone()
14690 }
14691 }
14692 _ => unreachable!(),
14693 }
14694 };
14695 out_vals[*idx] = v;
14696 }
14697 Ok(())
14698 }
14699 "ntile" => {
14700 if args.is_empty() {
14701 return Err(EngineError::Unsupported(
14702 "ntile(n) requires an integer argument".into(),
14703 ));
14704 }
14705 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
14706 .map_err(EngineError::Eval)?;
14707 let bucket_count: i64 = match v {
14708 Value::SmallInt(n) => i64::from(n),
14709 Value::Int(n) => i64::from(n),
14710 Value::BigInt(n) => n,
14711 _ => {
14712 return Err(EngineError::Unsupported(
14713 "ntile() argument must be integer".into(),
14714 ));
14715 }
14716 };
14717 if bucket_count < 1 {
14718 return Err(EngineError::Unsupported(
14719 "ntile() argument must be >= 1".into(),
14720 ));
14721 }
14722 #[allow(clippy::cast_sign_loss)]
14723 let buckets = bucket_count as usize;
14724 let n = slice.len();
14725 let base = n / buckets;
14728 let extras = n % buckets;
14729 let mut bucket: usize = 1;
14730 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
14731 let mut buckets_with_extra_remaining = extras;
14732 for (_, _, idx) in slice {
14733 if remaining_in_bucket == 0 {
14734 bucket += 1;
14735 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
14736 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
14737 base + 1
14738 } else {
14739 base
14740 };
14741 if remaining_in_bucket == 0 {
14744 remaining_in_bucket = 1;
14745 }
14746 }
14747 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
14748 remaining_in_bucket -= 1;
14749 }
14750 Ok(())
14751 }
14752 "percent_rank" => {
14753 let n = slice.len();
14756 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14757 let mut current_rank: i64 = 1;
14758 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14759 if let Some(p) = prev_key
14760 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14761 {
14762 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
14763 }
14764 if prev_key.is_none() {
14765 current_rank = 1;
14766 }
14767 #[allow(clippy::cast_precision_loss)]
14768 let pr = if n <= 1 {
14769 0.0
14770 } else {
14771 (current_rank - 1) as f64 / (n - 1) as f64
14772 };
14773 out_vals[*idx] = Value::Float(pr);
14774 prev_key = Some(okey.as_slice());
14775 }
14776 Ok(())
14777 }
14778 "cume_dist" => {
14779 let n = slice.len();
14781 for i in 0..slice.len() {
14783 let peer_end = peer_group_end(slice, i);
14784 #[allow(clippy::cast_precision_loss)]
14785 let cd = (peer_end + 1) as f64 / n as f64;
14786 let (_, _, idx) = &slice[i];
14787 out_vals[*idx] = Value::Float(cd);
14788 }
14789 Ok(())
14790 }
14791 other => Err(EngineError::Unsupported(alloc::format!(
14792 "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)"
14793 ))),
14794 }
14795}
14796
14797fn effective_frame(
14804 frame: Option<&WindowFrame>,
14805 ordered: bool,
14806) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
14807 match frame {
14808 None => {
14809 if ordered {
14810 Ok((
14811 FrameKind::Range,
14812 FrameBound::UnboundedPreceding,
14813 FrameBound::CurrentRow,
14814 ))
14815 } else {
14816 Ok((
14817 FrameKind::Rows,
14818 FrameBound::UnboundedPreceding,
14819 FrameBound::UnboundedFollowing,
14820 ))
14821 }
14822 }
14823 Some(fr) => {
14824 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
14825 if matches!(fr.start, FrameBound::UnboundedFollowing)
14827 || matches!(end, FrameBound::UnboundedPreceding)
14828 {
14829 return Err(EngineError::Unsupported(alloc::format!(
14830 "invalid frame: start={:?} end={:?}",
14831 fr.start,
14832 end
14833 )));
14834 }
14835 if fr.kind == FrameKind::Range
14840 && (matches!(
14841 fr.start,
14842 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
14843 ) || matches!(
14844 end,
14845 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
14846 ))
14847 {
14848 return Err(EngineError::Unsupported(
14849 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
14850 ));
14851 }
14852 Ok((fr.kind, fr.start.clone(), end))
14853 }
14854 }
14855}
14856
14857#[allow(clippy::type_complexity)]
14861fn frame_bounds_for_row(
14862 eff: &(FrameKind, FrameBound, FrameBound),
14863 i: usize,
14864 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14865) -> (usize, usize) {
14866 let (kind, start, end) = eff;
14867 let n = slice.len();
14868 let last = n.saturating_sub(1);
14869 let (mut lo, mut hi) = match kind {
14870 FrameKind::Rows => {
14871 let lo = match start {
14872 FrameBound::UnboundedPreceding => 0,
14873 FrameBound::OffsetPreceding(k) => {
14874 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14875 i.saturating_sub(k)
14876 }
14877 FrameBound::CurrentRow => i,
14878 FrameBound::OffsetFollowing(k) => {
14879 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14880 i.saturating_add(k).min(last)
14881 }
14882 FrameBound::UnboundedFollowing => last,
14883 };
14884 let hi = match end {
14885 FrameBound::UnboundedPreceding => 0,
14886 FrameBound::OffsetPreceding(k) => {
14887 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14888 i.saturating_sub(k)
14889 }
14890 FrameBound::CurrentRow => i,
14891 FrameBound::OffsetFollowing(k) => {
14892 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14893 i.saturating_add(k).min(last)
14894 }
14895 FrameBound::UnboundedFollowing => last,
14896 };
14897 (lo, hi)
14898 }
14899 FrameKind::Range => {
14900 let lo = match start {
14906 FrameBound::UnboundedPreceding => 0,
14907 FrameBound::CurrentRow => peer_group_start(slice, i),
14908 FrameBound::UnboundedFollowing => last,
14909 _ => unreachable!("offset bounds rejected for RANGE"),
14910 };
14911 let hi = match end {
14912 FrameBound::UnboundedPreceding => 0,
14913 FrameBound::CurrentRow => peer_group_end(slice, i),
14914 FrameBound::UnboundedFollowing => last,
14915 _ => unreachable!("offset bounds rejected for RANGE"),
14916 };
14917 (lo, hi)
14918 }
14919 };
14920 if hi >= n {
14921 hi = last;
14922 }
14923 if lo >= n {
14924 lo = last;
14925 }
14926 (lo, hi)
14927}
14928
14929#[allow(clippy::type_complexity)]
14933fn peer_group_start(
14934 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14935 i: usize,
14936) -> usize {
14937 let key = &slice[i].1;
14938 let mut j = i;
14939 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
14940 j -= 1;
14941 }
14942 j
14943}
14944
14945#[allow(clippy::type_complexity)]
14948fn peer_group_end(
14949 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14950 i: usize,
14951) -> usize {
14952 let key = &slice[i].1;
14953 let mut j = i;
14954 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
14955 j += 1;
14956 }
14957 j
14958}
14959
14960fn value_to_f64(v: &Value) -> Option<f64> {
14961 match v {
14962 Value::SmallInt(n) => Some(f64::from(*n)),
14963 Value::Int(n) => Some(f64::from(*n)),
14964 #[allow(clippy::cast_precision_loss)]
14965 Value::BigInt(n) => Some(*n as f64),
14966 Value::Float(x) => Some(*x),
14967 _ => None,
14968 }
14969}
14970
14971fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
14975 let mut any = false;
14976 for item in &stmt.items {
14977 if let SelectItem::Expr { expr, .. } = item {
14978 any = any || expr_has_subquery(expr);
14979 }
14980 }
14981 if let Some(w) = &stmt.where_ {
14982 any = any || expr_has_subquery(w);
14983 }
14984 if let Some(h) = &stmt.having {
14985 any = any || expr_has_subquery(h);
14986 }
14987 for o in &stmt.order_by {
14988 any = any || expr_has_subquery(&o.expr);
14989 }
14990 for (_, peer) in &stmt.unions {
14991 any = any || expr_tree_has_subquery(peer);
14992 }
14993 any
14994}
14995
14996pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
14997 match e {
14998 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
14999 Expr::AggregateOrdered { call, order_by, .. } => {
15000 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
15001 }
15002 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
15003 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15004 expr_has_subquery(expr)
15005 }
15006 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
15007 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
15008 Expr::Extract { source, .. } => expr_has_subquery(source),
15009 Expr::WindowFunction {
15010 args,
15011 partition_by,
15012 order_by,
15013 ..
15014 } => {
15015 args.iter().any(expr_has_subquery)
15016 || partition_by.iter().any(expr_has_subquery)
15017 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
15018 }
15019 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
15020 Expr::Array(items) => items.iter().any(expr_has_subquery),
15021 Expr::ArraySubscript { target, index } => {
15022 expr_has_subquery(target) || expr_has_subquery(index)
15023 }
15024 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
15025 Expr::Case {
15026 operand,
15027 branches,
15028 else_branch,
15029 } => {
15030 operand.as_deref().is_some_and(expr_has_subquery)
15031 || branches
15032 .iter()
15033 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
15034 || else_branch.as_deref().is_some_and(expr_has_subquery)
15035 }
15036 }
15037}
15038
15039fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
15046 let lit = match v {
15047 Value::Null => Literal::Null,
15048 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15049 Value::Int(n) => Literal::Integer(i64::from(n)),
15050 Value::BigInt(n) => Literal::Integer(n),
15051 Value::Float(x) => Literal::Float(x),
15052 Value::Text(s) | Value::Json(s) => Literal::String(s),
15053 Value::Bool(b) => Literal::Bool(b),
15054 other => {
15055 return Err(EngineError::Unsupported(alloc::format!(
15056 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
15057 other.data_type()
15058 )));
15059 }
15060 };
15061 Ok(Expr::Literal(lit))
15062}
15063
15064fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
15070 let lit = match v {
15071 Value::Null => Literal::Null,
15072 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15073 Value::Int(n) => Literal::Integer(i64::from(n)),
15074 Value::BigInt(n) => Literal::Integer(n),
15075 Value::Float(x) => Literal::Float(x),
15076 Value::Text(s) | Value::Json(s) => Literal::String(s),
15077 Value::Bool(b) => Literal::Bool(b),
15078 Value::Vector(xs) => Literal::Vector(xs),
15079 Value::Date(days) => {
15083 let micros = (i64::from(days)) * 86_400_000_000;
15084 Literal::String(format_timestamp_micros_as_date(micros))
15085 }
15086 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
15087 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
15088 other => {
15089 return Err(EngineError::Unsupported(alloc::format!(
15090 "INSERT … SELECT cannot materialise value of type {:?}; \
15091 add an explicit CAST in the inner SELECT",
15092 other.data_type()
15093 )));
15094 }
15095 };
15096 Ok(Expr::Literal(lit))
15097}
15098
15099fn format_timestamp_micros(us: i64) -> String {
15100 let days = us.div_euclid(86_400_000_000);
15102 let intra_day = us.rem_euclid(86_400_000_000);
15103 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
15104 let secs = intra_day / 1_000_000;
15105 let us_rem = intra_day % 1_000_000;
15106 let h = (secs / 3600) % 24;
15107 let m = (secs / 60) % 60;
15108 let s = secs % 60;
15109 if us_rem == 0 {
15110 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
15111 } else {
15112 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
15113 }
15114}
15115
15116fn format_timestamp_micros_as_date(us: i64) -> String {
15117 let days = us.div_euclid(86_400_000_000);
15120 let jdn = days + 2_440_588;
15122 let (y, mo, d) = jdn_to_ymd(jdn);
15123 alloc::format!("{y:04}-{mo:02}-{d:02}")
15124}
15125
15126fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
15127 let l = jdn + 68569;
15129 let n = (4 * l) / 146_097;
15130 let l = l - (146_097 * n + 3) / 4;
15131 let i = (4000 * (l + 1)) / 1_461_001;
15132 let l = l - (1461 * i) / 4 + 31;
15133 let j = (80 * l) / 2447;
15134 let day = (l - (2447 * j) / 80) as u32;
15135 let l = j / 11;
15136 let month = (j + 2 - 12 * l) as u32;
15137 let year = 100 * (n - 49) + i + l;
15138 (year, month, day)
15139}
15140
15141fn format_numeric(scaled: i128, scale: u8) -> String {
15142 if scale == 0 {
15143 return alloc::format!("{scaled}");
15144 }
15145 let abs = scaled.unsigned_abs();
15146 let divisor = 10u128.pow(u32::from(scale));
15147 let whole = abs / divisor;
15148 let frac = abs % divisor;
15149 let sign = if scaled < 0 { "-" } else { "" };
15150 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
15151}
15152
15153fn rewrite_column_in_source(
15177 src: &str,
15178 old: &str,
15179 new: &str,
15180) -> Result<alloc::string::String, EngineError> {
15181 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15182 EngineError::Unsupported(alloc::format!(
15183 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
15184 failed to parse for rewrite ({e})"
15185 ))
15186 })?;
15187 rewrite_column_in_expr(&mut expr, old, new);
15188 Ok(alloc::format!("{expr}"))
15189}
15190
15191fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
15199 match e {
15200 Expr::AggregateOrdered { call, order_by, .. } => {
15201 rewrite_column_in_expr(call, old, new);
15202 for o in order_by.iter_mut() {
15203 rewrite_column_in_expr(&mut o.expr, old, new);
15204 }
15205 }
15206 Expr::Column(c) => {
15207 if c.name.eq_ignore_ascii_case(old) {
15208 c.name = new.to_string();
15209 }
15210 }
15211 Expr::Binary { lhs, rhs, .. } => {
15212 rewrite_column_in_expr(lhs, old, new);
15213 rewrite_column_in_expr(rhs, old, new);
15214 }
15215 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15216 rewrite_column_in_expr(expr, old, new);
15217 }
15218 Expr::FunctionCall { args, .. } => {
15219 for a in args {
15220 rewrite_column_in_expr(a, old, new);
15221 }
15222 }
15223 Expr::Like { expr, pattern, .. } => {
15224 rewrite_column_in_expr(expr, old, new);
15225 rewrite_column_in_expr(pattern, old, new);
15226 }
15227 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
15228 Expr::WindowFunction {
15229 args,
15230 partition_by,
15231 order_by,
15232 ..
15233 } => {
15234 for a in args {
15235 rewrite_column_in_expr(a, old, new);
15236 }
15237 for p in partition_by {
15238 rewrite_column_in_expr(p, old, new);
15239 }
15240 for (o, _, _) in order_by {
15241 rewrite_column_in_expr(o, old, new);
15242 }
15243 }
15244 Expr::Array(items) => {
15245 for elem in items {
15246 rewrite_column_in_expr(elem, old, new);
15247 }
15248 }
15249 Expr::ArraySubscript { target, index } => {
15250 rewrite_column_in_expr(target, old, new);
15251 rewrite_column_in_expr(index, old, new);
15252 }
15253 Expr::AnyAll { expr, array, .. } => {
15254 rewrite_column_in_expr(expr, old, new);
15255 rewrite_column_in_expr(array, old, new);
15256 }
15257 Expr::Case {
15258 operand,
15259 branches,
15260 else_branch,
15261 } => {
15262 if let Some(o) = operand {
15263 rewrite_column_in_expr(o, old, new);
15264 }
15265 for (w, t) in branches {
15266 rewrite_column_in_expr(w, old, new);
15267 rewrite_column_in_expr(t, old, new);
15268 }
15269 if let Some(e) = else_branch {
15270 rewrite_column_in_expr(e, old, new);
15271 }
15272 }
15273 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
15277 Expr::Literal(_) | Expr::Placeholder(_) => {}
15278 }
15279}
15280
15281pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
15289 match stmt {
15290 Statement::Select(s) => substitute_select(s, params)?,
15291 Statement::Insert(ins) => {
15292 for row in &mut ins.rows {
15293 for e in row {
15294 substitute_expr(e, params)?;
15295 }
15296 }
15297 if let Some(clause) = &mut ins.on_conflict
15301 && let spg_sql::ast::OnConflictAction::Update {
15302 assignments,
15303 where_,
15304 } = &mut clause.action
15305 {
15306 for (_, e) in assignments.iter_mut() {
15307 substitute_expr(e, params)?;
15308 }
15309 if let Some(w) = where_ {
15310 substitute_expr(w, params)?;
15311 }
15312 }
15313 }
15314 Statement::Update(u) => {
15315 for (_, e) in &mut u.assignments {
15316 substitute_expr(e, params)?;
15317 }
15318 if let Some(w) = &mut u.where_ {
15319 substitute_expr(w, params)?;
15320 }
15321 }
15322 Statement::Delete(d) => {
15323 if let Some(w) = &mut d.where_ {
15324 substitute_expr(w, params)?;
15325 }
15326 }
15327 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
15328 _ => {}
15331 }
15332 Ok(())
15333}
15334
15335pub(crate) fn walk_select_exprs_mut(
15345 s: &mut SelectStatement,
15346 f: &mut impl FnMut(&mut Expr) -> Result<(), EngineError>,
15347) -> Result<(), EngineError> {
15348 for cte in &mut s.ctes {
15349 walk_select_exprs_mut(&mut cte.body, f)?;
15350 }
15351 for item in &mut s.items {
15352 if let SelectItem::Expr { expr, .. } = item {
15353 f(expr)?;
15354 }
15355 }
15356 if let Some(from) = &mut s.from {
15357 if let Some(sub) = &mut from.primary.lateral_subquery {
15358 walk_select_exprs_mut(sub, f)?;
15359 }
15360 for j in &mut from.joins {
15361 if let Some(sub) = &mut j.table.lateral_subquery {
15362 walk_select_exprs_mut(sub, f)?;
15363 }
15364 if let Some(on) = &mut j.on {
15365 f(on)?;
15366 }
15367 }
15368 }
15369 if let Some(w) = &mut s.where_ {
15370 f(w)?;
15371 }
15372 if let Some(gs) = &mut s.group_by {
15373 for g in gs {
15374 f(g)?;
15375 }
15376 }
15377 if let Some(h) = &mut s.having {
15378 f(h)?;
15379 }
15380 for o in &mut s.order_by {
15381 f(&mut o.expr)?;
15382 }
15383 for (_, peer) in &mut s.unions {
15384 walk_select_exprs_mut(peer, f)?;
15385 }
15386 Ok(())
15387}
15388
15389fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
15390 walk_select_exprs_mut(s, &mut |e| substitute_expr(e, params))?;
15391 for cte in &mut s.ctes {
15396 resolve_limit_offset_placeholders(&mut cte.body, params)?;
15397 }
15398 for (_, peer) in &mut s.unions {
15399 resolve_limit_offset_placeholders(peer, params)?;
15400 }
15401 if let Some(le) = s.limit {
15406 s.limit = Some(resolve_limit_placeholder(le, params)?);
15407 }
15408 if let Some(le) = s.offset {
15409 s.offset = Some(resolve_limit_placeholder(le, params)?);
15410 }
15411 Ok(())
15412}
15413
15414fn resolve_limit_offset_placeholders(
15417 s: &mut SelectStatement,
15418 params: &[Value],
15419) -> Result<(), EngineError> {
15420 if let Some(le) = s.limit {
15421 s.limit = Some(resolve_limit_placeholder(le, params)?);
15422 }
15423 if let Some(le) = s.offset {
15424 s.offset = Some(resolve_limit_placeholder(le, params)?);
15425 }
15426 for cte in &mut s.ctes {
15427 resolve_limit_offset_placeholders(&mut cte.body, params)?;
15428 }
15429 for (_, peer) in &mut s.unions {
15430 resolve_limit_offset_placeholders(peer, params)?;
15431 }
15432 Ok(())
15433}
15434
15435fn resolve_limit_placeholder(
15436 le: spg_sql::ast::LimitExpr,
15437 params: &[Value],
15438) -> Result<spg_sql::ast::LimitExpr, EngineError> {
15439 use spg_sql::ast::LimitExpr;
15440 match le {
15441 LimitExpr::Literal(_) => Ok(le),
15442 LimitExpr::Placeholder(n) => {
15443 let idx = usize::from(n).saturating_sub(1);
15444 let v = params.get(idx).ok_or_else(|| {
15445 EngineError::Eval(EvalError::PlaceholderOutOfRange {
15446 n,
15447 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
15448 })
15449 })?;
15450 let int = match v {
15451 Value::SmallInt(x) => Some(i64::from(*x)),
15452 Value::Int(x) => Some(i64::from(*x)),
15453 Value::BigInt(x) => Some(*x),
15454 _ => None,
15455 }
15456 .ok_or_else(|| {
15457 EngineError::Unsupported(alloc::format!(
15458 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
15459 ))
15460 })?;
15461 if int < 0 {
15462 return Err(EngineError::Unsupported(alloc::format!(
15463 "LIMIT/OFFSET ${n} bound to negative value {int}"
15464 )));
15465 }
15466 let bounded = u32::try_from(int).map_err(|_| {
15467 EngineError::Unsupported(alloc::format!(
15468 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
15469 ))
15470 })?;
15471 Ok(LimitExpr::Literal(bounded))
15472 }
15473 }
15474}
15475
15476fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
15477 if let Expr::Placeholder(n) = e {
15478 let idx = usize::from(*n).saturating_sub(1);
15479 let v = params.get(idx).ok_or_else(|| {
15480 EngineError::Eval(EvalError::PlaceholderOutOfRange {
15481 n: *n,
15482 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
15483 })
15484 })?;
15485 *e = Expr::Literal(value_to_literal(v.clone()));
15486 return Ok(());
15487 }
15488 match e {
15489 Expr::AggregateOrdered { call, order_by, .. } => {
15490 substitute_expr(call, params)?;
15491 for o in order_by.iter_mut() {
15492 substitute_expr(&mut o.expr, params)?;
15493 }
15494 }
15495 Expr::Binary { lhs, rhs, .. } => {
15496 substitute_expr(lhs, params)?;
15497 substitute_expr(rhs, params)?;
15498 }
15499 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15500 substitute_expr(expr, params)?;
15501 }
15502 Expr::FunctionCall { args, .. } => {
15503 for a in args {
15504 substitute_expr(a, params)?;
15505 }
15506 }
15507 Expr::Like { expr, pattern, .. } => {
15508 substitute_expr(expr, params)?;
15509 substitute_expr(pattern, params)?;
15510 }
15511 Expr::Extract { source, .. } => substitute_expr(source, params)?,
15512 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
15513 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
15514 Expr::InSubquery { expr, subquery, .. } => {
15515 substitute_expr(expr, params)?;
15516 substitute_select(subquery, params)?;
15517 }
15518 Expr::WindowFunction {
15519 args,
15520 partition_by,
15521 order_by,
15522 ..
15523 } => {
15524 for a in args {
15525 substitute_expr(a, params)?;
15526 }
15527 for p in partition_by {
15528 substitute_expr(p, params)?;
15529 }
15530 for (e, _, _) in order_by {
15531 substitute_expr(e, params)?;
15532 }
15533 }
15534 Expr::Literal(_) | Expr::Column(_) => {}
15535 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
15537 Expr::Array(items) => {
15538 for elem in items {
15539 substitute_expr(elem, params)?;
15540 }
15541 }
15542 Expr::ArraySubscript { target, index } => {
15543 substitute_expr(target, params)?;
15544 substitute_expr(index, params)?;
15545 }
15546 Expr::AnyAll { expr, array, .. } => {
15547 substitute_expr(expr, params)?;
15548 substitute_expr(array, params)?;
15549 }
15550 Expr::Case {
15551 operand,
15552 branches,
15553 else_branch,
15554 } => {
15555 if let Some(o) = operand {
15556 substitute_expr(o, params)?;
15557 }
15558 for (w, t) in branches {
15559 substitute_expr(w, params)?;
15560 substitute_expr(t, params)?;
15561 }
15562 if let Some(e) = else_branch {
15563 substitute_expr(e, params)?;
15564 }
15565 }
15566 }
15567 Ok(())
15568}
15569
15570fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
15588 use core::cmp::Ordering;
15589 match (a, b) {
15590 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
15591 (Value::Int(a), Value::Int(b)) => a.cmp(b),
15592 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
15593 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
15594 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
15595 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
15596 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
15597 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
15598 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
15599 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
15600 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
15601 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
15602 (Value::Date(a), Value::Date(b)) => a.cmp(b),
15603 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
15604 (Value::SmallInt(n), Value::Float(x)) => {
15606 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
15607 }
15608 (Value::Float(x), Value::SmallInt(n)) => {
15609 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
15610 }
15611 (Value::Int(n), Value::Float(x)) => {
15612 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
15613 }
15614 (Value::Float(x), Value::Int(n)) => {
15615 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
15616 }
15617 (Value::BigInt(n), Value::Float(x)) => {
15618 #[allow(clippy::cast_precision_loss)]
15619 let nf = *n as f64;
15620 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
15621 }
15622 (Value::Float(x), Value::BigInt(n)) => {
15623 #[allow(clippy::cast_precision_loss)]
15624 let nf = *n as f64;
15625 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
15626 }
15627 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
15630 }
15631}
15632
15633fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
15640 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
15641 out.push('[');
15642 for (i, b) in bounds.iter().enumerate() {
15643 if i > 0 {
15644 out.push_str(", ");
15645 }
15646 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
15647 if needs_quote {
15648 out.push('"');
15649 for ch in b.chars() {
15650 if ch == '"' || ch == '\\' {
15651 out.push('\\');
15652 }
15653 out.push(ch);
15654 }
15655 out.push('"');
15656 } else {
15657 out.push_str(b);
15658 }
15659 }
15660 out.push(']');
15661 out
15662}
15663
15664pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
15674 match v {
15675 Value::Null => "NULL".to_string(),
15676 Value::SmallInt(n) => alloc::format!("{n}"),
15677 Value::Int(n) => alloc::format!("{n}"),
15678 Value::BigInt(n) => alloc::format!("{n}"),
15679 Value::Float(x) => alloc::format!("{x:?}"),
15680 Value::Text(s) | Value::Json(s) => s.clone(),
15681 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
15682 Value::Date(d) => eval::format_date(*d),
15683 Value::Timestamp(t) => eval::format_timestamp(*t),
15684 Value::Time(us) => eval::format_time(*us),
15686 Value::Year(y) => alloc::format!("{y:04}"),
15688 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
15690 Value::Money(c) => eval::format_money(*c),
15692 v @ Value::Range { .. } => format_range_str(v),
15694 Value::Hstore(pairs) => format_hstore_str(pairs),
15696 Value::IntArray2D(rows) => format_int_2d_text(rows),
15698 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
15699 Value::TextArray2D(rows) => format_text_2d_text(rows),
15700 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
15701 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
15702 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
15703 alloc::format!("{v:?}")
15707 }
15708 _ => alloc::format!("{v:?}"),
15712 }
15713}
15714
15715const fn is_internal_table_name(_name: &str) -> bool {
15722 false
15723}
15724
15725fn value_to_literal(v: Value) -> Literal {
15726 match v {
15727 Value::Null => Literal::Null,
15728 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15729 Value::Int(n) => Literal::Integer(i64::from(n)),
15730 Value::BigInt(n) => Literal::Integer(n),
15731 Value::Float(x) => Literal::Float(x),
15732 Value::Text(s) | Value::Json(s) => Literal::String(s),
15733 Value::Bool(b) => Literal::Bool(b),
15734 Value::Vector(v) => Literal::Vector(v),
15735 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
15736 Value::Date(d) => Literal::String(eval::format_date(d)),
15737 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
15738 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
15744 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
15749 Value::TextArray(items) => Literal::TextArray(items),
15754 Value::IntArray(items) => Literal::IntArray(items),
15755 Value::BigIntArray(items) => Literal::BigIntArray(items),
15756 Value::Interval { months, micros } => Literal::Interval {
15757 months,
15758 micros,
15759 text: eval::format_interval(months, micros),
15760 },
15761 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
15764 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
15765 v => Literal::String(alloc::format!("{v:?}")),
15769 }
15770}
15771
15772fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
15773 let Some(now) = now_micros else {
15774 return;
15775 };
15776 match stmt {
15777 Statement::Select(s) => rewrite_select_clock(s, now),
15778 Statement::Insert(ins) => {
15779 for row in &mut ins.rows {
15780 for e in row {
15781 rewrite_expr_clock(e, now);
15782 }
15783 }
15784 if let Some(clause) = &mut ins.on_conflict
15788 && let spg_sql::ast::OnConflictAction::Update {
15789 assignments,
15790 where_,
15791 } = &mut clause.action
15792 {
15793 for (_, e) in assignments.iter_mut() {
15794 rewrite_expr_clock(e, now);
15795 }
15796 if let Some(w) = where_ {
15797 rewrite_expr_clock(w, now);
15798 }
15799 }
15800 }
15801 Statement::Update(u) => {
15805 for (_, e) in &mut u.assignments {
15806 rewrite_expr_clock(e, now);
15807 }
15808 if let Some(w) = &mut u.where_ {
15809 rewrite_expr_clock(w, now);
15810 }
15811 }
15812 Statement::Delete(d) => {
15813 if let Some(w) = &mut d.where_ {
15814 rewrite_expr_clock(w, now);
15815 }
15816 }
15817 _ => {}
15818 }
15819}
15820
15821fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
15822 let _ = walk_select_exprs_mut(s, &mut |e| {
15827 rewrite_expr_clock(e, now);
15828 Ok(())
15829 });
15830}
15831
15832fn rewrite_expr_clock(e: &mut Expr, now: i64) {
15840 if let Some(replacement) = clock_replacement_for(e, now) {
15844 *e = replacement;
15845 return;
15846 }
15847 match e {
15848 Expr::AggregateOrdered { call, order_by, .. } => {
15849 rewrite_expr_clock(call, now);
15850 for o in order_by.iter_mut() {
15851 rewrite_expr_clock(&mut o.expr, now);
15852 }
15853 }
15854 Expr::Binary { lhs, rhs, .. } => {
15855 rewrite_expr_clock(lhs, now);
15856 rewrite_expr_clock(rhs, now);
15857 }
15858 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15859 rewrite_expr_clock(expr, now);
15860 }
15861 Expr::FunctionCall { args, .. } => {
15862 for a in args {
15863 rewrite_expr_clock(a, now);
15864 }
15865 }
15866 Expr::Like { expr, pattern, .. } => {
15867 rewrite_expr_clock(expr, now);
15868 rewrite_expr_clock(pattern, now);
15869 }
15870 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
15871 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
15875 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
15876 Expr::InSubquery { expr, subquery, .. } => {
15877 rewrite_expr_clock(expr, now);
15878 rewrite_select_clock(subquery, now);
15879 }
15880 Expr::WindowFunction {
15883 args,
15884 partition_by,
15885 order_by,
15886 ..
15887 } => {
15888 for a in args {
15889 rewrite_expr_clock(a, now);
15890 }
15891 for p in partition_by {
15892 rewrite_expr_clock(p, now);
15893 }
15894 for (e, _, _) in order_by {
15895 rewrite_expr_clock(e, now);
15896 }
15897 }
15898 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
15899 Expr::Array(items) => {
15900 for elem in items {
15901 rewrite_expr_clock(elem, now);
15902 }
15903 }
15904 Expr::ArraySubscript { target, index } => {
15905 rewrite_expr_clock(target, now);
15906 rewrite_expr_clock(index, now);
15907 }
15908 Expr::AnyAll { expr, array, .. } => {
15909 rewrite_expr_clock(expr, now);
15910 rewrite_expr_clock(array, now);
15911 }
15912 Expr::Case {
15913 operand,
15914 branches,
15915 else_branch,
15916 } => {
15917 if let Some(o) = operand {
15918 rewrite_expr_clock(o, now);
15919 }
15920 for (w, t) in branches {
15921 rewrite_expr_clock(w, now);
15922 rewrite_expr_clock(t, now);
15923 }
15924 if let Some(e) = else_branch {
15925 rewrite_expr_clock(e, now);
15926 }
15927 }
15928 }
15929}
15930
15931fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
15938 let (kind, name) = match e {
15939 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
15940 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
15941 _ => return None,
15942 };
15943 enum ClockShape {
15951 Timestamp,
15952 Date,
15953 UnixSeconds,
15954 }
15955 let shape = match name.len() {
15956 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
15957 Some(ClockShape::Timestamp)
15958 }
15959 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
15960 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
15961 Some(ClockShape::UnixSeconds)
15962 }
15963 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
15964 _ => None,
15965 };
15966 let shape = shape?;
15967 let payload = match shape {
15968 ClockShape::Timestamp => now,
15969 ClockShape::Date => now.div_euclid(86_400_000_000),
15970 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
15971 };
15972 let target = match shape {
15973 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
15974 ClockShape::Date => spg_sql::ast::CastTarget::Date,
15975 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
15976 };
15977 Some(Expr::Cast {
15978 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
15979 target,
15980 })
15981}
15982
15983#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15984enum ClockSite {
15985 Fn,
15986 BareIdent,
15987}
15988
15989fn expand_group_by_all(s: &mut SelectStatement) {
16000 if !s.group_by_all {
16001 for (_, peer) in &mut s.unions {
16002 expand_group_by_all(peer);
16003 }
16004 return;
16005 }
16006 let mut groups: Vec<Expr> = Vec::new();
16007 for item in &s.items {
16008 if let SelectItem::Expr { expr, .. } = item
16009 && !aggregate::contains_aggregate(expr)
16010 {
16011 groups.push(expr.clone());
16012 }
16013 }
16014 s.group_by = Some(groups);
16015 s.group_by_all = false;
16016 for (_, peer) in &mut s.unions {
16017 expand_group_by_all(peer);
16018 }
16019}
16020
16021fn resolve_order_by_position(s: &mut SelectStatement) {
16022 for order in &mut s.order_by {
16027 match &order.expr {
16028 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
16029 if let Ok(idx_one_based) = usize::try_from(*n) {
16030 let idx = idx_one_based - 1;
16031 if idx < s.items.len()
16032 && let SelectItem::Expr { expr, .. } = &s.items[idx]
16033 {
16034 order.expr = expr.clone();
16035 }
16036 }
16037 }
16038 Expr::Column(c) if c.qualifier.is_none() => {
16039 for item in &s.items {
16041 if let SelectItem::Expr {
16042 expr,
16043 alias: Some(a),
16044 } = item
16045 && a == &c.name
16046 {
16047 order.expr = expr.clone();
16048 break;
16049 }
16050 }
16051 }
16052 _ => {}
16053 }
16054 }
16055 for (_, peer) in &mut s.unions {
16056 resolve_order_by_position(peer);
16057 }
16058}
16059
16060fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
16073 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
16074 match keep {
16075 Some(k) if k < tagged.len() && k > 0 => {
16076 let pivot = k - 1;
16077 tagged.select_nth_unstable_by(pivot, cmp);
16078 tagged[..k].sort_by(cmp);
16079 tagged.truncate(k);
16080 }
16081 _ => {
16082 tagged.sort_by(cmp);
16083 }
16084 }
16085}
16086
16087fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
16088 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
16089}
16090
16091fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
16095 use core::cmp::Ordering;
16096 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
16097 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
16098 let ord = if descs.get(i).copied().unwrap_or(false) {
16099 ord.reverse()
16100 } else {
16101 ord
16102 };
16103 if ord != Ordering::Equal {
16104 return ord;
16105 }
16106 }
16107 Ordering::Equal
16108}
16109
16110fn build_order_keys(
16113 order_by: &[OrderBy],
16114 row: &Row,
16115 ctx: &EvalContext,
16116) -> Result<Vec<f64>, EngineError> {
16117 let mut keys = Vec::with_capacity(order_by.len());
16118 for o in order_by {
16119 let v = eval::eval_expr(&o.expr, row, ctx)?;
16120 if matches!(v, Value::Null) {
16127 let nf = o.nulls_first.unwrap_or(o.desc);
16128 keys.push(if nf == o.desc {
16129 f64::INFINITY
16130 } else {
16131 f64::NEG_INFINITY
16132 });
16133 } else {
16134 keys.push(value_to_order_key(&v)?);
16135 }
16136 }
16137 Ok(keys)
16138}
16139
16140fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
16144 if let Some(off) = offset {
16145 let off = off as usize;
16146 if off >= rows.len() {
16147 rows.clear();
16148 } else {
16149 rows.drain(..off);
16150 }
16151 }
16152 if let Some(n) = limit {
16153 rows.truncate(n as usize);
16154 }
16155}
16156
16157fn apply_offset_and_limit_tagged(
16168 tagged: &mut Vec<(Vec<f64>, Row)>,
16169 offset: Option<u32>,
16170 limit: Option<u32>,
16171 with_ties: bool,
16172) {
16173 if let Some(off) = offset {
16174 let off = off as usize;
16175 if off >= tagged.len() {
16176 tagged.clear();
16177 } else {
16178 tagged.drain(..off);
16179 }
16180 }
16181 if let Some(n) = limit {
16182 let n = n as usize;
16183 if with_ties && n > 0 && n < tagged.len() {
16184 let cutoff_key = tagged[n - 1].0.clone();
16185 let mut end = n;
16186 while end < tagged.len() && tagged[end].0 == cutoff_key {
16187 end += 1;
16188 }
16189 tagged.truncate(end);
16190 } else {
16191 tagged.truncate(n);
16192 }
16193 }
16194}
16195
16196fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
16202 if stmt.limit_with_ties && stmt.order_by.is_empty() {
16203 return Err(EngineError::Unsupported(alloc::string::String::from(
16204 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
16205 )));
16206 }
16207 Ok(())
16208}
16209
16210fn resolve_foreign_key(
16224 local_table_name: &str,
16225 local_cols: &[ColumnSchema],
16226 fk: spg_sql::ast::ForeignKeyConstraint,
16227 catalog: &Catalog,
16228) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
16229 let mut local_columns = Vec::with_capacity(fk.columns.len());
16231 for name in &fk.columns {
16232 let pos = local_cols
16233 .iter()
16234 .position(|c| c.name == *name)
16235 .ok_or_else(|| {
16236 EngineError::Unsupported(alloc::format!(
16237 "FOREIGN KEY references unknown local column {name:?}"
16238 ))
16239 })?;
16240 local_columns.push(pos);
16241 }
16242 let is_self_ref = fk.parent_table == local_table_name;
16246 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
16247 (local_cols, local_table_name)
16248 } else {
16249 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
16250 EngineError::Storage(StorageError::TableNotFound {
16251 name: fk.parent_table.clone(),
16252 })
16253 })?;
16254 (
16255 parent_table.schema().columns.as_slice(),
16256 fk.parent_table.as_str(),
16257 )
16258 };
16259 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
16264 if fk.columns.len() != 1 {
16265 return Err(EngineError::Unsupported(
16266 "composite FOREIGN KEY without explicit parent column list is not supported \
16267 — list the parent columns explicitly"
16268 .into(),
16269 ));
16270 }
16271 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
16273 .ok_or_else(|| {
16274 EngineError::Unsupported(alloc::format!(
16275 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
16276 to default the FOREIGN KEY against"
16277 ))
16278 })?;
16279 alloc::vec![pos]
16280 } else {
16281 let mut out = Vec::with_capacity(fk.parent_columns.len());
16282 for name in &fk.parent_columns {
16283 let pos = parent_cols_for_lookup
16284 .iter()
16285 .position(|c| c.name == *name)
16286 .ok_or_else(|| {
16287 EngineError::Unsupported(alloc::format!(
16288 "FOREIGN KEY references unknown parent column \
16289 {name:?} on table {parent_table_str:?}"
16290 ))
16291 })?;
16292 out.push(pos);
16293 }
16294 out
16295 };
16296 if parent_columns.len() != local_columns.len() {
16297 return Err(EngineError::Unsupported(alloc::format!(
16298 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
16299 local_columns.len(),
16300 parent_columns.len()
16301 )));
16302 }
16303 if !is_self_ref {
16313 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
16314 let primary_parent_col = parent_columns[0];
16315 let has_btree = parent_table
16316 .schema()
16317 .columns
16318 .get(primary_parent_col)
16319 .is_some()
16320 && parent_table.indices().iter().any(|idx| {
16321 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16322 && idx.column_position == primary_parent_col
16323 && idx.partial_predicate.is_none()
16324 });
16325 if !has_btree {
16326 return Err(EngineError::Unsupported(alloc::format!(
16327 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
16328 index — create one with `CREATE INDEX ... ON {} ({})` first",
16329 parent_table_str,
16330 parent_table_str,
16331 parent_table.schema().columns[primary_parent_col].name,
16332 )));
16333 }
16334 }
16335 let on_delete = fk_action_sql_to_storage(fk.on_delete);
16336 let on_update = fk_action_sql_to_storage(fk.on_update);
16337 Ok(spg_storage::ForeignKeyConstraint {
16338 name: fk.name,
16339 local_columns,
16340 parent_table: fk.parent_table,
16341 parent_columns,
16342 on_delete,
16343 on_update,
16344 })
16345}
16346
16347fn pick_pk_index_column(
16353 catalog: &Catalog,
16354 parent_name: &str,
16355 is_self_ref: bool,
16356 local_cols: &[ColumnSchema],
16357) -> Option<usize> {
16358 if is_self_ref {
16359 let _ = local_cols;
16363 return Some(0);
16364 }
16365 let parent = catalog.get(parent_name)?;
16366 parent.indices().iter().find_map(|idx| {
16367 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16368 && idx.partial_predicate.is_none()
16369 && idx.included_columns.is_empty()
16370 && idx.expression.is_none()
16371 {
16372 Some(idx.column_position)
16373 } else {
16374 None
16375 }
16376 })
16377}
16378
16379fn resolve_on_conflict_columns(
16390 catalog: &Catalog,
16391 table_name: &str,
16392 target: &[String],
16393) -> Result<(Vec<usize>, bool), EngineError> {
16394 let table = catalog.get(table_name).ok_or_else(|| {
16395 EngineError::Storage(StorageError::TableNotFound {
16396 name: table_name.into(),
16397 })
16398 })?;
16399 if target.is_empty() {
16400 if let Some(uc) = table.schema().uniqueness_constraints.first() {
16410 return Ok((uc.columns.clone(), uc.nulls_not_distinct));
16411 }
16412 let pos = table
16413 .indices()
16414 .iter()
16415 .find_map(|idx| {
16416 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16417 && idx.partial_predicate.is_none()
16418 && idx.included_columns.is_empty()
16419 && idx.expression.is_none()
16420 {
16421 Some(idx.column_position)
16422 } else {
16423 None
16424 }
16425 })
16426 .ok_or_else(|| {
16427 EngineError::Unsupported(alloc::format!(
16428 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
16429 ))
16430 })?;
16431 return Ok((alloc::vec![pos], false));
16432 }
16433 let mut out = Vec::with_capacity(target.len());
16434 for name in target {
16435 let pos = table
16436 .schema()
16437 .columns
16438 .iter()
16439 .position(|c| c.name == *name)
16440 .ok_or_else(|| {
16441 EngineError::Unsupported(alloc::format!(
16442 "ON CONFLICT target column {name:?} not found on {table_name:?}"
16443 ))
16444 })?;
16445 out.push(pos);
16446 }
16447 let mut sorted = out.clone();
16450 sorted.sort_unstable();
16451 let nnd = table.schema().uniqueness_constraints.iter().any(|uc| {
16452 let mut u = uc.columns.clone();
16453 u.sort_unstable();
16454 u == sorted && uc.nulls_not_distinct
16455 });
16456 Ok((out, nnd))
16457}
16458
16459fn on_conflict_key_exists(
16462 catalog: &Catalog,
16463 table_name: &str,
16464 column_pos: usize,
16465 key: &Value,
16466) -> bool {
16467 let Some(table) = catalog.get(table_name) else {
16468 return false;
16469 };
16470 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
16471 return false;
16472 };
16473 table.indices().iter().any(|idx| {
16474 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16475 && idx.column_position == column_pos
16476 && idx.partial_predicate.is_none()
16477 && !idx.lookup_eq(&idx_key).is_empty()
16478 })
16479}
16480
16481fn lookup_row_position_by_keys(
16487 catalog: &Catalog,
16488 table_name: &str,
16489 column_positions: &[usize],
16490 key: &[&Value],
16491) -> Option<usize> {
16492 let table = catalog.get(table_name)?;
16493 table.rows().iter().position(|r| {
16494 column_positions
16495 .iter()
16496 .enumerate()
16497 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
16498 })
16499}
16500
16501fn on_conflict_keys_exist(
16506 catalog: &Catalog,
16507 table_name: &str,
16508 column_positions: &[usize],
16509 key: &[&Value],
16510) -> bool {
16511 if column_positions.len() == 1 {
16512 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
16513 }
16514 let Some(table) = catalog.get(table_name) else {
16515 return false;
16516 };
16517 table.rows().iter().any(|r| {
16518 column_positions
16519 .iter()
16520 .enumerate()
16521 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
16522 })
16523}
16524
16525fn apply_on_conflict_assignments(
16538 catalog: &Catalog,
16539 table_name: &str,
16540 target_pos: usize,
16541 incoming: &[Value],
16542 assignments: &[(String, Expr)],
16543 where_: Option<&Expr>,
16544) -> Result<Option<Vec<Value>>, EngineError> {
16545 let table = catalog.get(table_name).ok_or_else(|| {
16546 EngineError::Storage(StorageError::TableNotFound {
16547 name: table_name.into(),
16548 })
16549 })?;
16550 let schema_cols = table.schema().columns.clone();
16551 let existing = table
16552 .rows()
16553 .get(target_pos)
16554 .ok_or_else(|| {
16555 EngineError::Unsupported(alloc::format!(
16556 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
16557 ))
16558 })?
16559 .clone();
16560 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
16561 if let Some(w) = where_ {
16563 let pred = w.clone();
16564 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
16565 let v = eval::eval_expr(&pred, &existing, &ctx)?;
16566 if !matches!(v, Value::Bool(true)) {
16567 return Ok(None);
16568 }
16569 }
16570 let mut new_values = existing.values.clone();
16571 for (col_name, expr) in assignments {
16572 let target_idx = schema_cols
16573 .iter()
16574 .position(|c| c.name == *col_name)
16575 .ok_or_else(|| {
16576 EngineError::Eval(EvalError::ColumnNotFound {
16577 name: col_name.clone(),
16578 })
16579 })?;
16580 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
16581 let v = eval::eval_expr(&sub, &existing, &ctx)?;
16582 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
16583 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
16584 new_values[target_idx] = coerced;
16585 }
16586 Ok(Some(new_values))
16587}
16588
16589fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
16594 use spg_sql::ast::ColumnName;
16595 match expr {
16596 Expr::Column(ColumnName { qualifier, name })
16597 if qualifier
16598 .as_deref()
16599 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
16600 {
16601 let pos = schema_cols.iter().position(|c| c.name == name);
16602 match pos {
16603 Some(p) => {
16604 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
16605 value_to_literal_expr(v)
16606 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
16607 }
16608 None => Expr::Column(ColumnName { qualifier, name }),
16609 }
16610 }
16611 Expr::Binary { op, lhs, rhs } => Expr::Binary {
16612 op,
16613 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
16614 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
16615 },
16616 Expr::Unary { op, expr } => Expr::Unary {
16617 op,
16618 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
16619 },
16620 Expr::FunctionCall { name, args } => Expr::FunctionCall {
16621 name,
16622 args: args
16623 .into_iter()
16624 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
16625 .collect(),
16626 },
16627 other => other,
16628 }
16629}
16630
16631fn enforce_uniqueness_inserts(
16654 catalog: &Catalog,
16655 child_table: &str,
16656 constraints: &[spg_storage::UniquenessConstraint],
16657 rows: &[Vec<Value>],
16658) -> Result<(), EngineError> {
16659 if constraints.is_empty() {
16660 return Ok(());
16661 }
16662 let table = catalog.get(child_table).ok_or_else(|| {
16663 EngineError::Storage(StorageError::TableNotFound {
16664 name: child_table.into(),
16665 })
16666 })?;
16667 let schema = table.schema();
16668 for uc in constraints {
16678 let fold_key = |values: &[Value]| -> Vec<Value> {
16679 uc.columns
16680 .iter()
16681 .map(|&i| {
16682 let v = values.get(i).cloned().unwrap_or(Value::Null);
16683 collated_key_cell(&v, i, schema)
16684 })
16685 .collect()
16686 };
16687 let mut seen: hashbrown::HashSet<String> =
16688 hashbrown::HashSet::with_capacity(table.rows().len() + rows.len());
16689 for prow in table.rows() {
16690 let key = fold_key(&prow.values);
16691 if key.iter().any(|v| matches!(v, Value::Null)) && !uc.nulls_not_distinct {
16692 continue;
16693 }
16694 seen.insert(aggregate::encode_key(&key));
16695 }
16696 for (batch_idx, row_values) in rows.iter().enumerate() {
16697 let key = fold_key(row_values);
16698 if key.iter().any(|v| matches!(v, Value::Null)) && !uc.nulls_not_distinct {
16699 continue;
16700 }
16701 if !seen.insert(aggregate::encode_key(&key)) {
16702 let kind = if uc.is_primary_key {
16703 "PRIMARY KEY"
16704 } else {
16705 "UNIQUE"
16706 };
16707 let col_names: Vec<String> = uc
16708 .columns
16709 .iter()
16710 .map(|&i| table.schema().columns[i].name.clone())
16711 .collect();
16712 return Err(EngineError::Unsupported(alloc::format!(
16713 "{kind} violation on {child_table:?} columns {col_names:?}: \
16714 row #{batch_idx} duplicates an existing key"
16715 )));
16716 }
16717 }
16718 }
16719 Ok(())
16720}
16721
16722fn collated_key_cell(
16729 v: &spg_storage::Value,
16730 column_position: usize,
16731 schema: &spg_storage::TableSchema,
16732) -> spg_storage::Value {
16733 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
16734 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
16735 spg_storage::Value::Text(s.to_ascii_lowercase())
16736 }
16737 _ => v.clone(),
16738 }
16739}
16740
16741fn predicate_truthy(v: &spg_storage::Value) -> bool {
16749 use spg_storage::Value as V;
16750 match v {
16751 V::Bool(b) => *b,
16752 V::Int(n) => *n != 0,
16753 V::BigInt(n) => *n != 0,
16754 V::SmallInt(n) => *n != 0,
16755 _ => false,
16756 }
16757}
16758
16759fn check_existing_unique_violation(
16764 idx: &spg_storage::Index,
16765 schema: &spg_storage::TableSchema,
16766 rows: &[spg_storage::Row],
16767) -> Result<(), EngineError> {
16768 let predicate_expr = match idx.partial_predicate.as_deref() {
16769 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16770 EngineError::Unsupported(alloc::format!(
16771 "stored partial predicate {s:?} failed to re-parse: {e:?}"
16772 ))
16773 })?),
16774 None => None,
16775 };
16776 let ctx = eval::EvalContext::new(&schema.columns, None);
16777 let key_positions = unique_key_positions(idx);
16778 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
16779 for row in rows {
16780 if let Some(expr) = &predicate_expr {
16781 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
16782 EngineError::Unsupported(alloc::format!(
16783 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
16784 ))
16785 })?;
16786 if !predicate_truthy(&v) {
16787 continue;
16788 }
16789 }
16790 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
16791 .iter()
16792 .map(|&p| {
16793 let v = row
16794 .values
16795 .get(p)
16796 .cloned()
16797 .unwrap_or(spg_storage::Value::Null);
16798 collated_key_cell(&v, p, schema)
16799 })
16800 .collect();
16801 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16802 continue;
16803 }
16804 if seen.iter().any(|other| *other == key) {
16805 return Err(EngineError::Unsupported(alloc::format!(
16806 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
16807 idx.name
16808 )));
16809 }
16810 seen.push(key);
16811 }
16812 Ok(())
16813}
16814
16815fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
16819 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
16820 out.push(idx.column_position);
16821 out.extend_from_slice(&idx.extra_column_positions);
16822 out
16823}
16824
16825fn enforce_unique_index_inserts(
16833 catalog: &Catalog,
16834 table_name: &str,
16835 rows: &[alloc::vec::Vec<spg_storage::Value>],
16836) -> Result<(), EngineError> {
16837 let table = catalog.get(table_name).ok_or_else(|| {
16838 EngineError::Storage(StorageError::TableNotFound {
16839 name: table_name.into(),
16840 })
16841 })?;
16842 let schema = table.schema();
16843 let ctx = eval::EvalContext::new(&schema.columns, None);
16844 for idx in table.indices() {
16845 if !idx.is_unique {
16846 continue;
16847 }
16848 let predicate_expr = match idx.partial_predicate.as_deref() {
16850 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16851 EngineError::Unsupported(alloc::format!(
16852 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
16853 idx.name
16854 ))
16855 })?),
16856 None => None,
16857 };
16858 let key_positions = unique_key_positions(idx);
16859 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
16860 key_positions
16861 .iter()
16862 .map(|&p| {
16863 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
16864 collated_key_cell(&v, p, schema)
16865 })
16866 .collect()
16867 };
16868 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
16869 let Some(expr) = &predicate_expr else {
16870 return Ok(true);
16871 };
16872 let tmp_row = spg_storage::Row {
16873 values: values.to_vec(),
16874 };
16875 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
16876 EngineError::Unsupported(alloc::format!(
16877 "UNIQUE INDEX {:?} predicate eval: {e:?}",
16878 idx.name
16879 ))
16880 })?;
16881 Ok(predicate_truthy(&v))
16882 };
16883 let mut seen: hashbrown::HashSet<String> =
16888 hashbrown::HashSet::with_capacity(table.rows().len() + rows.len());
16889 for prow in table.rows() {
16890 if !participates(&prow.values)? {
16891 continue;
16892 }
16893 let key = key_of(&prow.values);
16894 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16895 continue;
16896 }
16897 seen.insert(aggregate::encode_key(&key));
16898 }
16899 for (batch_idx, row_values) in rows.iter().enumerate() {
16900 if !participates(row_values)? {
16901 continue;
16902 }
16903 let key = key_of(row_values);
16904 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16905 continue;
16906 }
16907 if !seen.insert(aggregate::encode_key(&key)) {
16908 return Err(EngineError::Unsupported(alloc::format!(
16909 "UNIQUE INDEX {:?} violation on {table_name:?}: \
16910 row #{batch_idx} duplicates an existing key",
16911 idx.name
16912 )));
16913 }
16914 }
16915 }
16916 Ok(())
16917}
16918
16919fn any_column_changed(
16927 filter_cols: &[String],
16928 schema_cols: &[ColumnSchema],
16929 old_row: &Row,
16930 new_row: &Row,
16931) -> bool {
16932 for col_name in filter_cols {
16933 let Some(pos) = schema_cols
16934 .iter()
16935 .position(|c| c.name.eq_ignore_ascii_case(col_name))
16936 else {
16937 continue;
16938 };
16939 let old_v = old_row.values.get(pos);
16940 let new_v = new_row.values.get(pos);
16941 if old_v != new_v {
16942 return true;
16943 }
16944 }
16945 false
16946}
16947
16948fn enforce_check_constraints(
16953 catalog: &Catalog,
16954 table_name: &str,
16955 rows: &[alloc::vec::Vec<spg_storage::Value>],
16956) -> Result<(), EngineError> {
16957 let table = catalog.get(table_name).ok_or_else(|| {
16958 EngineError::Storage(StorageError::TableNotFound {
16959 name: table_name.into(),
16960 })
16961 })?;
16962 let schema = table.schema();
16963 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
16967 alloc::vec::Vec::new();
16968 for (idx, col) in schema.columns.iter().enumerate() {
16969 let Some(dname) = &col.user_domain_type else {
16970 continue;
16971 };
16972 let Some(dom) = catalog.domain_types().get(dname) else {
16973 continue;
16974 };
16975 let mut parsed_for_col: alloc::vec::Vec<Expr> =
16976 alloc::vec::Vec::with_capacity(dom.checks.len());
16977 for src in &dom.checks {
16978 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
16979 EngineError::Unsupported(alloc::format!(
16980 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
16981 col.name
16982 ))
16983 })?;
16984 parsed_for_col.push(expr);
16985 }
16986 if !parsed_for_col.is_empty() {
16987 domain_checks_per_col.push((idx, parsed_for_col));
16988 }
16989 }
16990 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
16991 return Ok(());
16992 }
16993 let ctx = eval::EvalContext::new(&schema.columns, None);
16994 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
16995 for (i, src) in schema.checks.iter().enumerate() {
16996 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
16997 EngineError::Unsupported(alloc::format!(
16998 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
16999 ))
17000 })?;
17001 parsed.push((i, expr));
17002 }
17003 for (batch_idx, row_values) in rows.iter().enumerate() {
17004 let tmp_row = spg_storage::Row {
17005 values: row_values.clone(),
17006 };
17007 for (i, expr) in &parsed {
17008 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
17009 EngineError::Unsupported(alloc::format!(
17010 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
17011 ))
17012 })?;
17013 if matches!(v, spg_storage::Value::Bool(false)) {
17015 return Err(EngineError::Unsupported(alloc::format!(
17016 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
17017 schema.checks[*i]
17018 )));
17019 }
17020 }
17021 for (col_idx, checks) in &domain_checks_per_col {
17027 let cell = row_values
17028 .get(*col_idx)
17029 .cloned()
17030 .unwrap_or(spg_storage::Value::Null);
17031 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
17032 "value",
17033 schema.columns[*col_idx].ty,
17034 schema.columns[*col_idx].nullable,
17035 )];
17036 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
17037 let synth_row = spg_storage::Row {
17038 values: alloc::vec![cell],
17039 };
17040 for (ci, expr) in checks.iter().enumerate() {
17041 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
17042 EngineError::Unsupported(alloc::format!(
17043 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
17044 schema.columns[*col_idx].name
17045 ))
17046 })?;
17047 if matches!(v, spg_storage::Value::Bool(false)) {
17048 return Err(EngineError::Unsupported(alloc::format!(
17049 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
17050 schema.columns[*col_idx].name
17051 )));
17052 }
17053 }
17054 }
17055 }
17056 Ok(())
17057}
17058
17059fn enforce_fk_inserts(
17060 catalog: &Catalog,
17061 child_table: &str,
17062 fks: &[spg_storage::ForeignKeyConstraint],
17063 rows: &[Vec<Value>],
17064) -> Result<(), EngineError> {
17065 for fk in fks {
17066 let parent_is_self = fk.parent_table == child_table;
17067 let parent = if parent_is_self {
17068 catalog.get(child_table).ok_or_else(|| {
17071 EngineError::Storage(StorageError::TableNotFound {
17072 name: child_table.into(),
17073 })
17074 })?
17075 } else {
17076 catalog.get(&fk.parent_table).ok_or_else(|| {
17077 EngineError::Storage(StorageError::TableNotFound {
17078 name: fk.parent_table.clone(),
17079 })
17080 })?
17081 };
17082 for (batch_idx, row_values) in rows.iter().enumerate() {
17083 if fk.local_columns.len() == 1 {
17087 let v = &row_values[fk.local_columns[0]];
17088 if matches!(v, Value::Null) {
17089 continue;
17090 }
17091 let parent_col = fk.parent_columns[0];
17092 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
17093 EngineError::Unsupported(alloc::format!(
17094 "FOREIGN KEY column value of type {:?} is not index-eligible",
17095 v.data_type()
17096 ))
17097 })?;
17098 let present_committed = parent.indices().iter().any(|idx| {
17099 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
17100 && idx.column_position == parent_col
17101 && idx.partial_predicate.is_none()
17102 && !idx.lookup_eq(&key).is_empty()
17103 });
17104 let present_in_batch = parent_is_self
17108 && rows[..batch_idx]
17109 .iter()
17110 .any(|earlier| earlier.get(parent_col) == Some(v));
17111 if !(present_committed || present_in_batch) {
17112 return Err(EngineError::Unsupported(alloc::format!(
17113 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
17114 fk.parent_table,
17115 parent
17116 .schema()
17117 .columns
17118 .get(parent_col)
17119 .map_or("?", |c| c.name.as_str()),
17120 v,
17121 )));
17122 }
17123 } else {
17124 if fk
17128 .local_columns
17129 .iter()
17130 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
17131 {
17132 continue;
17133 }
17134 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
17135 let parent_match_committed = parent.rows().iter().any(|prow| {
17136 fk.parent_columns
17137 .iter()
17138 .enumerate()
17139 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
17140 });
17141 let parent_match_in_batch = parent_is_self
17142 && rows[..batch_idx].iter().any(|earlier| {
17143 fk.parent_columns
17144 .iter()
17145 .enumerate()
17146 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
17147 });
17148 if !(parent_match_committed || parent_match_in_batch) {
17149 return Err(EngineError::Unsupported(alloc::format!(
17150 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
17151 fk.parent_table,
17152 )));
17153 }
17154 }
17155 }
17156 }
17157 Ok(())
17158}
17159
17160#[derive(Debug, Clone)]
17164struct FkChildStep {
17165 child_table: String,
17166 action: FkChildAction,
17167}
17168
17169#[derive(Debug, Clone)]
17170enum FkChildAction {
17171 Delete { positions: Vec<usize> },
17173 SetNull {
17177 positions: Vec<usize>,
17178 columns: Vec<usize>,
17179 },
17180 SetDefault {
17184 positions: Vec<usize>,
17185 columns: Vec<usize>,
17186 defaults: Vec<Value>,
17187 },
17188}
17189
17190fn plan_fk_parent_deletions(
17206 catalog: &Catalog,
17207 parent_table_name: &str,
17208 to_delete_positions: &[usize],
17209 to_delete_rows: &[Vec<Value>],
17210) -> Result<Vec<FkChildStep>, EngineError> {
17211 use alloc::collections::{BTreeMap, BTreeSet};
17212 if to_delete_rows.is_empty() {
17213 return Ok(Vec::new());
17214 }
17215 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
17216 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
17218 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17219 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
17220 for &p in to_delete_positions {
17221 visited.insert((parent_table_name.to_string(), p));
17222 }
17223 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
17224 .iter()
17225 .map(|r| (parent_table_name.to_string(), r.clone()))
17226 .collect();
17227 while let Some((cur_parent, parent_row)) = work.pop() {
17228 for child_name in catalog.table_names() {
17229 let child = catalog
17230 .get(&child_name)
17231 .expect("table_names → catalog.get round-trip is total");
17232 for fk in &child.schema().foreign_keys {
17233 if fk.parent_table != cur_parent {
17234 continue;
17235 }
17236 let parent_key: Vec<&Value> = fk
17237 .parent_columns
17238 .iter()
17239 .map(|&pi| &parent_row[pi])
17240 .collect();
17241 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
17242 continue;
17243 }
17244 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
17245 if child_name == cur_parent
17246 && visited.contains(&(child_name.clone(), child_row_idx))
17247 {
17248 continue;
17249 }
17250 let matches_key = fk
17251 .local_columns
17252 .iter()
17253 .enumerate()
17254 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
17255 if !matches_key {
17256 continue;
17257 }
17258 match fk.on_delete {
17259 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
17260 return Err(EngineError::Unsupported(alloc::format!(
17261 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
17262 restricted by FK from {child_name:?}.{:?}",
17263 fk.local_columns,
17264 )));
17265 }
17266 spg_storage::FkAction::Cascade => {
17267 if visited.insert((child_name.clone(), child_row_idx)) {
17268 delete_plan
17269 .entry(child_name.clone())
17270 .or_default()
17271 .insert(child_row_idx);
17272 work.push((child_name.clone(), child_row.values.clone()));
17273 }
17274 }
17275 spg_storage::FkAction::SetNull => {
17276 for &li in &fk.local_columns {
17278 let col = child.schema().columns.get(li).ok_or_else(|| {
17279 EngineError::Unsupported(alloc::format!(
17280 "FK local column {li} missing in {child_name:?}"
17281 ))
17282 })?;
17283 if !col.nullable {
17284 return Err(EngineError::Unsupported(alloc::format!(
17285 "FOREIGN KEY ON DELETE SET NULL: column \
17286 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
17287 col.name,
17288 )));
17289 }
17290 }
17291 let entry = setnull_plan.entry(child_name.clone()).or_default();
17292 for &li in &fk.local_columns {
17293 entry.insert((child_row_idx, li));
17294 }
17295 }
17296 spg_storage::FkAction::SetDefault => {
17297 let entry = setdefault_plan.entry(child_name.clone()).or_default();
17299 for &li in &fk.local_columns {
17300 let col = child.schema().columns.get(li).ok_or_else(|| {
17301 EngineError::Unsupported(alloc::format!(
17302 "FK local column {li} missing in {child_name:?}"
17303 ))
17304 })?;
17305 let default = col.default.clone().ok_or_else(|| {
17306 EngineError::Unsupported(alloc::format!(
17307 "FOREIGN KEY ON DELETE SET DEFAULT: column \
17308 {child_name:?}.{:?} has no DEFAULT declared",
17309 col.name,
17310 ))
17311 })?;
17312 entry.insert((child_row_idx, li), default);
17313 }
17314 }
17315 }
17316 }
17317 }
17318 }
17319 }
17320 let mut steps: Vec<FkChildStep> = Vec::new();
17328 for (child_table, entries) in setnull_plan {
17329 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
17330 steps.push(FkChildStep {
17331 child_table,
17332 action: FkChildAction::SetNull { positions, columns },
17333 });
17334 }
17335 for (child_table, entries) in setdefault_plan {
17336 let mut positions = Vec::with_capacity(entries.len());
17337 let mut columns = Vec::with_capacity(entries.len());
17338 let mut defaults = Vec::with_capacity(entries.len());
17339 for ((p, c), v) in entries {
17340 positions.push(p);
17341 columns.push(c);
17342 defaults.push(v);
17343 }
17344 steps.push(FkChildStep {
17345 child_table,
17346 action: FkChildAction::SetDefault {
17347 positions,
17348 columns,
17349 defaults,
17350 },
17351 });
17352 }
17353 for (child_table, positions) in delete_plan {
17354 steps.push(FkChildStep {
17355 child_table,
17356 action: FkChildAction::Delete {
17357 positions: positions.into_iter().collect(),
17358 },
17359 });
17360 }
17361 Ok(steps)
17362}
17363
17364fn plan_fk_parent_updates(
17381 catalog: &Catalog,
17382 parent_table_name: &str,
17383 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
17384) -> Result<Vec<FkChildStep>, EngineError> {
17385 use alloc::collections::BTreeMap;
17386 if plan_with_old.is_empty() {
17387 return Ok(Vec::new());
17388 }
17389 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
17394 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
17395 BTreeMap::new();
17396 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17397 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17399
17400 for child_name in catalog.table_names() {
17401 let child = catalog
17402 .get(&child_name)
17403 .expect("table_names → catalog.get total");
17404 for fk in &child.schema().foreign_keys {
17405 if fk.parent_table != parent_table_name {
17406 continue;
17407 }
17408 for (_pos, old_row, new_row) in plan_with_old {
17409 let key_changed = fk
17411 .parent_columns
17412 .iter()
17413 .any(|&pi| old_row.get(pi) != new_row.get(pi));
17414 if !key_changed {
17415 continue;
17416 }
17417 let old_key: Vec<&Value> =
17419 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
17420 if old_key.iter().any(|v| matches!(v, Value::Null)) {
17421 continue;
17423 }
17424 let new_key: Vec<&Value> =
17425 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
17426 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
17427 if child_name == parent_table_name
17430 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
17431 {
17432 continue;
17433 }
17434 let matches_key = fk
17435 .local_columns
17436 .iter()
17437 .enumerate()
17438 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
17439 if !matches_key {
17440 continue;
17441 }
17442 match fk.on_update {
17443 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
17444 return Err(EngineError::Unsupported(alloc::format!(
17445 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
17446 restricted by FK from {child_name:?}.{:?}",
17447 fk.local_columns,
17448 )));
17449 }
17450 spg_storage::FkAction::Cascade => {
17451 let entry = cascade_plan.entry(child_name.clone()).or_default();
17453 for (i, &li) in fk.local_columns.iter().enumerate() {
17454 entry.insert((child_row_idx, li), new_key[i].clone());
17455 }
17456 }
17457 spg_storage::FkAction::SetNull => {
17458 for &li in &fk.local_columns {
17459 let col = child.schema().columns.get(li).ok_or_else(|| {
17460 EngineError::Unsupported(alloc::format!(
17461 "FK local column {li} missing in {child_name:?}"
17462 ))
17463 })?;
17464 if !col.nullable {
17465 return Err(EngineError::Unsupported(alloc::format!(
17466 "FOREIGN KEY ON UPDATE SET NULL: column \
17467 {child_name:?}.{:?} is NOT NULL",
17468 col.name,
17469 )));
17470 }
17471 }
17472 let entry = setnull_plan.entry(child_name.clone()).or_default();
17473 for &li in &fk.local_columns {
17474 entry.insert((child_row_idx, li));
17475 }
17476 }
17477 spg_storage::FkAction::SetDefault => {
17478 let entry = setdefault_plan.entry(child_name.clone()).or_default();
17479 for &li in &fk.local_columns {
17480 let col = child.schema().columns.get(li).ok_or_else(|| {
17481 EngineError::Unsupported(alloc::format!(
17482 "FK local column {li} missing in {child_name:?}"
17483 ))
17484 })?;
17485 let default = col.default.clone().ok_or_else(|| {
17486 EngineError::Unsupported(alloc::format!(
17487 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
17488 {child_name:?}.{:?} has no DEFAULT",
17489 col.name,
17490 ))
17491 })?;
17492 entry.insert((child_row_idx, li), default);
17493 }
17494 }
17495 }
17496 }
17497 }
17498 }
17499 }
17500 let mut steps: Vec<FkChildStep> = Vec::new();
17503 for (child_table, entries) in cascade_plan {
17504 let mut positions = Vec::with_capacity(entries.len());
17505 let mut columns = Vec::with_capacity(entries.len());
17506 let mut defaults = Vec::with_capacity(entries.len());
17507 for ((p, c), v) in entries {
17508 positions.push(p);
17509 columns.push(c);
17510 defaults.push(v);
17511 }
17512 steps.push(FkChildStep {
17517 child_table,
17518 action: FkChildAction::SetDefault {
17519 positions,
17520 columns,
17521 defaults,
17522 },
17523 });
17524 }
17525 for (child_table, entries) in setnull_plan {
17526 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
17527 steps.push(FkChildStep {
17528 child_table,
17529 action: FkChildAction::SetNull { positions, columns },
17530 });
17531 }
17532 for (child_table, entries) in setdefault_plan {
17533 let mut positions = Vec::with_capacity(entries.len());
17534 let mut columns = Vec::with_capacity(entries.len());
17535 let mut defaults = Vec::with_capacity(entries.len());
17536 for ((p, c), v) in entries {
17537 positions.push(p);
17538 columns.push(c);
17539 defaults.push(v);
17540 }
17541 steps.push(FkChildStep {
17542 child_table,
17543 action: FkChildAction::SetDefault {
17544 positions,
17545 columns,
17546 defaults,
17547 },
17548 });
17549 }
17550 let _ = delete_plan; Ok(steps)
17552}
17553
17554fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
17558 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
17559 EngineError::Storage(StorageError::TableNotFound {
17560 name: step.child_table.clone(),
17561 })
17562 })?;
17563 match &step.action {
17564 FkChildAction::Delete { positions } => {
17565 let _ = child.delete_rows(positions);
17566 }
17567 FkChildAction::SetNull { positions, columns } => {
17568 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
17569 }
17570 FkChildAction::SetDefault {
17571 positions,
17572 columns,
17573 defaults,
17574 } => {
17575 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
17576 }
17577 }
17578 Ok(())
17579}
17580
17581fn apply_per_cell_writes(
17587 child: &mut spg_storage::Table,
17588 positions: &[usize],
17589 columns: &[usize],
17590 mut value_for: impl FnMut(usize) -> Value,
17591) -> Result<(), EngineError> {
17592 use alloc::collections::BTreeMap;
17593 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
17594 for i in 0..positions.len() {
17595 by_row
17596 .entry(positions[i])
17597 .or_default()
17598 .push((columns[i], value_for(i)));
17599 }
17600 for (pos, mutations) in by_row {
17601 let mut new_values = child.rows()[pos].values.clone();
17602 for (col, v) in mutations {
17603 if let Some(slot) = new_values.get_mut(col) {
17604 *slot = v;
17605 }
17606 }
17607 child
17608 .update_row(pos, new_values)
17609 .map_err(EngineError::Storage)?;
17610 }
17611 Ok(())
17612}
17613
17614fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
17615 match a {
17616 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
17617 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
17618 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
17619 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
17620 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
17621 }
17622}
17623
17624fn resolve_column_default_free(
17630 col: &ColumnSchema,
17631 clock_fn: Option<ClockFn>,
17632) -> Result<Value, EngineError> {
17633 if let Some(rt) = &col.runtime_default {
17634 return eval_runtime_default_free(rt, col.ty, clock_fn);
17635 }
17636 Ok(col.default.clone().unwrap_or(Value::Null))
17637}
17638
17639fn eval_runtime_default_free(
17640 rt: &str,
17641 ty: DataType,
17642 clock_fn: Option<ClockFn>,
17643) -> Result<Value, EngineError> {
17644 let s = rt.trim().to_ascii_lowercase();
17645 let with_no_parens = s.trim_end_matches("()");
17651 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
17652 if with_no_parens.ends_with(')') {
17653 &with_no_parens[..open_idx]
17654 } else {
17655 with_no_parens
17656 }
17657 } else {
17658 with_no_parens
17659 };
17660 let now_us = match clock_fn {
17661 Some(f) => f(),
17662 None => 0,
17663 };
17664 let v = match canonical {
17665 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
17666 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
17667 "current_time" | "localtime" => Value::Timestamp(now_us),
17668 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
17674 other => {
17675 return Err(EngineError::Unsupported(alloc::format!(
17676 "runtime DEFAULT expression {other:?} not supported \
17677 (v7.17.0 whitelist: now() / current_timestamp / \
17678 current_date / current_time / localtimestamp / \
17679 localtime / gen_random_uuid() / \
17680 uuid_generate_v4())"
17681 )));
17682 }
17683 };
17684 coerce_value(v, ty, "DEFAULT", 0)
17685}
17686
17687fn is_runtime_default_expr(expr: &Expr) -> bool {
17693 match expr {
17694 Expr::FunctionCall { .. } => true,
17695 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
17696 _ => false,
17697 }
17698}
17699
17700fn canonicalize_set_value(
17713 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17714 col_idx: usize,
17715 col_name: &str,
17716 value: Value,
17717) -> Result<Value, EngineError> {
17718 let Some(variants) = lookup.get(&col_idx) else {
17719 return Ok(value);
17720 };
17721 match value {
17722 Value::Null => Ok(Value::Null),
17723 Value::Text(s) => {
17724 if s.is_empty() {
17725 return Ok(Value::Text(alloc::string::String::new()));
17726 }
17727 let mut present = alloc::vec![false; variants.len()];
17730 for raw in s.split(',') {
17731 let tok = raw.trim();
17732 if tok.is_empty() {
17733 continue;
17734 }
17735 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
17736 EngineError::Unsupported(alloc::format!(
17737 "column {col_name:?}: invalid SET token {tok:?}; \
17738 allowed: {variants:?}"
17739 ))
17740 })?;
17741 present[idx] = true;
17742 }
17743 let mut out = alloc::string::String::new();
17745 let mut first = true;
17746 for (i, keep) in present.iter().enumerate() {
17747 if !keep {
17748 continue;
17749 }
17750 if !first {
17751 out.push(',');
17752 }
17753 first = false;
17754 out.push_str(&variants[i]);
17755 }
17756 Ok(Value::Text(out))
17757 }
17758 other => Err(EngineError::Unsupported(alloc::format!(
17759 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
17760 other.data_type()
17761 ))),
17762 }
17763}
17764
17765fn enforce_enum_label(
17766 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17767 col_idx: usize,
17768 col_name: &str,
17769 value: &Value,
17770) -> Result<(), EngineError> {
17771 if let Some(labels) = lookup.get(&col_idx) {
17772 match value {
17773 Value::Null => Ok(()),
17774 Value::Text(s) => {
17775 if labels.iter().any(|l| l == s) {
17776 Ok(())
17777 } else {
17778 Err(EngineError::Unsupported(alloc::format!(
17779 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
17780 )))
17781 }
17782 }
17783 other => Err(EngineError::Unsupported(alloc::format!(
17784 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
17785 other.data_type()
17786 ))),
17787 }
17788 } else {
17789 Ok(())
17790 }
17791}
17792
17793fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
17794 let ty = column_type_to_data_type(c.ty);
17795 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
17796 if let Some(name) = c.user_type_ref {
17803 schema.user_enum_type = Some(name);
17804 }
17805 if let Some(expr) = c.on_update_runtime {
17808 schema.on_update_runtime = Some(alloc::format!("{expr}"));
17809 }
17810 schema.collation = match c.collation {
17814 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
17815 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
17816 };
17817 schema.is_unsigned = c.is_unsigned;
17820 schema.inline_enum_variants = c.inline_enum_variants;
17824 schema.inline_set_variants = c.inline_set_variants;
17828 if let Some(default_expr) = c.default {
17829 if is_runtime_default_expr(&default_expr) {
17835 let display = alloc::format!("{default_expr}");
17836 schema = schema.with_runtime_default(display);
17837 } else {
17838 let raw = literal_expr_to_value(default_expr)?;
17839 let coerced = coerce_value(raw, ty, &c.name, 0)?;
17840 schema = schema.with_default(coerced);
17841 }
17842 }
17843 if c.auto_increment {
17844 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
17846 return Err(EngineError::Unsupported(alloc::format!(
17847 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
17848 )));
17849 }
17850 schema = schema.with_auto_increment();
17851 }
17852 Ok(schema)
17853}
17854
17855fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
17860 let s = s.trim();
17861 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
17862 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
17864 if cleaned.len() % 2 != 0 {
17865 return Err("odd-length hex literal");
17866 }
17867 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
17868 let cleaned_bytes = cleaned.as_bytes();
17869 for i in (0..cleaned_bytes.len()).step_by(2) {
17870 let hi = hex_nibble(cleaned_bytes[i])?;
17871 let lo = hex_nibble(cleaned_bytes[i + 1])?;
17872 out.push((hi << 4) | lo);
17873 }
17874 return Ok(out);
17875 }
17876 let bytes = s.as_bytes();
17879 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
17880 let mut i = 0;
17881 while i < bytes.len() {
17882 let b = bytes[i];
17883 if b == b'\\' && i + 1 < bytes.len() {
17884 let n = bytes[i + 1];
17885 if n == b'\\' {
17886 out.push(b'\\');
17887 i += 2;
17888 continue;
17889 }
17890 if n.is_ascii_digit()
17891 && i + 3 < bytes.len()
17892 && bytes[i + 2].is_ascii_digit()
17893 && bytes[i + 3].is_ascii_digit()
17894 {
17895 let oct = |x: u8| (x - b'0') as u32;
17896 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
17897 if v <= 0xFF {
17898 out.push(v as u8);
17899 i += 4;
17900 continue;
17901 }
17902 }
17903 }
17904 out.push(b);
17905 i += 1;
17906 }
17907 Ok(out)
17908}
17909
17910fn hex_nibble(b: u8) -> Result<u8, &'static str> {
17911 match b {
17912 b'0'..=b'9' => Ok(b - b'0'),
17913 b'a'..=b'f' => Ok(b - b'a' + 10),
17914 b'A'..=b'F' => Ok(b - b'A' + 10),
17915 _ => Err("invalid hex digit"),
17916 }
17917}
17918
17919fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
17933 let mut has_text = false;
17934 let mut has_bigint = false;
17935 let mut has_int = false;
17936 for v in &items {
17937 match v {
17938 Value::Null => {}
17939 Value::Text(_) | Value::Json(_) => has_text = true,
17940 Value::BigInt(_) => has_bigint = true,
17941 Value::Int(_) | Value::SmallInt(_) => has_int = true,
17942 _ => has_text = true,
17943 }
17944 }
17945 if has_text || (!has_bigint && !has_int) {
17946 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
17947 .into_iter()
17948 .map(|v| match v {
17949 Value::Null => None,
17950 Value::Text(s) | Value::Json(s) => Some(s),
17951 other => Some(alloc::format!("{other:?}")),
17952 })
17953 .collect();
17954 return Value::TextArray(out);
17955 }
17956 if has_bigint {
17957 let out: alloc::vec::Vec<Option<i64>> = items
17958 .into_iter()
17959 .map(|v| match v {
17960 Value::Null => None,
17961 Value::Int(n) => Some(i64::from(n)),
17962 Value::SmallInt(n) => Some(i64::from(n)),
17963 Value::BigInt(n) => Some(n),
17964 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
17965 })
17966 .collect();
17967 return Value::BigIntArray(out);
17968 }
17969 let out: alloc::vec::Vec<Option<i32>> = items
17970 .into_iter()
17971 .map(|v| match v {
17972 Value::Null => None,
17973 Value::Int(n) => Some(n),
17974 Value::SmallInt(n) => Some(i32::from(n)),
17975 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
17976 })
17977 .collect();
17978 Value::IntArray(out)
17979}
17980
17981fn decode_text_array_literal(
17982 s: &str,
17983) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
17984 let trimmed = s.trim();
17985 let inner = trimmed
17986 .strip_prefix('{')
17987 .and_then(|x| x.strip_suffix('}'))
17988 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
17989 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
17990 if inner.trim().is_empty() {
17991 return Ok(out);
17992 }
17993 let bytes = inner.as_bytes();
17994 let mut i = 0;
17995 while i <= bytes.len() {
17996 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
17998 i += 1;
17999 }
18000 if i < bytes.len() && bytes[i] == b'"' {
18002 i += 1; let mut buf = alloc::string::String::new();
18004 while i < bytes.len() && bytes[i] != b'"' {
18005 if bytes[i] == b'\\' && i + 1 < bytes.len() {
18006 buf.push(bytes[i + 1] as char);
18007 i += 2;
18008 } else {
18009 buf.push(bytes[i] as char);
18010 i += 1;
18011 }
18012 }
18013 if i >= bytes.len() {
18014 return Err("unterminated quoted element");
18015 }
18016 i += 1; out.push(Some(buf));
18018 } else {
18019 let start = i;
18021 while i < bytes.len() && bytes[i] != b',' {
18022 i += 1;
18023 }
18024 let raw = inner[start..i].trim();
18025 if raw.eq_ignore_ascii_case("NULL") {
18026 out.push(None);
18027 } else {
18028 out.push(Some(alloc::string::ToString::to_string(raw)));
18029 }
18030 }
18031 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
18033 i += 1;
18034 }
18035 if i >= bytes.len() {
18036 break;
18037 }
18038 if bytes[i] != b',' {
18039 return Err("expected ',' between TEXT[] elements");
18040 }
18041 i += 1;
18042 }
18043 Ok(out)
18044}
18045
18046fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
18051 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
18052 out.push('{');
18053 for (i, item) in items.iter().enumerate() {
18054 if i > 0 {
18055 out.push(',');
18056 }
18057 match item {
18058 None => out.push_str("NULL"),
18059 Some(s) => {
18060 let needs_quote = s.is_empty()
18061 || s.eq_ignore_ascii_case("NULL")
18062 || s.chars()
18063 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
18064 if needs_quote {
18065 out.push('"');
18066 for c in s.chars() {
18067 if c == '"' || c == '\\' {
18068 out.push('\\');
18069 }
18070 out.push(c);
18071 }
18072 out.push('"');
18073 } else {
18074 out.push_str(s);
18075 }
18076 }
18077 }
18078 }
18079 out.push('}');
18080 out
18081}
18082
18083fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
18087 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
18088 out.push_str("\\x");
18089 for byte in b {
18090 let hi = byte >> 4;
18091 let lo = byte & 0x0F;
18092 out.push(hex_digit(hi));
18093 out.push(hex_digit(lo));
18094 }
18095 out
18096}
18097
18098const fn hex_digit(n: u8) -> char {
18099 match n {
18100 0..=9 => (b'0' + n) as char,
18101 10..=15 => (b'a' + n - 10) as char,
18102 _ => '?',
18103 }
18104}
18105
18106fn parse_hstore_str(
18118 s: &str,
18119) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
18120 let bytes = s.as_bytes();
18121 let mut i = 0;
18122 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
18123 let skip_ws = |bytes: &[u8], i: &mut usize| {
18124 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
18125 *i += 1;
18126 }
18127 };
18128 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
18129 if *i >= bytes.len() {
18130 return None;
18131 }
18132 if bytes[*i] == b'"' {
18133 *i += 1;
18134 let mut out = alloc::string::String::new();
18135 while *i < bytes.len() {
18136 match bytes[*i] {
18137 b'"' => {
18138 *i += 1;
18139 return Some(out);
18140 }
18141 b'\\' if *i + 1 < bytes.len() => {
18142 out.push(bytes[*i + 1] as char);
18143 *i += 2;
18144 }
18145 c => {
18146 out.push(c as char);
18147 *i += 1;
18148 }
18149 }
18150 }
18151 None
18152 } else {
18153 let start = *i;
18154 while *i < bytes.len()
18155 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
18156 {
18157 *i += 1;
18158 }
18159 if *i == start {
18160 return None;
18161 }
18162 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
18163 }
18164 };
18165 skip_ws(bytes, &mut i);
18166 while i < bytes.len() {
18167 let key = parse_token(bytes, &mut i)?;
18168 skip_ws(bytes, &mut i);
18169 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
18170 return None;
18171 }
18172 i += 2;
18173 skip_ws(bytes, &mut i);
18174 let val_token = if i + 4 <= bytes.len()
18176 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
18177 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
18178 {
18179 i += 4;
18180 None
18181 } else {
18182 Some(parse_token(bytes, &mut i)?)
18183 };
18184 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
18186 out[pos] = (key, val_token);
18187 } else {
18188 out.push((key, val_token));
18189 }
18190 skip_ws(bytes, &mut i);
18191 if i >= bytes.len() {
18192 break;
18193 }
18194 if bytes[i] == b',' {
18195 i += 1;
18196 skip_ws(bytes, &mut i);
18197 continue;
18198 }
18199 return None;
18200 }
18201 Some(out)
18202}
18203
18204fn format_hstore_str(
18208 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
18209) -> alloc::string::String {
18210 let mut out = alloc::string::String::new();
18211 for (i, (k, v)) in pairs.iter().enumerate() {
18212 if i > 0 {
18213 out.push_str(", ");
18214 }
18215 out.push('"');
18216 out.push_str(k);
18217 out.push_str("\"=>");
18218 match v {
18219 None => out.push_str("NULL"),
18220 Some(val) => {
18221 out.push('"');
18222 out.push_str(val);
18223 out.push('"');
18224 }
18225 }
18226 }
18227 out
18228}
18229
18230pub fn format_hstore_text(
18233 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
18234) -> alloc::string::String {
18235 format_hstore_str(pairs)
18236}
18237
18238fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
18243 let s = s.trim();
18244 let outer = s
18245 .strip_prefix('{')
18246 .and_then(|x| x.strip_suffix('}'))
18247 .ok_or("missing outer '{...}' braces")?;
18248 let trimmed = outer.trim();
18249 if trimmed.is_empty() {
18250 return Ok(Vec::new());
18251 }
18252 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
18253 let mut i = 0;
18254 let bytes = trimmed.as_bytes();
18255 while i < bytes.len() {
18256 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
18257 i += 1;
18258 }
18259 if i >= bytes.len() {
18260 break;
18261 }
18262 if bytes[i] != b'{' {
18263 return Err("expected '{' opening a row");
18264 }
18265 i += 1;
18266 let row_start = i;
18267 let mut depth = 1;
18268 while i < bytes.len() && depth > 0 {
18269 match bytes[i] {
18270 b'{' => depth += 1,
18271 b'}' => depth -= 1,
18272 _ => {}
18273 }
18274 if depth > 0 {
18275 i += 1;
18276 }
18277 }
18278 if depth != 0 {
18279 return Err("unbalanced '{...}' in row");
18280 }
18281 let row_text = &trimmed[row_start..i];
18282 i += 1;
18283 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
18284 Vec::new()
18285 } else {
18286 row_text.split(',').map(|t| t.trim().to_string()).collect()
18287 };
18288 rows.push(cells);
18289 }
18290 if let Some(first) = rows.first() {
18291 let cols = first.len();
18292 for r in &rows {
18293 if r.len() != cols {
18294 return Err("ragged 2D array (rows have different column counts)");
18295 }
18296 }
18297 }
18298 Ok(rows)
18299}
18300
18301fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
18302 let raw = split_2d_literal(s)?;
18303 raw.into_iter()
18304 .map(|row| {
18305 row.into_iter()
18306 .map(|cell| {
18307 if cell.eq_ignore_ascii_case("NULL") {
18308 Ok(None)
18309 } else {
18310 cell.parse::<i32>()
18311 .map(Some)
18312 .map_err(|_| "invalid int element")
18313 }
18314 })
18315 .collect()
18316 })
18317 .collect()
18318}
18319
18320fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
18321 let raw = split_2d_literal(s)?;
18322 raw.into_iter()
18323 .map(|row| {
18324 row.into_iter()
18325 .map(|cell| {
18326 if cell.eq_ignore_ascii_case("NULL") {
18327 Ok(None)
18328 } else {
18329 cell.parse::<i64>()
18330 .map(Some)
18331 .map_err(|_| "invalid bigint element")
18332 }
18333 })
18334 .collect()
18335 })
18336 .collect()
18337}
18338
18339fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
18340 let raw = split_2d_literal(s)?;
18341 Ok(raw
18342 .into_iter()
18343 .map(|row| {
18344 row.into_iter()
18345 .map(|cell| {
18346 if cell.eq_ignore_ascii_case("NULL") {
18347 None
18348 } else {
18349 Some(cell.trim_matches('"').to_string())
18350 }
18351 })
18352 .collect()
18353 })
18354 .collect())
18355}
18356
18357fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
18358 let mut out = alloc::string::String::from("{");
18359 for (i, row) in rows.iter().enumerate() {
18360 if i > 0 {
18361 out.push(',');
18362 }
18363 out.push('{');
18364 for (j, cell) in row.iter().enumerate() {
18365 if j > 0 {
18366 out.push(',');
18367 }
18368 match cell {
18369 None => out.push_str("NULL"),
18370 Some(n) => out.push_str(&alloc::format!("{n}")),
18371 }
18372 }
18373 out.push('}');
18374 }
18375 out.push('}');
18376 out
18377}
18378
18379fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
18380 let mut out = alloc::string::String::from("{");
18381 for (i, row) in rows.iter().enumerate() {
18382 if i > 0 {
18383 out.push(',');
18384 }
18385 out.push('{');
18386 for (j, cell) in row.iter().enumerate() {
18387 if j > 0 {
18388 out.push(',');
18389 }
18390 match cell {
18391 None => out.push_str("NULL"),
18392 Some(n) => out.push_str(&alloc::format!("{n}")),
18393 }
18394 }
18395 out.push('}');
18396 }
18397 out.push('}');
18398 out
18399}
18400
18401fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
18402 let mut out = alloc::string::String::from("{");
18403 for (i, row) in rows.iter().enumerate() {
18404 if i > 0 {
18405 out.push(',');
18406 }
18407 out.push('{');
18408 for (j, cell) in row.iter().enumerate() {
18409 if j > 0 {
18410 out.push(',');
18411 }
18412 match cell {
18413 None => out.push_str("NULL"),
18414 Some(s) => out.push_str(s),
18415 }
18416 }
18417 out.push('}');
18418 }
18419 out.push('}');
18420 out
18421}
18422
18423pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
18426 format_int_2d_text(rows)
18427}
18428pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
18429 format_bigint_2d_text(rows)
18430}
18431pub fn format_text_2d_text_pub(
18432 rows: &[Vec<Option<alloc::string::String>>],
18433) -> alloc::string::String {
18434 format_text_2d_text(rows)
18435}
18436
18437fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
18442 let s = s.trim();
18443 if s.eq_ignore_ascii_case("empty") {
18444 return Some(Value::Range {
18445 kind,
18446 lower: None,
18447 upper: None,
18448 lower_inc: false,
18449 upper_inc: false,
18450 empty: true,
18451 });
18452 }
18453 let bytes = s.as_bytes();
18454 if bytes.len() < 3 {
18455 return None;
18456 }
18457 let lower_inc = match bytes[0] {
18458 b'[' => true,
18459 b'(' => false,
18460 _ => return None,
18461 };
18462 let upper_inc = match bytes[bytes.len() - 1] {
18463 b']' => true,
18464 b')' => false,
18465 _ => return None,
18466 };
18467 let inner = &s[1..s.len() - 1];
18468 let (lo_text, up_text) = inner.split_once(',')?;
18469 let lower = if lo_text.is_empty() {
18470 None
18471 } else {
18472 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
18473 };
18474 let upper = if up_text.is_empty() {
18475 None
18476 } else {
18477 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
18478 };
18479 Some(Value::Range {
18480 kind,
18481 lower,
18482 upper,
18483 lower_inc,
18484 upper_inc,
18485 empty: false,
18486 })
18487}
18488
18489fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
18492 let text = text.trim().trim_matches('"');
18493 use spg_storage::RangeKind as K;
18494 match kind {
18495 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
18496 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
18497 K::Num => {
18498 let dot = text.find('.');
18501 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
18502 let digits: alloc::string::String = text
18503 .chars()
18504 .filter(|c| *c == '-' || c.is_ascii_digit())
18505 .collect();
18506 let scaled: i128 = digits.parse().ok()?;
18507 Some(Value::Numeric { scaled, scale })
18508 }
18509 K::Ts | K::TsTz => {
18510 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
18515 }
18516 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
18517 }
18518}
18519
18520pub fn format_range_text(v: &Value) -> alloc::string::String {
18524 format_range_str(v)
18525}
18526
18527fn format_range_str(v: &Value) -> alloc::string::String {
18528 let Value::Range {
18529 lower,
18530 upper,
18531 lower_inc,
18532 upper_inc,
18533 empty,
18534 ..
18535 } = v
18536 else {
18537 return alloc::string::String::new();
18538 };
18539 if *empty {
18540 return "empty".into();
18541 }
18542 let mut out = alloc::string::String::new();
18543 out.push(if *lower_inc { '[' } else { '(' });
18544 if let Some(l) = lower {
18545 out.push_str(&format_range_element(l));
18546 }
18547 out.push(',');
18548 if let Some(u) = upper {
18549 out.push_str(&format_range_element(u));
18550 }
18551 out.push(if *upper_inc { ']' } else { ')' });
18552 out
18553}
18554
18555fn format_range_element(v: &Value) -> alloc::string::String {
18556 match v {
18557 Value::Int(n) => alloc::format!("{n}"),
18558 Value::BigInt(n) => alloc::format!("{n}"),
18559 Value::Date(d) => crate::eval::format_date(*d),
18560 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
18561 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
18562 other => alloc::format!("{other:?}"),
18563 }
18564}
18565
18566fn parse_money_str(s: &str) -> Option<i64> {
18577 let s = s.trim();
18578 let (neg, rest) = match s.strip_prefix('-') {
18579 Some(r) => (true, r.trim_start()),
18580 None => (false, s),
18581 };
18582 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
18583 let (int_part, frac_part) = match rest.split_once('.') {
18584 Some((i, f)) => (i, Some(f)),
18585 None => (rest, None),
18586 };
18587 if int_part.is_empty() {
18588 return None;
18589 }
18590 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
18592 for b in int_part.bytes() {
18593 match b {
18594 b',' => {}
18595 b'0'..=b'9' => int_digits.push(b as char),
18596 _ => return None,
18597 }
18598 }
18599 if int_digits.is_empty() {
18600 return None;
18601 }
18602 let dollars: i64 = int_digits.parse().ok()?;
18603 let cents: i64 = match frac_part {
18604 None => 0,
18605 Some(f) => {
18606 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
18607 return None;
18608 }
18609 let padded = if f.len() == 1 {
18610 alloc::format!("{f}0")
18611 } else {
18612 f.to_string()
18613 };
18614 padded.parse().ok()?
18615 }
18616 };
18617 let total = dollars.checked_mul(100)?.checked_add(cents)?;
18618 Some(if neg { -total } else { total })
18619}
18620
18621fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
18632 let s = s.trim();
18633 let bytes = s.as_bytes();
18637 let sign_pos = bytes
18638 .iter()
18639 .enumerate()
18640 .rev()
18641 .find(|&(_, &b)| b == b'+' || b == b'-')
18642 .map(|(i, _)| i)?;
18643 if sign_pos == 0 {
18644 return None; }
18646 let time_part = &s[..sign_pos];
18647 let offset_part = &s[sign_pos..];
18648 let us = parse_time_str(time_part)?;
18649 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
18650 let offset_body = &offset_part[1..];
18651 let (hh_str, mm_str) = match offset_body.split_once(':') {
18652 Some((h, m)) => (h, m),
18653 None => (offset_body, "0"),
18654 };
18655 let hh: i32 = hh_str.parse().ok()?;
18656 let mm: i32 = mm_str.parse().ok()?;
18657 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
18658 return None;
18659 }
18660 let total = sign * (hh * 3600 + mm * 60);
18661 if total.abs() > 50_400 {
18662 return None;
18663 }
18664 Some((us, total))
18665}
18666
18667fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
18672 if n == 0 || (1901..=2155).contains(&n) {
18673 return Ok(Value::Year(n as u16));
18676 }
18677 Err(EngineError::Eval(EvalError::TypeMismatch {
18678 detail: alloc::format!(
18679 "year value out of range: {n} (column `{col_name}`; \
18680 MySQL accepts 0 or 1901..=2155)"
18681 ),
18682 }))
18683}
18684
18685fn parse_time_str(s: &str) -> Option<i64> {
18697 let s = s.trim();
18698 let (hms, frac) = match s.split_once('.') {
18699 Some((h, f)) => (h, Some(f)),
18700 None => (s, None),
18701 };
18702 let mut parts = hms.split(':');
18703 let hh: u32 = parts.next()?.parse().ok()?;
18704 let mm: u32 = parts.next()?.parse().ok()?;
18705 let ss: u32 = parts.next()?.parse().ok()?;
18706 if parts.next().is_some() {
18707 return None;
18708 }
18709 if hh > 23 || mm > 59 || ss > 59 {
18710 return None;
18711 }
18712 let frac_us: i64 = match frac {
18713 None => 0,
18714 Some(f) => {
18715 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
18716 return None;
18717 }
18718 let mut padded = alloc::string::String::with_capacity(6);
18720 padded.push_str(f);
18721 while padded.len() < 6 {
18722 padded.push('0');
18723 }
18724 padded.parse().ok()?
18725 }
18726 };
18727 Some(
18728 i64::from(hh) * 3_600_000_000
18729 + i64::from(mm) * 60_000_000
18730 + i64::from(ss) * 1_000_000
18731 + frac_us,
18732 )
18733}
18734
18735const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
18736 match t {
18737 ColumnTypeName::SmallInt => DataType::SmallInt,
18738 ColumnTypeName::Int => DataType::Int,
18739 ColumnTypeName::BigInt => DataType::BigInt,
18740 ColumnTypeName::Float => DataType::Float,
18741 ColumnTypeName::Text => DataType::Text,
18742 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
18743 ColumnTypeName::Char(n) => DataType::Char(n),
18744 ColumnTypeName::Bool => DataType::Bool,
18745 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
18746 dim,
18747 encoding: match encoding {
18748 SqlVecEncoding::F32 => VecEncoding::F32,
18749 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
18750 SqlVecEncoding::F16 => VecEncoding::F16,
18751 },
18752 },
18753 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
18754 ColumnTypeName::Date => DataType::Date,
18755 ColumnTypeName::Timestamp => DataType::Timestamp,
18756 ColumnTypeName::Timestamptz => DataType::Timestamptz,
18757 ColumnTypeName::Json => DataType::Json,
18758 ColumnTypeName::Jsonb => DataType::Jsonb,
18759 ColumnTypeName::Bytes => DataType::Bytes,
18760 ColumnTypeName::TextArray => DataType::TextArray,
18761 ColumnTypeName::IntArray => DataType::IntArray,
18762 ColumnTypeName::BigIntArray => DataType::BigIntArray,
18763 ColumnTypeName::TsVector => DataType::TsVector,
18764 ColumnTypeName::TsQuery => DataType::TsQuery,
18765 ColumnTypeName::Uuid => DataType::Uuid,
18766 ColumnTypeName::Time => DataType::Time,
18767 ColumnTypeName::Year => DataType::Year,
18768 ColumnTypeName::TimeTz => DataType::TimeTz,
18769 ColumnTypeName::Money => DataType::Money,
18770 ColumnTypeName::Range(k) => DataType::Range(match k {
18771 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
18772 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
18773 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
18774 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
18775 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
18776 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
18777 }),
18778 ColumnTypeName::Hstore => DataType::Hstore,
18779 ColumnTypeName::IntArray2D => DataType::IntArray2D,
18780 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
18781 ColumnTypeName::TextArray2D => DataType::TextArray2D,
18782 }
18783}
18784
18785fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
18789 match expr {
18790 Expr::Literal(l) => Ok(literal_to_value(l)),
18791 Expr::Cast { expr, target } => {
18792 let inner_value = literal_expr_to_value(*expr)?;
18793 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
18794 }
18795 Expr::Unary {
18796 op: UnOp::Neg,
18797 expr,
18798 } => match *expr {
18799 Expr::Literal(Literal::Integer(n)) => {
18800 let neg = n.checked_neg().ok_or_else(|| {
18803 EngineError::Unsupported("integer literal overflow on negation".into())
18804 })?;
18805 Ok(int_value_for(neg))
18806 }
18807 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
18808 other => Err(EngineError::Unsupported(alloc::format!(
18809 "unary minus over non-literal expression: {other:?}"
18810 ))),
18811 },
18812 Expr::Array(items) => {
18820 let mut materialised: alloc::vec::Vec<Value> =
18821 alloc::vec::Vec::with_capacity(items.len());
18822 for elem in items {
18823 materialised.push(literal_expr_to_value(elem)?);
18824 }
18825 Ok(array_literal_widen(materialised))
18826 }
18827 other => {
18840 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
18841 let ctx = EvalContext::new(&empty_schema, None);
18842 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
18843 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
18844 }
18845 }
18846}
18847
18848fn literal_to_value(l: Literal) -> Value {
18849 match l {
18850 Literal::Integer(n) => int_value_for(n),
18851 Literal::Float(x) => Value::Float(x),
18852 Literal::String(s) => Value::Text(s),
18853 Literal::Bool(b) => Value::Bool(b),
18854 Literal::Null => Value::Null,
18855 Literal::Vector(v) => Value::Vector(v),
18856 Literal::TextArray(items) => Value::TextArray(items),
18857 Literal::IntArray(items) => Value::IntArray(items),
18858 Literal::BigIntArray(items) => Value::BigIntArray(items),
18859 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
18860 }
18861}
18862
18863fn int_value_for(n: i64) -> Value {
18867 if let Ok(small) = i32::try_from(n) {
18868 Value::Int(small)
18869 } else {
18870 Value::BigInt(n)
18871 }
18872}
18873
18874#[allow(clippy::too_many_lines)]
18880fn check_unsigned_range(
18885 v: &Value,
18886 schema: &ColumnSchema,
18887 position: usize,
18888) -> Result<(), EngineError> {
18889 if !schema.is_unsigned {
18890 return Ok(());
18891 }
18892 let n = match v {
18893 Value::SmallInt(x) => i64::from(*x),
18894 Value::Int(x) => i64::from(*x),
18895 Value::BigInt(x) => *x,
18896 _ => return Ok(()), };
18898 if n < 0 {
18899 return Err(EngineError::Unsupported(alloc::format!(
18900 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
18901 schema.name
18902 )));
18903 }
18904 Ok(())
18905}
18906
18907fn coerce_value(
18908 v: Value,
18909 expected: DataType,
18910 col_name: &str,
18911 position: usize,
18912) -> Result<Value, EngineError> {
18913 if v.is_null() {
18914 return Ok(Value::Null);
18915 }
18916 let actual = v.data_type().expect("non-null");
18917 if actual == expected {
18918 return Ok(v);
18919 }
18920 let coerced = match (v, expected) {
18921 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
18922 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
18923 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
18924 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18925 i128::from(n),
18926 precision,
18927 scale,
18928 col_name,
18929 )?),
18930 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
18931 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
18932 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
18933 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18934 i128::from(n),
18935 precision,
18936 scale,
18937 col_name,
18938 )?),
18939 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
18940 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
18941 #[allow(clippy::cast_precision_loss)]
18942 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
18943 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18944 i128::from(n),
18945 precision,
18946 scale,
18947 col_name,
18948 )?),
18949 (Value::Float(x), DataType::Numeric { precision, scale }) => {
18950 Some(numeric_from_float(x, precision, scale, col_name)?)
18951 }
18952 (Value::Text(s), DataType::Numeric { precision, scale }) => {
18963 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
18964 return Err(EngineError::Eval(EvalError::TypeMismatch {
18965 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
18966 }));
18967 };
18968 Some(numeric_rescale(
18969 mantissa, src_scale, precision, scale, col_name,
18970 )?)
18971 }
18972 (Value::Text(s), DataType::Date) => {
18974 let d = eval::parse_date_literal(&s).ok_or_else(|| {
18975 EngineError::Eval(EvalError::TypeMismatch {
18976 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
18977 })
18978 })?;
18979 Some(Value::Date(d))
18980 }
18981 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
18988 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
18989 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
18990 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
18991 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
18992 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
18993 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
18994 _ => None,
18995 },
18996 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19005 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19006 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19007 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
19011 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
19012 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
19020 (Value::Text(s), DataType::Bytes) => {
19027 let bytes = decode_bytea_literal(&s).map_err(|e| {
19028 EngineError::Eval(EvalError::TypeMismatch {
19029 detail: alloc::format!(
19030 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
19031 ),
19032 })
19033 })?;
19034 Some(Value::Bytes(bytes))
19035 }
19036 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
19040 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
19048 Some(b) => Some(Value::Uuid(b)),
19049 None => {
19050 return Err(EngineError::Eval(EvalError::TypeMismatch {
19051 detail: alloc::format!(
19052 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
19053 ),
19054 }));
19055 }
19056 },
19057 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
19062 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
19068 Some(us) => Some(Value::Time(us)),
19069 None => {
19070 return Err(EngineError::Eval(EvalError::TypeMismatch {
19071 detail: alloc::format!(
19072 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
19073 ),
19074 }));
19075 }
19076 },
19077 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
19079 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
19084 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
19085 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
19086 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
19090 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
19091 Err(_) => {
19092 return Err(EngineError::Eval(EvalError::TypeMismatch {
19093 detail: alloc::format!(
19094 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
19095 ),
19096 }));
19097 }
19098 },
19099 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
19101 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
19105 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
19106 None => {
19107 return Err(EngineError::Eval(EvalError::TypeMismatch {
19108 detail: alloc::format!(
19109 "invalid input syntax for type time with time zone: \
19110 {s:?} (column `{col_name}`)"
19111 ),
19112 }));
19113 }
19114 },
19115 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
19117 Some(Value::Text(eval::format_timetz(us, offset_secs)))
19118 }
19119 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
19123 Some(c) => Some(Value::Money(c)),
19124 None => {
19125 return Err(EngineError::Eval(EvalError::TypeMismatch {
19126 detail: alloc::format!(
19127 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
19128 ),
19129 }));
19130 }
19131 },
19132 (Value::SmallInt(n), DataType::Money) => {
19136 Some(Value::Money(i64::from(n).saturating_mul(100)))
19137 }
19138 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
19139 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
19140 (Value::Float(x), DataType::Money) => {
19141 let scaled = x * 100.0;
19144 let cents = if scaled >= 0.0 {
19145 (scaled + 0.5) as i64
19146 } else {
19147 (scaled - 0.5) as i64
19148 };
19149 Some(Value::Money(cents))
19150 }
19151 (Value::Numeric { scaled, scale }, DataType::Money) => {
19152 let cents = if scale == 2 {
19155 scaled
19156 } else if scale < 2 {
19157 let mult = 10_i128.pow(u32::from(2 - scale));
19158 scaled.saturating_mul(mult)
19159 } else {
19160 let div = 10_i128.pow(u32::from(scale - 2));
19161 let half = div / 2;
19162 let bias = if scaled >= 0 { half } else { -half };
19163 (scaled + bias) / div
19164 };
19165 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
19166 }
19167 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
19169 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
19173 Some(v) => Some(v),
19174 None => {
19175 return Err(EngineError::Eval(EvalError::TypeMismatch {
19176 detail: alloc::format!(
19177 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
19178 ),
19179 }));
19180 }
19181 },
19182 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
19184 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
19186 Some(pairs) => Some(Value::Hstore(pairs)),
19187 None => {
19188 return Err(EngineError::Eval(EvalError::TypeMismatch {
19189 detail: alloc::format!(
19190 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
19191 ),
19192 }));
19193 }
19194 },
19195 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
19197 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
19200 Ok(m) => Some(Value::IntArray2D(m)),
19201 Err(e) => {
19202 return Err(EngineError::Eval(EvalError::TypeMismatch {
19203 detail: alloc::format!(
19204 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
19205 ),
19206 }));
19207 }
19208 },
19209 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
19210 Ok(m) => Some(Value::BigIntArray2D(m)),
19211 Err(e) => {
19212 return Err(EngineError::Eval(EvalError::TypeMismatch {
19213 detail: alloc::format!(
19214 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
19215 ),
19216 }));
19217 }
19218 },
19219 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
19220 Ok(m) => Some(Value::TextArray2D(m)),
19221 Err(e) => {
19222 return Err(EngineError::Eval(EvalError::TypeMismatch {
19223 detail: alloc::format!(
19224 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
19225 ),
19226 }));
19227 }
19228 },
19229 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
19231 (Value::BigIntArray2D(rows), DataType::Text) => {
19232 Some(Value::Text(format_bigint_2d_text(&rows)))
19233 }
19234 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
19235 (Value::Text(s), DataType::TextArray) => {
19240 let arr = decode_text_array_literal(&s).map_err(|e| {
19241 EngineError::Eval(EvalError::TypeMismatch {
19242 detail: alloc::format!(
19243 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
19244 ),
19245 })
19246 })?;
19247 Some(Value::TextArray(arr))
19248 }
19249 (Value::Text(s), DataType::IntArray) => {
19255 let arr = decode_text_array_literal(&s).map_err(|e| {
19256 EngineError::Eval(EvalError::TypeMismatch {
19257 detail: alloc::format!(
19258 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
19259 ),
19260 })
19261 })?;
19262 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
19263 for elem in arr {
19264 match elem {
19265 None => out.push(None),
19266 Some(t) => {
19267 let n: i32 = t.parse().map_err(|_| {
19268 EngineError::Eval(EvalError::TypeMismatch {
19269 detail: alloc::format!(
19270 "cannot parse {t:?} as INT element for `{col_name}`"
19271 ),
19272 })
19273 })?;
19274 out.push(Some(n));
19275 }
19276 }
19277 }
19278 Some(Value::IntArray(out))
19279 }
19280 (Value::Text(s), DataType::BigIntArray) => {
19281 let arr = decode_text_array_literal(&s).map_err(|e| {
19282 EngineError::Eval(EvalError::TypeMismatch {
19283 detail: alloc::format!(
19284 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
19285 ),
19286 })
19287 })?;
19288 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
19289 for elem in arr {
19290 match elem {
19291 None => out.push(None),
19292 Some(t) => {
19293 let n: i64 = t.parse().map_err(|_| {
19294 EngineError::Eval(EvalError::TypeMismatch {
19295 detail: alloc::format!(
19296 "cannot parse {t:?} as BIGINT element for `{col_name}`"
19297 ),
19298 })
19299 })?;
19300 out.push(Some(n));
19301 }
19302 }
19303 }
19304 Some(Value::BigIntArray(out))
19305 }
19306 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
19310 (Value::Text(s), DataType::Vector { dim, encoding }) => {
19319 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
19320 EngineError::Eval(EvalError::TypeMismatch {
19321 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
19322 })
19323 })?;
19324 if parsed.len() != dim as usize {
19325 return Err(EngineError::Eval(EvalError::TypeMismatch {
19326 detail: alloc::format!(
19327 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
19328 parsed.len()
19329 ),
19330 }));
19331 }
19332 Some(match encoding {
19333 VecEncoding::F32 => Value::Vector(parsed),
19334 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
19335 VecEncoding::F16 => {
19336 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
19337 }
19338 })
19339 }
19340 (Value::Text(s), DataType::TsVector) => {
19350 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
19351 EngineError::Eval(EvalError::TypeMismatch {
19352 detail: alloc::format!(
19353 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
19354 ),
19355 })
19356 })?;
19357 Some(Value::TsVector(lexs))
19358 }
19359 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
19360 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
19361 EngineError::Eval(EvalError::TypeMismatch {
19362 detail: alloc::format!(
19363 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
19364 ),
19365 })
19366 })?;
19367 Some(Value::Timestamp(t))
19368 }
19369 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
19372 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
19373 }
19374 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
19378 (Value::Timestamp(t), DataType::Date) => {
19379 let days = t.div_euclid(86_400_000_000);
19380 i32::try_from(days).ok().map(Value::Date)
19381 }
19382 (
19383 Value::Numeric {
19384 scaled,
19385 scale: src_scale,
19386 },
19387 DataType::Numeric { precision, scale },
19388 ) => Some(numeric_rescale(
19389 scaled, src_scale, precision, scale, col_name,
19390 )?),
19391 #[allow(clippy::cast_precision_loss)]
19392 (Value::Numeric { scaled, scale }, DataType::Float) => {
19393 let mut div = 1.0_f64;
19394 for _ in 0..scale {
19395 div *= 10.0;
19396 }
19397 Some(Value::Float((scaled as f64) / div))
19398 }
19399 (Value::Numeric { scaled, scale }, DataType::Int) => {
19400 let truncated = numeric_truncate_to_integer(scaled, scale);
19401 i32::try_from(truncated).ok().map(Value::Int)
19402 }
19403 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
19404 let truncated = numeric_truncate_to_integer(scaled, scale);
19405 i64::try_from(truncated).ok().map(Value::BigInt)
19406 }
19407 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
19408 let truncated = numeric_truncate_to_integer(scaled, scale);
19409 i16::try_from(truncated).ok().map(Value::SmallInt)
19410 }
19411 (Value::Text(s), DataType::Varchar(max)) => {
19413 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
19414 Some(Value::Text(s))
19415 } else {
19416 return Err(EngineError::Unsupported(alloc::format!(
19417 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
19418 {} chars",
19419 s.chars().count()
19420 )));
19421 }
19422 }
19423 (
19431 Value::Vector(v),
19432 DataType::Vector {
19433 dim,
19434 encoding: VecEncoding::Sq8,
19435 },
19436 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
19437 (
19442 Value::Vector(v),
19443 DataType::Vector {
19444 dim,
19445 encoding: VecEncoding::F16,
19446 },
19447 ) if v.len() == dim as usize => Some(Value::HalfVector(
19448 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
19449 )),
19450 (Value::Text(s), DataType::Char(size)) => {
19454 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
19455 if len > size {
19456 return Err(EngineError::Unsupported(alloc::format!(
19457 "value for CHAR({size}) column `{col_name}` exceeds length: \
19458 {len} chars"
19459 )));
19460 }
19461 let need = (size - len) as usize;
19462 let mut padded = s;
19463 padded.reserve(need);
19464 for _ in 0..need {
19465 padded.push(' ');
19466 }
19467 Some(Value::Text(padded))
19468 }
19469 _ => None,
19470 };
19471 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
19472 column: col_name.into(),
19473 expected,
19474 actual,
19475 position,
19476 }))
19477}
19478
19479fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
19485 use core::fmt::Write;
19486 let mut out = alloc::string::String::from("(");
19487 for (i, a) in args.iter().enumerate() {
19488 if i > 0 {
19489 out.push_str(", ");
19490 }
19491 match a.mode {
19492 spg_sql::ast::FunctionArgMode::In => {}
19493 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
19494 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
19495 }
19496 if let Some(n) = &a.name {
19497 out.push_str(n);
19498 out.push(' ');
19499 }
19500 match &a.ty {
19501 spg_sql::ast::FunctionArgType::Typed(t) => {
19502 let _ = write!(out, "{t}");
19503 }
19504 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
19505 }
19506 }
19507 out.push(')');
19508 out
19509}
19510
19511fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
19520 match expr {
19521 spg_sql::ast::Expr::FunctionCall { name, args } => {
19522 name.eq_ignore_ascii_case("unnest") && args.len() == 1
19523 }
19524 _ => false,
19525 }
19526}
19527
19528fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
19532 match expr {
19533 spg_sql::ast::Expr::FunctionCall { name, args }
19534 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
19535 {
19536 Some(&args[0])
19537 }
19538 _ => None,
19539 }
19540}
19541
19542fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
19547 match v {
19548 Value::Null => Ok(Vec::new()),
19549 Value::TextArray(items) => Ok(items
19550 .iter()
19551 .map(|opt| {
19552 opt.as_ref()
19553 .map(|s| Value::Text(s.clone()))
19554 .unwrap_or(Value::Null)
19555 })
19556 .collect()),
19557 Value::IntArray(items) => Ok(items
19558 .iter()
19559 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
19560 .collect()),
19561 Value::BigIntArray(items) => Ok(items
19562 .iter()
19563 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
19564 .collect()),
19565 other => Err(EngineError::Eval(EvalError::TypeMismatch {
19566 detail: alloc::format!(
19567 "unnest() expects an array argument, got {:?}",
19568 other.data_type()
19569 ),
19570 })),
19571 }
19572}
19573
19574#[cfg(test)]
19575mod tests {
19576 use super::*;
19577 use alloc::vec;
19578
19579 fn unwrap_command_ok(r: &QueryResult) -> usize {
19580 match r {
19581 QueryResult::CommandOk { affected, .. } => *affected,
19582 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
19583 }
19584 }
19585
19586 #[test]
19587 fn update_seek_positions_engages_on_indexed_eq() {
19588 let mut e = Engine::new();
19589 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
19590 .unwrap();
19591 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
19592 for i in 0..100 {
19593 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
19594 .unwrap();
19595 }
19596 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
19597 .expect("parse");
19598 let Statement::Update(u) = stmt else {
19599 panic!("expected Update, got {stmt:?}");
19600 };
19601 let w = u.where_.as_ref().expect("where");
19602 let table = e.catalog().get("b").unwrap();
19603 let schema_cols = table.schema().columns.clone();
19604 let Expr::Binary { lhs, op, rhs } = w else {
19606 panic!("WHERE not Binary: {w:?}");
19607 };
19608 assert_eq!(*op, BinOp::Eq, "op not Eq");
19609 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
19610 assert!(
19611 pair.is_some(),
19612 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
19613 );
19614 let (col_pos, value) = pair.unwrap();
19615 assert!(
19616 table.index_on(col_pos).is_some(),
19617 "no index on col {col_pos}"
19618 );
19619 assert!(
19620 IndexKey::from_value(&value).is_some(),
19621 "IndexKey::from_value None for {value:?}"
19622 );
19623 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
19624 assert_eq!(positions, Some(vec![42]), "seek did not engage");
19625 }
19626
19627 #[test]
19628 fn create_table_registers_schema() {
19629 let mut e = Engine::new();
19630 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
19631 .unwrap();
19632 assert_eq!(e.catalog().table_count(), 1);
19633 let t = e.catalog().get("foo").unwrap();
19634 assert_eq!(t.schema().columns.len(), 2);
19635 assert_eq!(t.schema().columns[0].ty, DataType::Int);
19636 assert!(!t.schema().columns[0].nullable);
19637 assert_eq!(t.schema().columns[1].ty, DataType::Text);
19638 }
19639
19640 #[test]
19641 fn create_table_vector_default_is_f32_encoded() {
19642 let mut e = Engine::new();
19643 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
19644 let t = e.catalog().get("t").unwrap();
19645 assert_eq!(
19646 t.schema().columns[0].ty,
19647 DataType::Vector {
19648 dim: 8,
19649 encoding: VecEncoding::F32,
19650 },
19651 );
19652 }
19653
19654 #[test]
19655 fn create_table_vector_using_sq8_succeeds() {
19656 let mut e = Engine::new();
19660 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
19661 let t = e.catalog().get("t").unwrap();
19662 assert_eq!(
19663 t.schema().columns[0].ty,
19664 DataType::Vector {
19665 dim: 8,
19666 encoding: VecEncoding::Sq8,
19667 },
19668 );
19669 }
19670
19671 #[test]
19672 fn insert_into_sq8_column_quantises_f32_payload() {
19673 let mut e = Engine::new();
19680 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
19681 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19682 .unwrap();
19683 let t = e.catalog().get("t").unwrap();
19684 assert_eq!(t.rows().len(), 1);
19685 match &t.rows()[0].values[0] {
19686 Value::Sq8Vector(q) => {
19687 assert_eq!(q.bytes.len(), 4);
19688 assert!((q.min - 0.0).abs() < 1e-6);
19690 assert!((q.max - 1.0).abs() < 1e-6);
19691 }
19692 other => panic!("expected Sq8Vector cell, got {other:?}"),
19693 }
19694 }
19695
19696 #[test]
19697 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
19698 let mut e = Engine::new();
19705 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
19706 .unwrap();
19707 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19708 .unwrap();
19709 let t = e.catalog().get("t").unwrap();
19710 assert_eq!(t.rows().len(), 1);
19711 match &t.rows()[0].values[0] {
19712 Value::HalfVector(h) => {
19713 assert_eq!(h.dim(), 4);
19714 let back = h.to_f32_vec();
19715 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
19716 for (g, e) in back.iter().zip(expected.iter()) {
19717 assert!(
19718 (g - e).abs() < 1e-6,
19719 "{g} vs {e} should be exact on f16 grid"
19720 );
19721 }
19722 }
19723 other => panic!("expected HalfVector cell, got {other:?}"),
19724 }
19725 }
19726
19727 #[test]
19728 fn alter_index_rebuild_in_place_succeeds() {
19729 let mut e = Engine::new();
19734 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19735 .unwrap();
19736 for i in 0..8_i32 {
19737 #[allow(clippy::cast_precision_loss)]
19738 let base = (i as f32) * 0.1;
19739 e.execute(&alloc::format!(
19740 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
19741 b1 = base + 0.01,
19742 b2 = base + 0.02,
19743 ))
19744 .unwrap();
19745 }
19746 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19747 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
19748 assert_eq!(
19750 e.catalog().get("t").unwrap().schema().columns[1].ty,
19751 DataType::Vector {
19752 dim: 3,
19753 encoding: VecEncoding::F32,
19754 },
19755 );
19756 }
19757
19758 #[test]
19759 fn alter_index_rebuild_with_encoding_switches_cell_type() {
19760 let mut e = Engine::new();
19765 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
19766 .unwrap();
19767 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
19768 .unwrap();
19769 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19770 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
19771 .unwrap();
19772 let t = e.catalog().get("t").unwrap();
19773 assert_eq!(
19774 t.schema().columns[1].ty,
19775 DataType::Vector {
19776 dim: 4,
19777 encoding: VecEncoding::Sq8,
19778 },
19779 );
19780 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
19781 }
19782
19783 #[test]
19784 fn alter_index_rebuild_unknown_index_errors() {
19785 let mut e = Engine::new();
19786 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
19787 assert!(
19788 matches!(
19789 &err,
19790 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
19791 ),
19792 "got: {err}"
19793 );
19794 }
19795
19796 #[test]
19797 fn alter_index_rebuild_on_btree_index_errors() {
19798 let mut e = Engine::new();
19801 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19802 e.execute("INSERT INTO t VALUES (1)").unwrap();
19803 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
19804 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
19805 assert!(
19806 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
19807 "got: {err}"
19808 );
19809 }
19810
19811 #[test]
19812 fn prepared_insert_substitutes_placeholders() {
19813 let mut e = Engine::new();
19819 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
19820 .unwrap();
19821 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
19822 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
19823 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
19824 .unwrap();
19825 }
19826 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
19828 let QueryResult::Rows { rows, .. } = rows_result else {
19829 panic!("expected Rows")
19830 };
19831 assert_eq!(rows.len(), 3);
19832 }
19833
19834 #[test]
19835 fn prepared_select_with_placeholder_filters_rows() {
19836 let mut e = Engine::new();
19837 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
19838 .unwrap();
19839 for i in 0..10_i32 {
19840 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
19841 .unwrap();
19842 }
19843 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
19844 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
19845 else {
19846 panic!("expected Rows")
19847 };
19848 assert_eq!(rows.len(), 1);
19850 assert_eq!(rows[0].values[0], Value::Int(5));
19851 }
19852
19853 #[test]
19854 fn prepared_too_few_params_errors() {
19855 let mut e = Engine::new();
19856 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19857 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
19858 let err = e.execute_prepared(stmt, &[]).unwrap_err();
19859 assert!(
19860 matches!(
19861 &err,
19862 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
19863 ),
19864 "got: {err}"
19865 );
19866 }
19867
19868 #[test]
19869 fn bytea_cast_round_trips_text_input() {
19870 let e = Engine::new();
19873 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
19874 let QueryResult::Rows { rows, .. } = r else {
19875 panic!("expected Rows")
19876 };
19877 assert_eq!(rows.len(), 1);
19878 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
19879 }
19880
19881 #[test]
19882 fn bytea_cast_pg_escape_hex_form() {
19883 let e = Engine::new();
19887 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
19888 let QueryResult::Rows { rows, .. } = r else {
19889 panic!("expected Rows")
19890 };
19891 assert_eq!(
19892 rows[0].values[0],
19893 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
19894 );
19895 }
19896
19897 #[test]
19898 fn bytea_cast_chains_through_octet_length() {
19899 let e = Engine::new();
19903 let r = e
19904 .execute_readonly("SELECT octet_length('hello'::bytea)")
19905 .unwrap();
19906 let QueryResult::Rows { rows, .. } = r else {
19907 panic!("expected Rows")
19908 };
19909 match &rows[0].values[0] {
19910 Value::Int(n) => assert_eq!(*n, 5),
19911 Value::BigInt(n) => assert_eq!(*n, 5),
19912 other => panic!("expected integer length, got {other:?}"),
19913 }
19914 }
19915
19916 #[test]
19917 fn readonly_prepared_on_snapshot_select_with_placeholder() {
19918 let mut e = Engine::new();
19924 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
19925 .unwrap();
19926 for i in 0..10_i32 {
19927 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
19928 .unwrap();
19929 }
19930 let snapshot = e.clone_snapshot();
19931 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
19932 let QueryResult::Rows { rows, .. } =
19933 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
19934 .unwrap()
19935 else {
19936 panic!("expected Rows")
19937 };
19938 assert_eq!(rows.len(), 1);
19939 assert_eq!(rows[0].values[0], Value::Int(5));
19940 }
19941
19942 #[test]
19943 fn readonly_prepared_on_snapshot_rejects_writes() {
19944 let mut e = Engine::new();
19948 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19949 let snapshot = e.clone_snapshot();
19950 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
19951 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
19952 .unwrap_err();
19953 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
19954 }
19955
19956 #[test]
19957 fn readonly_prepared_on_snapshot_frozen_view() {
19958 let mut e = Engine::new();
19964 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19965 e.execute("INSERT INTO t VALUES (1)").unwrap();
19966 let snapshot = e.clone_snapshot();
19967 e.execute("INSERT INTO t VALUES (2)").unwrap();
19968 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
19969 let QueryResult::Rows { rows, .. } =
19970 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
19971 .unwrap()
19972 else {
19973 panic!("expected Rows")
19974 };
19975 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
19976 }
19977
19978 #[test]
19979 fn describe_prepared_on_snapshot_resolves_columns() {
19980 let mut e = Engine::new();
19985 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
19986 .unwrap();
19987 let snapshot = e.clone_snapshot();
19988 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
19989 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
19990 assert_eq!(cols.len(), 2);
19991 assert_eq!(cols[0].name, "id");
19992 assert_eq!(cols[0].ty, DataType::Int);
19993 assert_eq!(cols[1].name, "name");
19994 assert_eq!(cols[1].ty, DataType::Text);
19995 }
19996
19997 #[test]
19998 fn insert_into_half_column_dim_mismatch_errors() {
19999 let mut e = Engine::new();
20000 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
20001 .unwrap();
20002 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
20003 assert!(matches!(
20004 &err,
20005 EngineError::Storage(StorageError::TypeMismatch { .. })
20006 ));
20007 }
20008
20009 #[test]
20010 fn insert_into_sq8_column_dim_mismatch_errors() {
20011 let mut e = Engine::new();
20016 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
20017 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
20018 assert!(
20019 matches!(
20020 &err,
20021 EngineError::Storage(StorageError::TypeMismatch { .. })
20022 ),
20023 "got: {err}",
20024 );
20025 }
20026
20027 #[test]
20028 fn create_table_duplicate_errors() {
20029 let mut e = Engine::new();
20030 e.execute("CREATE TABLE foo (a INT)").unwrap();
20031 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
20032 assert!(matches!(
20033 err,
20034 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
20035 ));
20036 }
20037
20038 #[test]
20039 fn insert_into_unknown_table_errors() {
20040 let mut e = Engine::new();
20041 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
20042 assert!(matches!(
20043 err,
20044 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
20045 ));
20046 }
20047
20048 #[test]
20049 fn insert_happy_path_reports_one_affected() {
20050 let mut e = Engine::new();
20051 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20052 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
20053 assert_eq!(unwrap_command_ok(&r), 1);
20054 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
20055 }
20056
20057 #[test]
20058 fn insert_arity_mismatch_propagates() {
20059 let mut e = Engine::new();
20060 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
20061 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
20062 assert!(matches!(
20063 err,
20064 EngineError::Storage(StorageError::ArityMismatch { .. })
20065 ));
20066 }
20067
20068 #[test]
20069 fn insert_negative_integer_via_unary_minus() {
20070 let mut e = Engine::new();
20071 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20072 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
20073 let rows = e.catalog().get("foo").unwrap().rows();
20074 assert_eq!(rows[0].values[0], Value::Int(-7));
20075 }
20076
20077 #[test]
20078 fn insert_expression_evaluated_against_empty_context() {
20079 let mut e = Engine::new();
20084 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20085 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
20086 let rows = e.catalog().get("foo").unwrap().rows();
20087 assert_eq!(rows[0].values[0], Value::Int(3));
20088 }
20089
20090 #[test]
20091 fn select_star_returns_all_rows_in_insertion_order() {
20092 let mut e = Engine::new();
20093 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
20094 .unwrap();
20095 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
20096 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
20097 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
20098
20099 let r = e.execute("SELECT * FROM foo").unwrap();
20100 let QueryResult::Rows { columns, rows } = r else {
20101 panic!("expected Rows")
20102 };
20103 assert_eq!(columns.len(), 2);
20104 assert_eq!(columns[0].name, "a");
20105 assert_eq!(rows.len(), 3);
20106 assert_eq!(
20107 rows[1].values,
20108 vec![Value::Int(2), Value::Text("two".into())]
20109 );
20110 }
20111
20112 #[test]
20113 fn select_star_on_empty_table_returns_zero_rows() {
20114 let mut e = Engine::new();
20115 e.execute("CREATE TABLE foo (a INT)").unwrap();
20116 let r = e.execute("SELECT * FROM foo").unwrap();
20117 match r {
20118 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
20119 QueryResult::CommandOk { .. } => panic!("expected Rows"),
20120 }
20121 }
20122
20123 fn make_three_row_users(e: &mut Engine) {
20126 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
20127 .unwrap();
20128 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
20129 .unwrap();
20130 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
20131 .unwrap();
20132 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
20133 .unwrap();
20134 }
20135
20136 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
20137 match r {
20138 QueryResult::Rows { columns, rows } => (columns, rows),
20139 QueryResult::CommandOk { .. } => panic!("expected Rows"),
20140 }
20141 }
20142
20143 #[test]
20144 fn where_filter_passes_only_true_rows() {
20145 let mut e = Engine::new();
20146 make_three_row_users(&mut e);
20147 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
20148 let (_, rows) = unwrap_rows(r);
20149 assert_eq!(rows.len(), 2);
20150 assert_eq!(rows[0].values[0], Value::Int(2));
20151 assert_eq!(rows[1].values[0], Value::Int(3));
20152 }
20153
20154 #[test]
20155 fn where_with_null_result_filters_out_row() {
20156 let mut e = Engine::new();
20157 make_three_row_users(&mut e);
20158 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
20160 let (_, rows) = unwrap_rows(r);
20161 assert_eq!(rows.len(), 1);
20162 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
20163 }
20164
20165 #[test]
20166 fn projection_named_columns() {
20167 let mut e = Engine::new();
20168 make_three_row_users(&mut e);
20169 let r = e.execute("SELECT name, score FROM users").unwrap();
20170 let (cols, rows) = unwrap_rows(r);
20171 assert_eq!(cols.len(), 2);
20172 assert_eq!(cols[0].name, "name");
20173 assert_eq!(cols[1].name, "score");
20174 assert_eq!(rows.len(), 3);
20175 assert_eq!(
20176 rows[0].values,
20177 vec![Value::Text("alice".into()), Value::Int(90)]
20178 );
20179 }
20180
20181 #[test]
20182 fn projection_with_column_alias() {
20183 let mut e = Engine::new();
20184 make_three_row_users(&mut e);
20185 let r = e
20186 .execute("SELECT name AS who FROM users WHERE id = 1")
20187 .unwrap();
20188 let (cols, rows) = unwrap_rows(r);
20189 assert_eq!(cols[0].name, "who");
20190 assert_eq!(rows.len(), 1);
20191 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
20192 }
20193
20194 #[test]
20195 fn qualified_column_with_table_alias_resolves() {
20196 let mut e = Engine::new();
20197 make_three_row_users(&mut e);
20198 let r = e
20199 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
20200 .unwrap();
20201 let (cols, rows) = unwrap_rows(r);
20202 assert_eq!(cols.len(), 2);
20203 assert_eq!(rows.len(), 2);
20204 }
20205
20206 #[test]
20207 fn qualified_column_with_wrong_alias_errors() {
20208 let mut e = Engine::new();
20209 make_three_row_users(&mut e);
20210 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
20211 assert!(matches!(
20212 err,
20213 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
20214 ));
20215 }
20216
20217 #[test]
20218 fn select_unknown_column_errors_in_projection() {
20219 let mut e = Engine::new();
20220 make_three_row_users(&mut e);
20221 let err = e.execute("SELECT ghost FROM users").unwrap_err();
20222 assert!(matches!(
20223 err,
20224 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
20225 ));
20226 }
20227
20228 #[test]
20229 fn where_unknown_column_errors() {
20230 let mut e = Engine::new();
20231 make_three_row_users(&mut e);
20232 let err = e
20233 .execute("SELECT * FROM users WHERE ghost = 1")
20234 .unwrap_err();
20235 assert!(matches!(
20236 err,
20237 EngineError::Eval(EvalError::ColumnNotFound { .. })
20238 ));
20239 }
20240
20241 #[test]
20242 fn expression_projection_evaluates_and_renders() {
20243 let mut e = Engine::new();
20246 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
20247 e.execute("INSERT INTO t VALUES (3)").unwrap();
20248 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
20249 assert_eq!(rows.len(), 1);
20250 assert_eq!(rows[0].values[0], Value::Int(3));
20253 }
20254
20255 #[test]
20256 fn select_unknown_table_errors() {
20257 let mut e = Engine::new();
20258 let err = e.execute("SELECT * FROM ghost").unwrap_err();
20259 assert!(matches!(
20260 err,
20261 EngineError::Storage(StorageError::TableNotFound { .. })
20262 ));
20263 }
20264
20265 #[test]
20266 fn invalid_sql_returns_parse_error() {
20267 let mut e = Engine::new();
20270 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
20271 assert!(matches!(err, EngineError::Parse(_)));
20272 }
20273
20274 #[test]
20277 fn create_index_registers_on_table() {
20278 let mut e = Engine::new();
20279 make_three_row_users(&mut e);
20280 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
20281 let t = e.catalog().get("users").unwrap();
20282 assert_eq!(t.indices().len(), 1);
20283 assert_eq!(t.indices()[0].name, "by_name");
20284 }
20285
20286 #[test]
20287 fn create_index_on_unknown_table_errors() {
20288 let mut e = Engine::new();
20289 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
20290 assert!(matches!(
20291 err,
20292 EngineError::Storage(StorageError::TableNotFound { .. })
20293 ));
20294 }
20295
20296 #[test]
20297 fn create_index_on_unknown_column_errors() {
20298 let mut e = Engine::new();
20299 make_three_row_users(&mut e);
20300 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
20301 assert!(matches!(
20302 err,
20303 EngineError::Storage(StorageError::ColumnNotFound { .. })
20304 ));
20305 }
20306
20307 #[test]
20308 fn select_eq_uses_index_returns_same_rows_as_scan() {
20309 let mut without = Engine::new();
20313 make_three_row_users(&mut without);
20314 let mut with = Engine::new();
20315 make_three_row_users(&mut with);
20316 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
20317
20318 let q = "SELECT * FROM users WHERE id = 2";
20319 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
20320 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
20321 assert_eq!(no_idx_rows, idx_rows);
20322 assert_eq!(idx_rows.len(), 1);
20323 }
20324
20325 #[test]
20326 fn select_eq_with_no_matching_index_value_returns_empty() {
20327 let mut e = Engine::new();
20328 make_three_row_users(&mut e);
20329 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
20330 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
20331 assert_eq!(rows.len(), 0);
20332 }
20333
20334 #[test]
20337 fn begin_sets_in_transaction_flag() {
20338 let mut e = Engine::new();
20339 assert!(!e.in_transaction());
20340 e.execute("BEGIN").unwrap();
20341 assert!(e.in_transaction());
20342 }
20343
20344 #[test]
20345 fn double_begin_errors() {
20346 let mut e = Engine::new();
20347 e.execute("BEGIN").unwrap();
20348 let err = e.execute("BEGIN").unwrap_err();
20349 assert_eq!(err, EngineError::TransactionAlreadyOpen);
20350 }
20351
20352 #[test]
20353 fn commit_without_begin_errors() {
20354 let mut e = Engine::new();
20355 let err = e.execute("COMMIT").unwrap_err();
20356 assert_eq!(err, EngineError::NoActiveTransaction);
20357 }
20358
20359 #[test]
20360 fn rollback_without_begin_errors() {
20361 let mut e = Engine::new();
20362 let err = e.execute("ROLLBACK").unwrap_err();
20363 assert_eq!(err, EngineError::NoActiveTransaction);
20364 }
20365
20366 #[test]
20367 fn commit_applies_shadow_to_committed_catalog() {
20368 let mut e = Engine::new();
20369 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20370 e.execute("BEGIN").unwrap();
20371 e.execute("INSERT INTO t VALUES (1)").unwrap();
20372 e.execute("INSERT INTO t VALUES (2)").unwrap();
20373 e.execute("COMMIT").unwrap();
20374 assert!(!e.in_transaction());
20375 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
20376 }
20377
20378 #[test]
20379 fn rollback_discards_shadow() {
20380 let mut e = Engine::new();
20381 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20382 e.execute("BEGIN").unwrap();
20383 e.execute("INSERT INTO t VALUES (1)").unwrap();
20384 e.execute("INSERT INTO t VALUES (2)").unwrap();
20385 e.execute("ROLLBACK").unwrap();
20386 assert!(!e.in_transaction());
20387 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
20388 }
20389
20390 #[test]
20391 fn select_during_tx_sees_uncommitted_writes_own_session() {
20392 let mut e = Engine::new();
20395 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20396 e.execute("BEGIN").unwrap();
20397 e.execute("INSERT INTO t VALUES (42)").unwrap();
20398 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
20399 assert_eq!(rows.len(), 1);
20400 assert_eq!(rows[0].values[0], Value::Int(42));
20401 }
20402
20403 #[test]
20404 fn snapshot_with_no_users_is_bare_catalog_format() {
20405 let mut e = Engine::new();
20406 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20407 let bytes = e.snapshot();
20408 assert_eq!(
20409 &bytes[..8],
20410 b"SPGDB001",
20411 "must be the bare v3.x catalog magic"
20412 );
20413 let e2 = Engine::restore_envelope(&bytes).unwrap();
20414 assert!(e2.users().is_empty());
20415 assert_eq!(e2.catalog().table_count(), 1);
20416 }
20417
20418 #[test]
20419 fn snapshot_with_users_round_trips_both_via_envelope() {
20420 let mut e = Engine::new();
20421 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20422 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
20423 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
20424 .unwrap();
20425 let bytes = e.snapshot();
20426 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
20427 let e2 = Engine::restore_envelope(&bytes).unwrap();
20428 assert_eq!(e2.users().len(), 2);
20429 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
20430 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
20431 assert_eq!(e2.verify_user("alice", "wrong"), None);
20432 assert_eq!(e2.catalog().table_count(), 1);
20433 }
20434
20435 #[test]
20436 fn ddl_inside_tx_also_rolled_back() {
20437 let mut e = Engine::new();
20438 e.execute("BEGIN").unwrap();
20439 e.execute("CREATE TABLE t (v INT)").unwrap();
20440 e.execute("SELECT * FROM t").unwrap();
20442 e.execute("ROLLBACK").unwrap();
20443 let err = e.execute("SELECT * FROM t").unwrap_err();
20445 assert!(matches!(
20446 err,
20447 EngineError::Storage(StorageError::TableNotFound { .. })
20448 ));
20449 }
20450
20451 #[test]
20454 fn create_publication_lands_in_catalog() {
20455 let mut e = Engine::new();
20456 assert!(e.publications().is_empty());
20457 e.execute("CREATE PUBLICATION pub_a").unwrap();
20458 assert_eq!(e.publications().len(), 1);
20459 assert!(e.publications().contains("pub_a"));
20460 }
20461
20462 #[test]
20463 fn create_publication_duplicate_errors() {
20464 let mut e = Engine::new();
20465 e.execute("CREATE PUBLICATION pub_a").unwrap();
20466 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
20467 assert!(
20468 alloc::format!("{err:?}").contains("DuplicateName"),
20469 "got {err:?}"
20470 );
20471 }
20472
20473 #[test]
20474 fn drop_publication_silent_when_absent() {
20475 let mut e = Engine::new();
20476 let r = e.execute("DROP PUBLICATION nope").unwrap();
20479 match r {
20480 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20481 other => panic!("expected CommandOk, got {other:?}"),
20482 }
20483 }
20484
20485 #[test]
20486 fn drop_publication_present_reports_one_affected() {
20487 let mut e = Engine::new();
20488 e.execute("CREATE PUBLICATION pub_a").unwrap();
20489 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
20490 match r {
20491 QueryResult::CommandOk {
20492 affected,
20493 modified_catalog,
20494 } => {
20495 assert_eq!(affected, 1);
20496 assert!(modified_catalog);
20497 }
20498 other => panic!("expected CommandOk, got {other:?}"),
20499 }
20500 assert!(e.publications().is_empty());
20501 }
20502
20503 #[test]
20504 fn publications_persist_across_snapshot_restore() {
20505 let mut e = Engine::new();
20510 e.execute("CREATE PUBLICATION pub_a").unwrap();
20511 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
20512 .unwrap();
20513 let snap = e.snapshot();
20514 let e2 = Engine::restore_envelope(&snap).unwrap();
20515 assert_eq!(e2.publications().len(), 2);
20516 assert!(e2.publications().contains("pub_a"));
20517 assert!(e2.publications().contains("pub_b"));
20518 }
20519
20520 #[test]
20521 fn create_publication_allowed_inside_transaction() {
20522 let mut e = Engine::new();
20526 e.execute("BEGIN").unwrap();
20527 e.execute("CREATE PUBLICATION pub_a").unwrap();
20528 e.execute("COMMIT").unwrap();
20529 assert!(e.publications().contains("pub_a"));
20530 }
20531
20532 #[test]
20535 fn create_publication_for_table_list_lands_with_scope() {
20536 let mut e = Engine::new();
20537 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
20538 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
20539 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
20540 .unwrap();
20541 let scope = e.publications().get("pub_a").cloned();
20542 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
20543 panic!("expected ForTables scope, got {scope:?}")
20544 };
20545 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20546 }
20547
20548 #[test]
20549 fn create_publication_all_tables_except_lands_with_scope() {
20550 let mut e = Engine::new();
20551 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
20552 .unwrap();
20553 let scope = e.publications().get("pub_a").cloned();
20554 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
20555 panic!("expected AllTablesExcept scope, got {scope:?}")
20556 };
20557 assert_eq!(ts, alloc::vec!["t3".to_string()]);
20558 }
20559
20560 #[test]
20561 fn show_publications_empty_returns_zero_rows() {
20562 let e = Engine::new();
20563 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
20564 let QueryResult::Rows { rows, columns } = r else {
20565 panic!()
20566 };
20567 assert!(rows.is_empty());
20568 assert_eq!(columns.len(), 3);
20569 assert_eq!(columns[0].name, "name");
20570 assert_eq!(columns[1].name, "scope");
20571 assert_eq!(columns[2].name, "table_count");
20572 }
20573
20574 #[test]
20575 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
20576 let mut e = Engine::new();
20577 e.execute("CREATE PUBLICATION z_pub").unwrap();
20578 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
20579 .unwrap();
20580 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
20581 .unwrap();
20582 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
20583 let QueryResult::Rows { rows, .. } = r else {
20584 panic!()
20585 };
20586 assert_eq!(rows.len(), 3);
20587 let names: Vec<&str> = rows
20589 .iter()
20590 .map(|r| {
20591 if let Value::Text(s) = &r.values[0] {
20592 s.as_str()
20593 } else {
20594 panic!()
20595 }
20596 })
20597 .collect();
20598 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
20599 match &rows[0].values[1] {
20601 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
20602 other => panic!("expected Text, got {other:?}"),
20603 }
20604 assert_eq!(rows[0].values[2], Value::Int(2));
20605 match &rows[1].values[1] {
20607 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
20608 other => panic!("expected Text, got {other:?}"),
20609 }
20610 assert_eq!(rows[1].values[2], Value::Int(1));
20611 match &rows[2].values[1] {
20613 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
20614 other => panic!("expected Text, got {other:?}"),
20615 }
20616 assert_eq!(rows[2].values[2], Value::Null);
20617 }
20618
20619 #[test]
20620 fn for_list_scopes_persist_across_snapshot() {
20621 let mut e = Engine::new();
20624 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
20625 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
20626 .unwrap();
20627 let snap = e.snapshot();
20628 let e2 = Engine::restore_envelope(&snap).unwrap();
20629 assert_eq!(e2.publications().len(), 2);
20630 let p1 = e2.publications().get("p1").cloned();
20631 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
20632 panic!("p1 scope lost: {p1:?}")
20633 };
20634 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20635 let p2 = e2.publications().get("p2").cloned();
20636 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
20637 panic!("p2 scope lost: {p2:?}")
20638 };
20639 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
20640 }
20641
20642 #[test]
20645 fn create_subscription_lands_in_catalog_with_defaults() {
20646 let mut e = Engine::new();
20647 e.execute(
20648 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
20649 )
20650 .unwrap();
20651 let s = e.subscriptions().get("sub_a").cloned().expect("present");
20652 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
20653 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
20654 assert!(s.enabled);
20655 assert_eq!(s.last_received_pos, 0);
20656 }
20657
20658 #[test]
20659 fn create_subscription_duplicate_name_errors() {
20660 let mut e = Engine::new();
20661 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
20662 .unwrap();
20663 let err = e
20664 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
20665 .unwrap_err();
20666 assert!(
20667 alloc::format!("{err:?}").contains("DuplicateName"),
20668 "got {err:?}"
20669 );
20670 }
20671
20672 #[test]
20673 fn drop_subscription_silent_when_absent() {
20674 let mut e = Engine::new();
20675 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
20676 match r {
20677 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20678 other => panic!("expected CommandOk, got {other:?}"),
20679 }
20680 }
20681
20682 #[test]
20683 fn subscription_advance_updates_last_pos_monotone() {
20684 let mut e = Engine::new();
20685 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20686 .unwrap();
20687 assert!(e.subscription_advance("s", 100));
20688 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20689 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20691 assert!(e.subscription_advance("s", 200));
20692 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
20693 assert!(!e.subscription_advance("missing", 1));
20694 }
20695
20696 #[test]
20697 fn show_subscriptions_returns_rows_ordered_by_name() {
20698 let mut e = Engine::new();
20699 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
20700 .unwrap();
20701 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
20702 .unwrap();
20703 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
20704 let QueryResult::Rows { rows, columns } = r else {
20705 panic!()
20706 };
20707 assert_eq!(rows.len(), 2);
20708 assert_eq!(columns.len(), 5);
20709 assert_eq!(columns[0].name, "name");
20710 assert_eq!(columns[4].name, "last_received_pos");
20711 let names: Vec<&str> = rows
20713 .iter()
20714 .map(|r| {
20715 if let Value::Text(s) = &r.values[0] {
20716 s.as_str()
20717 } else {
20718 panic!()
20719 }
20720 })
20721 .collect();
20722 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
20723 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
20725 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
20726 assert_eq!(rows[0].values[3], Value::Bool(true));
20727 assert_eq!(rows[0].values[4], Value::BigInt(0));
20728 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
20730 }
20731
20732 #[test]
20733 fn subscriptions_persist_across_snapshot_envelope_v4() {
20734 let mut e = Engine::new();
20735 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
20736 .unwrap();
20737 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
20738 .unwrap();
20739 e.subscription_advance("s2", 42);
20740 let snap = e.snapshot();
20741 let e2 = Engine::restore_envelope(&snap).unwrap();
20742 assert_eq!(e2.subscriptions().len(), 2);
20743 let s1 = e2.subscriptions().get("s1").unwrap();
20744 assert_eq!(s1.conn_str, "h=A");
20745 assert_eq!(
20746 s1.publications,
20747 alloc::vec!["p1".to_string(), "p2".to_string()]
20748 );
20749 assert_eq!(s1.last_received_pos, 0);
20750 let s2 = e2.subscriptions().get("s2").unwrap();
20751 assert_eq!(s2.last_received_pos, 42);
20752 }
20753
20754 #[test]
20755 fn v3_envelope_loads_with_empty_subscriptions() {
20756 let mut e = Engine::new();
20760 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
20761 let catalog = e.catalog.serialize();
20762 let users = crate::users::serialize_users(&e.users);
20763 let pubs = e.publications.serialize();
20764 let mut buf = Vec::new();
20765 buf.extend_from_slice(b"SPGENV01");
20766 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
20768 buf.extend_from_slice(&catalog);
20769 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
20770 buf.extend_from_slice(&users);
20771 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
20772 buf.extend_from_slice(&pubs);
20773 let crc = spg_crypto::crc32::crc32(&buf);
20774 buf.extend_from_slice(&crc.to_le_bytes());
20775
20776 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
20777 assert!(e2.subscriptions().is_empty());
20778 assert!(e2.publications().contains("pub_legacy"));
20779 }
20780
20781 #[test]
20782 fn create_subscription_allowed_inside_transaction() {
20783 let mut e = Engine::new();
20784 e.execute("BEGIN").unwrap();
20785 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20786 .unwrap();
20787 e.execute("COMMIT").unwrap();
20788 assert!(e.subscriptions().contains("s"));
20789 }
20790
20791 #[test]
20793 fn analyze_populates_histogram_bounds() {
20794 let mut e = Engine::new();
20795 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
20796 .unwrap();
20797 for i in 0..50 {
20798 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
20799 .unwrap();
20800 }
20801 e.execute("ANALYZE t").unwrap();
20802 let stats = e.statistics();
20803 let id_stats = stats.get("t", "id").unwrap();
20804 assert!(id_stats.histogram_bounds.len() >= 2);
20805 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
20806 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
20807 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
20808 assert_eq!(id_stats.n_distinct, 50);
20809 }
20810
20811 #[test]
20812 fn reanalyze_overwrites_prior_stats() {
20813 let mut e = Engine::new();
20814 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20815 for i in 0..10 {
20816 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20817 .unwrap();
20818 }
20819 e.execute("ANALYZE t").unwrap();
20820 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
20821 assert_eq!(n1, 10);
20822 for i in 10..30 {
20823 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20824 .unwrap();
20825 }
20826 e.execute("ANALYZE t").unwrap();
20827 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
20828 assert_eq!(n2, 30);
20829 }
20830
20831 #[test]
20832 fn analyze_unknown_table_errors() {
20833 let mut e = Engine::new();
20834 let err = e.execute("ANALYZE nonexistent").unwrap_err();
20835 assert!(matches!(
20836 err,
20837 EngineError::Storage(StorageError::TableNotFound { .. })
20838 ));
20839 }
20840
20841 #[test]
20842 fn bare_analyze_covers_all_user_tables() {
20843 let mut e = Engine::new();
20844 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
20845 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
20846 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
20847 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
20848 let r = e.execute("ANALYZE").unwrap();
20849 match r {
20850 QueryResult::CommandOk {
20851 affected,
20852 modified_catalog,
20853 } => {
20854 assert_eq!(affected, 2);
20855 assert!(modified_catalog);
20856 }
20857 other => panic!("expected CommandOk, got {other:?}"),
20858 }
20859 assert!(e.statistics().get("t1", "id").is_some());
20860 assert!(e.statistics().get("t2", "name").is_some());
20861 }
20862
20863 #[test]
20864 fn select_from_spg_statistic_returns_rows_per_column() {
20865 let mut e = Engine::new();
20866 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
20867 .unwrap();
20868 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
20869 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
20870 e.execute("ANALYZE t").unwrap();
20871 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
20872 let QueryResult::Rows { rows, columns } = r else {
20873 panic!()
20874 };
20875 assert_eq!(columns.len(), 6);
20877 assert_eq!(columns[0].name, "table_name");
20878 assert_eq!(columns[4].name, "histogram_bounds");
20879 assert_eq!(columns[5].name, "cold_row_count");
20880 assert_eq!(rows.len(), 2, "one row per column of t");
20881 match (&rows[0].values[0], &rows[0].values[1]) {
20883 (Value::Text(t), Value::Text(c)) => {
20884 assert_eq!(t, "t");
20885 assert_eq!(c, "id");
20887 }
20888 _ => panic!(),
20889 }
20890 }
20891
20892 #[test]
20893 fn analyze_skips_vector_columns() {
20894 let mut e = Engine::new();
20897 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
20898 .unwrap();
20899 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
20900 e.execute("ANALYZE t").unwrap();
20901 assert!(e.statistics().get("t", "id").is_some());
20902 assert!(e.statistics().get("t", "v").is_none());
20903 }
20904
20905 #[test]
20906 fn statistics_persist_across_envelope_v5_round_trip() {
20907 let mut e = Engine::new();
20908 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20909 for i in 0..20 {
20910 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20911 .unwrap();
20912 }
20913 e.execute("ANALYZE").unwrap();
20914 let snap = e.snapshot();
20915 let e2 = Engine::restore_envelope(&snap).unwrap();
20916 let s = e2.statistics().get("t", "id").unwrap();
20917 assert_eq!(s.n_distinct, 20);
20918 }
20919
20920 #[test]
20923 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
20924 let mut e = Engine::new();
20928 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20929 for i in 0..9 {
20930 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20931 .unwrap();
20932 }
20933 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
20934 e.execute("INSERT INTO t VALUES (9)").unwrap();
20935 let needs = e.tables_needing_analyze();
20936 assert_eq!(needs, alloc::vec!["t".to_string()]);
20937 }
20938
20939 #[test]
20940 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
20941 let mut e = Engine::new();
20947 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20948 for i in 0..1000 {
20949 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20950 .unwrap();
20951 }
20952 e.execute("ANALYZE t").unwrap();
20953 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
20954 for i in 1000..1050 {
20955 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20956 .unwrap();
20957 }
20958 assert!(
20959 e.tables_needing_analyze().is_empty(),
20960 "50 inserts < threshold of ~105"
20961 );
20962 for i in 1050..1200 {
20963 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20964 .unwrap();
20965 }
20966 assert_eq!(
20967 e.tables_needing_analyze(),
20968 alloc::vec!["t".to_string()],
20969 "200 inserts > 0.1 × 1200 threshold"
20970 );
20971 }
20972
20973 #[test]
20974 fn auto_analyze_threshold_resets_after_analyze() {
20975 let mut e = Engine::new();
20976 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20977 for i in 0..200 {
20978 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20979 .unwrap();
20980 }
20981 assert!(!e.tables_needing_analyze().is_empty());
20982 e.execute("ANALYZE").unwrap();
20983 assert!(
20984 e.tables_needing_analyze().is_empty(),
20985 "ANALYZE must reset the counter"
20986 );
20987 }
20988
20989 #[test]
20990 fn auto_analyze_threshold_tracks_updates_and_deletes() {
20991 let mut e = Engine::new();
20992 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
20993 .unwrap();
20994 for i in 0..50 {
20995 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
20996 .unwrap();
20997 }
20998 e.execute("ANALYZE t").unwrap();
20999 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
21002 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
21003 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
21004 }
21005
21006 #[test]
21007 fn v4_envelope_loads_with_empty_statistics() {
21008 let mut e = Engine::new();
21012 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
21013 .unwrap();
21014 let catalog = e.catalog.serialize();
21015 let users = crate::users::serialize_users(&e.users);
21016 let pubs = e.publications.serialize();
21017 let subs = e.subscriptions.serialize();
21018 let mut buf = Vec::new();
21019 buf.extend_from_slice(b"SPGENV01");
21020 buf.push(4u8);
21021 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
21022 buf.extend_from_slice(&catalog);
21023 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
21024 buf.extend_from_slice(&users);
21025 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
21026 buf.extend_from_slice(&pubs);
21027 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
21028 buf.extend_from_slice(&subs);
21029 let crc = spg_crypto::crc32::crc32(&buf);
21030 buf.extend_from_slice(&crc.to_le_bytes());
21031 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
21032 assert!(e2.statistics().is_empty());
21033 }
21034
21035 #[test]
21036 fn v1_v2_envelope_loads_with_empty_publications() {
21037 let mut e = Engine::new();
21044 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
21047 .unwrap();
21048
21049 let catalog = e.catalog.serialize();
21051 let users = crate::users::serialize_users(&e.users);
21052 let mut buf = Vec::new();
21053 buf.extend_from_slice(b"SPGENV01");
21054 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
21056 buf.extend_from_slice(&catalog);
21057 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
21058 buf.extend_from_slice(&users);
21059 let crc = spg_crypto::crc32::crc32(&buf);
21060 buf.extend_from_slice(&crc.to_le_bytes());
21061
21062 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
21063 assert!(e2.publications().is_empty());
21064 }
21065}