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::InList { expr, list, .. } => {
11169 self.resolve_expr_subqueries(expr, cancel)?;
11170 for item in list {
11171 self.resolve_expr_subqueries(item, cancel)?;
11172 }
11173 }
11174 Expr::Array(items) => {
11176 for elem in items {
11177 self.resolve_expr_subqueries(elem, cancel)?;
11178 }
11179 }
11180 Expr::ArraySubscript { target, index } => {
11181 self.resolve_expr_subqueries(target, cancel)?;
11182 self.resolve_expr_subqueries(index, cancel)?;
11183 }
11184 Expr::AnyAll { expr, array, .. } => {
11185 self.resolve_expr_subqueries(expr, cancel)?;
11186 self.resolve_expr_subqueries(array, cancel)?;
11187 }
11188 Expr::Case {
11189 operand,
11190 branches,
11191 else_branch,
11192 } => {
11193 if let Some(o) = operand {
11194 self.resolve_expr_subqueries(o, cancel)?;
11195 }
11196 for (w, t) in branches {
11197 self.resolve_expr_subqueries(w, cancel)?;
11198 self.resolve_expr_subqueries(t, cancel)?;
11199 }
11200 if let Some(e) = else_branch {
11201 self.resolve_expr_subqueries(e, cancel)?;
11202 }
11203 }
11204 }
11205 Ok(())
11206 }
11207
11208 fn eval_expr_with_correlated(
11216 &self,
11217 expr: &Expr,
11218 row: &Row,
11219 ctx: &EvalContext<'_>,
11220 cancel: CancelToken<'_>,
11221 mut memo: Option<&mut memoize::MemoizeCache>,
11222 ) -> Result<Value, EngineError> {
11223 let has_subq = if let Some(m) = memo.as_deref_mut() {
11228 let key = core::ptr::from_ref::<Expr>(expr) as usize;
11229 match m.has_subquery.get(&key) {
11230 Some(b) => *b,
11231 None => {
11232 let b = expr_has_subquery(expr);
11233 m.has_subquery.insert(key, b);
11234 b
11235 }
11236 }
11237 } else {
11238 expr_has_subquery(expr)
11239 };
11240 if !has_subq {
11241 if let Some(m) = memo.as_deref_mut()
11246 && expr_may_use_in_set(expr)
11247 {
11248 return eval_with_in_sets(expr, row, ctx, m);
11249 }
11250 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
11251 }
11252 if let Some(m) = memo.as_deref_mut() {
11258 let key = core::ptr::from_ref::<Expr>(expr) as usize;
11259 let plan_hit = m.expr_plans.contains_key(&key);
11264 let mut subs: Vec<&SelectStatement> = Vec::new();
11265 if !plan_hit {
11266 collect_scalar_subqueries(expr, &mut subs);
11267 }
11268 if !plan_hit && !subs.is_empty() {
11269 let mut plan: Vec<Option<alloc::rc::Rc<memoize::GroupMap>>> =
11270 Vec::with_capacity(subs.len());
11271 for sub in &subs {
11272 let repr = alloc::format!("{sub}");
11273 if !m.group_maps.contains_key(&repr) {
11274 let built = self
11275 .try_batch_correlated_scalar(sub, cancel)?
11276 .map(alloc::rc::Rc::new);
11277 m.group_maps.insert(repr.clone(), built);
11278 }
11279 plan.push(m.group_maps.get(&repr).cloned().flatten());
11280 }
11281 let mut template = expr.clone();
11282 hollow_scalar_subqueries(&mut template);
11283 m.expr_plans.insert(key, (subs.len(), plan, template));
11284 }
11285 if let Some((_, plan, template)) = m.expr_plans.get(&key)
11286 && !plan.is_empty()
11287 && plan.iter().all(|p| p.is_some())
11288 {
11289 let plan = plan.clone();
11296 let mut e = template.clone();
11297 let mut idx = 0usize;
11298 let ok = splice_planned_subqueries(&mut e, &plan, &mut idx, row, ctx)?;
11299 if ok {
11300 if expr_has_subquery(&e) {
11301 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11302 }
11303 return eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval);
11304 }
11305 }
11306 }
11307 let mut e = expr.clone();
11308 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11309 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
11310 }
11311
11312 fn resolve_correlated_in_expr(
11313 &self,
11314 e: &mut Expr,
11315 row: &Row,
11316 ctx: &EvalContext<'_>,
11317 cancel: CancelToken<'_>,
11318 mut memo: Option<&mut memoize::MemoizeCache>,
11319 ) -> Result<(), EngineError> {
11320 match e {
11321 Expr::AggregateOrdered { call, order_by, .. } => {
11322 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
11323 for o in order_by.iter_mut() {
11324 self.resolve_correlated_in_expr(
11325 &mut o.expr,
11326 row,
11327 ctx,
11328 cancel,
11329 memo.as_deref_mut(),
11330 )?;
11331 }
11332 }
11333 Expr::ScalarSubquery(inner) => {
11334 if memo.is_some() {
11341 let repr = alloc::format!("{}", **inner);
11342 let entry_known = memo
11343 .as_ref()
11344 .is_some_and(|m| m.group_maps.contains_key(&repr));
11345 if !entry_known {
11346 let built = self
11347 .try_batch_correlated_scalar(inner, cancel)?
11348 .map(alloc::rc::Rc::new);
11349 if let Some(m) = memo.as_deref_mut() {
11350 m.group_maps.insert(repr.clone(), built);
11351 }
11352 }
11353 if let Some(m) = memo.as_deref_mut()
11354 && let Some(Some(gm)) = m.group_maps.get(&repr)
11355 {
11356 let (outer_col, map) = gm.as_ref();
11357 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
11358 .map_err(EngineError::Eval)?;
11359 let v = if matches!(key_v, Value::Null) {
11360 Value::Null
11361 } else {
11362 map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
11363 .cloned()
11364 .unwrap_or(Value::Null)
11365 };
11366 *e = value_to_literal_expr(v)?;
11367 return Ok(());
11368 }
11369 }
11370 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
11375 subquery_repr: alloc::format!("{}", **inner),
11376 outer_values: row.values.clone(),
11377 });
11378 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
11379 && let Some(cached) = cache.get(k)
11380 {
11381 *e = value_to_literal_expr(cached)?;
11382 return Ok(());
11383 }
11384 let mut s = (**inner).clone();
11385 substitute_outer_columns(&mut s, row, ctx);
11386 let r = self.exec_select_cancel(&s, cancel)?;
11387 let QueryResult::Rows { rows, .. } = r else {
11388 return Err(EngineError::Unsupported(
11389 "scalar subquery: inner did not return rows".into(),
11390 ));
11391 };
11392 let value = match rows.as_slice() {
11393 [] => Value::Null,
11394 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
11395 _ => {
11396 return Err(EngineError::Unsupported(alloc::format!(
11397 "scalar subquery returned {} rows; expected 0 or 1",
11398 rows.len()
11399 )));
11400 }
11401 };
11402 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
11403 cache.insert(k, value.clone());
11404 }
11405 *e = value_to_literal_expr(value)?;
11406 }
11407 Expr::Exists { subquery, negated } => {
11408 let mut s = (**subquery).clone();
11409 substitute_outer_columns(&mut s, row, ctx);
11410 let r = self.exec_select_cancel(&s, cancel)?;
11411 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
11412 let bit = if *negated { !exists } else { exists };
11413 *e = Expr::Literal(Literal::Bool(bit));
11414 }
11415 Expr::InSubquery {
11416 expr: lhs,
11417 subquery,
11418 negated,
11419 } => {
11420 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11421 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
11422 let mut s = (**subquery).clone();
11423 substitute_outer_columns(&mut s, row, ctx);
11424 let r = self.exec_select_cancel(&s, cancel)?;
11425 let QueryResult::Rows { columns, rows, .. } = r else {
11426 return Err(EngineError::Unsupported(
11427 "IN-subquery: inner did not return rows".into(),
11428 ));
11429 };
11430 if columns.len() != 1 {
11431 return Err(EngineError::Unsupported(alloc::format!(
11432 "IN-subquery must project exactly one column; got {}",
11433 columns.len()
11434 )));
11435 }
11436 let mut found = false;
11437 let mut any_null = false;
11438 for r0 in rows {
11439 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
11440 if v.is_null() {
11441 any_null = true;
11442 continue;
11443 }
11444 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
11445 found = true;
11446 break;
11447 }
11448 }
11449 let bit = if found {
11450 !*negated
11451 } else if any_null {
11452 return Err(EngineError::Unsupported(
11453 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
11454 ));
11455 } else {
11456 *negated
11457 };
11458 *e = Expr::Literal(Literal::Bool(bit));
11459 }
11460 Expr::Binary { lhs, rhs, .. } => {
11461 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11462 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
11463 }
11464 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11465 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11466 }
11467 Expr::Like { expr, pattern, .. } => {
11468 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11469 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
11470 }
11471 Expr::FunctionCall { args, .. } => {
11472 for a in args {
11473 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
11474 }
11475 }
11476 Expr::Extract { source, .. } => {
11477 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
11478 }
11479 Expr::WindowFunction { .. }
11480 | Expr::Literal(_)
11481 | Expr::Placeholder(_)
11482 | Expr::Column(_) => {}
11483 Expr::Array(items) => {
11485 for elem in items {
11486 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
11487 }
11488 }
11489 Expr::ArraySubscript { target, index } => {
11490 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
11491 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
11492 }
11493 Expr::AnyAll { expr, array, .. } => {
11494 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11495 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
11496 }
11497 Expr::InList { expr, list, .. } => {
11498 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11499 for item in list {
11500 self.resolve_correlated_in_expr(item, row, ctx, cancel, memo.as_deref_mut())?;
11501 }
11502 }
11503 Expr::Case {
11504 operand,
11505 branches,
11506 else_branch,
11507 } => {
11508 if let Some(o) = operand {
11509 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
11510 }
11511 for (w, t) in branches {
11512 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
11513 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
11514 }
11515 if let Some(e) = else_branch {
11516 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
11517 }
11518 }
11519 }
11520 Ok(())
11521 }
11522
11523 fn subquery_replacement(
11524 &self,
11525 e: &Expr,
11526 cancel: CancelToken<'_>,
11527 ) -> Result<Option<Expr>, EngineError> {
11528 match e {
11529 Expr::ScalarSubquery(inner) => {
11530 let mut s = (**inner).clone();
11531 self.resolve_select_subqueries(&mut s, cancel)?;
11534 let r = match self.exec_bare_select_cancel(&s, cancel) {
11535 Ok(r) => r,
11536 Err(e) if is_correlation_error(&e) => return Ok(None),
11537 Err(e) => return Err(e),
11538 };
11539 let QueryResult::Rows { rows, .. } = r else {
11540 return Err(EngineError::Unsupported(
11541 "scalar subquery: inner statement did not return rows".into(),
11542 ));
11543 };
11544 let value = match rows.as_slice() {
11545 [] => Value::Null,
11546 [row] => row.values.first().cloned().unwrap_or(Value::Null),
11547 _ => {
11548 return Err(EngineError::Unsupported(alloc::format!(
11549 "scalar subquery returned {} rows; expected 0 or 1",
11550 rows.len()
11551 )));
11552 }
11553 };
11554 Ok(Some(value_to_literal_expr(value)?))
11555 }
11556 Expr::Exists { subquery, negated } => {
11557 let mut s = (**subquery).clone();
11558 self.resolve_select_subqueries(&mut s, cancel)?;
11559 let r = match self.exec_bare_select_cancel(&s, cancel) {
11560 Ok(r) => r,
11561 Err(e) if is_correlation_error(&e) => return Ok(None),
11562 Err(e) => return Err(e),
11563 };
11564 let exists = match r {
11565 QueryResult::Rows { rows, .. } => !rows.is_empty(),
11566 QueryResult::CommandOk { .. } => false,
11567 };
11568 let bit = if *negated { !exists } else { exists };
11569 Ok(Some(Expr::Literal(Literal::Bool(bit))))
11570 }
11571 Expr::InSubquery {
11572 expr,
11573 subquery,
11574 negated,
11575 } => {
11576 let mut s = (**subquery).clone();
11577 self.resolve_select_subqueries(&mut s, cancel)?;
11578 let r = match self.exec_bare_select_cancel(&s, cancel) {
11579 Ok(r) => r,
11580 Err(e) if is_correlation_error(&e) => return Ok(None),
11581 Err(e) => return Err(e),
11582 };
11583 let QueryResult::Rows { columns, rows, .. } = r else {
11584 return Err(EngineError::Unsupported(
11585 "IN-subquery: inner statement did not return rows".into(),
11586 ));
11587 };
11588 if columns.len() != 1 {
11589 return Err(EngineError::Unsupported(alloc::format!(
11590 "IN-subquery must project exactly one column; got {}",
11591 columns.len()
11592 )));
11593 }
11594 let mut list: Vec<Expr> = Vec::with_capacity(rows.len());
11600 for row in rows {
11601 let v = row.values.into_iter().next().unwrap_or(Value::Null);
11602 list.push(value_to_literal_expr(v)?);
11603 }
11604 Ok(Some(Expr::InList {
11605 expr: expr.clone(),
11606 list,
11607 negated: *negated,
11608 }))
11609 }
11610 _ => Ok(None),
11611 }
11612 }
11613}
11614
11615fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
11627 if let Some(from) = &stmt.from
11628 && from_refers_to(from, target)
11629 {
11630 return true;
11631 }
11632 for (_, peer) in &stmt.unions {
11633 if select_refers_to(peer, target) {
11634 return true;
11635 }
11636 }
11637 for item in &stmt.items {
11638 if let SelectItem::Expr { expr, .. } = item
11639 && expr_refers_to(expr, target)
11640 {
11641 return true;
11642 }
11643 }
11644 if let Some(w) = &stmt.where_
11645 && expr_refers_to(w, target)
11646 {
11647 return true;
11648 }
11649 false
11650}
11651
11652fn from_refers_to(from: &FromClause, target: &str) -> bool {
11653 if from.primary.name.eq_ignore_ascii_case(target) {
11654 return true;
11655 }
11656 from.joins
11657 .iter()
11658 .any(|j| j.table.name.eq_ignore_ascii_case(target))
11659}
11660
11661fn collect_qualified_refs(
11666 stmt: &SelectStatement,
11667 out: &mut alloc::collections::BTreeSet<(String, String)>,
11668) -> Option<()> {
11669 for item in &stmt.items {
11670 match item {
11671 SelectItem::Wildcard => return None,
11672 SelectItem::Expr { expr, .. } => collect_qualified_refs_expr(expr, out)?,
11673 }
11674 }
11675 if let Some(w) = &stmt.where_ {
11676 collect_qualified_refs_expr(w, out)?;
11677 }
11678 if let Some(from) = &stmt.from {
11679 for j in &from.joins {
11680 if let Some(on) = &j.on {
11681 collect_qualified_refs_expr(on, out)?;
11682 }
11683 if j.table.lateral_subquery.is_some() {
11684 return None;
11685 }
11686 }
11687 }
11688 if let Some(gs) = &stmt.group_by {
11689 for g in gs {
11690 collect_qualified_refs_expr(g, out)?;
11691 }
11692 }
11693 if let Some(h) = &stmt.having {
11694 collect_qualified_refs_expr(h, out)?;
11695 }
11696 for o in &stmt.order_by {
11697 collect_qualified_refs_expr(&o.expr, out)?;
11698 }
11699 for (_, peer) in &stmt.unions {
11700 collect_qualified_refs(peer, out)?;
11701 }
11702 for cte in &stmt.ctes {
11703 collect_qualified_refs(&cte.body, out)?;
11704 }
11705 Some(())
11706}
11707
11708fn collect_qualified_refs_expr(
11709 e: &Expr,
11710 out: &mut alloc::collections::BTreeSet<(String, String)>,
11711) -> Option<()> {
11712 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
11715 let mut subs: Vec<&SelectStatement> = Vec::new();
11716 visit_expr_columns_and_subqueries(
11717 e,
11718 &mut |c: &spg_sql::ast::ColumnName| cols.push(c.clone()),
11719 &mut |sub| subs.push(sub),
11720 );
11721 for c in cols {
11722 match c.qualifier {
11723 Some(q) => {
11724 out.insert((q, c.name));
11725 }
11726 None => return None,
11727 }
11728 }
11729 for sub in subs {
11730 collect_qualified_refs(sub, out)?;
11731 }
11732 Some(())
11733}
11734
11735fn visit_expr_columns_and_subqueries<'a>(
11738 e: &'a Expr,
11739 on_col: &mut impl FnMut(&'a spg_sql::ast::ColumnName),
11740 on_sub: &mut impl FnMut(&'a SelectStatement),
11741) {
11742 match e {
11743 Expr::Column(c) => on_col(c),
11744 Expr::ScalarSubquery(s) => on_sub(s),
11745 Expr::Exists { subquery, .. } => on_sub(subquery),
11746 Expr::InSubquery { expr, subquery, .. } => {
11747 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11748 on_sub(subquery);
11749 }
11750 Expr::Binary { lhs, rhs, .. } => {
11751 visit_expr_columns_and_subqueries(lhs, on_col, on_sub);
11752 visit_expr_columns_and_subqueries(rhs, on_col, on_sub);
11753 }
11754 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11755 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11756 }
11757 Expr::Like { expr, pattern, .. } => {
11758 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11759 visit_expr_columns_and_subqueries(pattern, on_col, on_sub);
11760 }
11761 Expr::FunctionCall { args, .. } => {
11762 for a in args {
11763 visit_expr_columns_and_subqueries(a, on_col, on_sub);
11764 }
11765 }
11766 Expr::AggregateOrdered { call, order_by, .. } => {
11767 visit_expr_columns_and_subqueries(call, on_col, on_sub);
11768 for o in order_by {
11769 visit_expr_columns_and_subqueries(&o.expr, on_col, on_sub);
11770 }
11771 }
11772 Expr::Case {
11773 operand,
11774 branches,
11775 else_branch,
11776 } => {
11777 if let Some(op) = operand {
11778 visit_expr_columns_and_subqueries(op, on_col, on_sub);
11779 }
11780 for (w, t) in branches {
11781 visit_expr_columns_and_subqueries(w, on_col, on_sub);
11782 visit_expr_columns_and_subqueries(t, on_col, on_sub);
11783 }
11784 if let Some(eb) = else_branch {
11785 visit_expr_columns_and_subqueries(eb, on_col, on_sub);
11786 }
11787 }
11788 Expr::ArraySubscript { target, index } => {
11789 visit_expr_columns_and_subqueries(target, on_col, on_sub);
11790 visit_expr_columns_and_subqueries(index, on_col, on_sub);
11791 }
11792 Expr::Literal(_) | Expr::Placeholder(_) => {}
11793 _ => {
11798 static BAIL: spg_sql::ast::ColumnName = spg_sql::ast::ColumnName {
11801 qualifier: None,
11802 name: String::new(),
11803 };
11804 on_col(&BAIL);
11805 }
11806 }
11807}
11808
11809fn collect_column_qualifiers<'e>(e: &'e Expr, out: &mut Vec<&'e str>, all_qualified: &mut bool) {
11813 if let Expr::Column(c) = e {
11814 match &c.qualifier {
11815 Some(q) => out.push(q.as_str()),
11816 None => *all_qualified = false,
11817 }
11818 return;
11819 }
11820 match e {
11823 Expr::Binary { lhs, rhs, .. } => {
11824 collect_column_qualifiers(lhs, out, all_qualified);
11825 collect_column_qualifiers(rhs, out, all_qualified);
11826 }
11827 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11828 collect_column_qualifiers(expr, out, all_qualified);
11829 }
11830 Expr::Like { expr, pattern, .. } => {
11831 collect_column_qualifiers(expr, out, all_qualified);
11832 collect_column_qualifiers(pattern, out, all_qualified);
11833 }
11834 Expr::FunctionCall { args, .. } => {
11835 for a in args {
11836 collect_column_qualifiers(a, out, all_qualified);
11837 }
11838 }
11839 Expr::Literal(_) | Expr::Placeholder(_) => {}
11840 _ => *all_qualified = false,
11843 }
11844}
11845
11846fn expr_refers_to(e: &Expr, target: &str) -> bool {
11847 match e {
11848 Expr::AggregateOrdered { call, order_by, .. } => {
11849 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
11850 }
11851 Expr::ScalarSubquery(s) => select_refers_to(s, target),
11852 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
11853 select_refers_to(subquery, target)
11854 }
11855 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
11856 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11857 expr_refers_to(expr, target)
11858 }
11859 Expr::Like { expr, pattern, .. } => {
11860 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
11861 }
11862 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
11863 Expr::Extract { source, .. } => expr_refers_to(source, target),
11864 Expr::WindowFunction {
11865 args,
11866 partition_by,
11867 order_by,
11868 ..
11869 } => {
11870 args.iter().any(|a| expr_refers_to(a, target))
11871 || partition_by.iter().any(|p| expr_refers_to(p, target))
11872 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
11873 }
11874 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
11875 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
11876 Expr::InList { expr, list, .. } => {
11877 expr_refers_to(expr, target) || list.iter().any(|e| expr_refers_to(e, target))
11878 }
11879 Expr::ArraySubscript { target: t, index } => {
11880 expr_refers_to(t, target) || expr_refers_to(index, target)
11881 }
11882 Expr::AnyAll { expr, array, .. } => {
11883 expr_refers_to(expr, target) || expr_refers_to(array, target)
11884 }
11885 Expr::Case {
11886 operand,
11887 branches,
11888 else_branch,
11889 } => {
11890 operand
11891 .as_deref()
11892 .is_some_and(|o| expr_refers_to(o, target))
11893 || branches
11894 .iter()
11895 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
11896 || else_branch
11897 .as_deref()
11898 .is_some_and(|e| expr_refers_to(e, target))
11899 }
11900 }
11901}
11902
11903fn pg_data_type_text(ty: DataType) -> alloc::string::String {
11914 let s = match ty {
11915 DataType::Int => "integer",
11916 DataType::BigInt => "bigint",
11917 DataType::SmallInt => "smallint",
11918 DataType::Float => "double precision",
11919 DataType::Bool => "boolean",
11920 DataType::Text => "text",
11921 DataType::Varchar(_) => "character varying",
11922 DataType::Date => "date",
11923 DataType::Timestamp => "timestamp without time zone",
11924 DataType::Timestamptz => "timestamp with time zone",
11925 DataType::Json => "jsonb",
11926 DataType::Bytes => "bytea",
11927 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
11928 DataType::TsVector => "tsvector",
11929 DataType::TsQuery => "tsquery",
11930 DataType::Vector { .. } => "USER-DEFINED",
11931 _ => "USER-DEFINED",
11934 };
11935 alloc::string::String::from(s)
11936}
11937
11938fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11945 let schema = alloc::vec![
11946 ColumnSchema::new("table_catalog", DataType::Text, false),
11947 ColumnSchema::new("table_schema", DataType::Text, false),
11948 ColumnSchema::new("table_name", DataType::Text, false),
11949 ColumnSchema::new("column_name", DataType::Text, false),
11950 ColumnSchema::new("ordinal_position", DataType::Int, false),
11951 ColumnSchema::new("is_nullable", DataType::Text, false),
11952 ColumnSchema::new("data_type", DataType::Text, false),
11953 ];
11954 let mut rows: Vec<Row> = Vec::new();
11955 for tname in cat.table_names() {
11956 let Some(t) = cat.get(&tname) else { continue };
11957 for (i, col) in t.schema().columns.iter().enumerate() {
11958 #[allow(clippy::cast_possible_wrap)]
11959 let ordinal = (i + 1) as i32;
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(col.name.clone()),
11965 Value::Int(ordinal),
11966 Value::Text(if col.nullable {
11967 "YES".into()
11968 } else {
11969 "NO".into()
11970 }),
11971 Value::Text(pg_data_type_text(col.ty)),
11972 ]));
11973 }
11974 }
11975 (schema, rows)
11976}
11977
11978fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11980 let schema = alloc::vec![
11981 ColumnSchema::new("table_catalog", DataType::Text, false),
11982 ColumnSchema::new("table_schema", DataType::Text, false),
11983 ColumnSchema::new("table_name", DataType::Text, false),
11984 ColumnSchema::new("table_type", DataType::Text, false),
11985 ];
11986 let mut rows: Vec<Row> = Vec::new();
11987 for tname in cat.table_names() {
11988 rows.push(Row::new(alloc::vec![
11989 Value::Text("spg".into()),
11990 Value::Text("public".into()),
11991 Value::Text(tname.clone()),
11992 Value::Text("BASE TABLE".into()),
11993 ]));
11994 }
11995 (schema, rows)
11996}
11997
11998fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12002 let schema = alloc::vec![
12003 ColumnSchema::new("relname", DataType::Text, false),
12004 ColumnSchema::new("relkind", DataType::Text, false),
12005 ColumnSchema::new("relnamespace", DataType::BigInt, false),
12006 ];
12007 let mut rows: Vec<Row> = Vec::new();
12008 for tname in cat.table_names() {
12009 rows.push(Row::new(alloc::vec![
12010 Value::Text(tname.clone()),
12011 Value::Text("r".into()),
12012 Value::BigInt(2200), ]));
12014 }
12015 (schema, rows)
12016}
12017
12018fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12022 let schema = alloc::vec![
12023 ColumnSchema::new("attrelid", DataType::Text, false),
12024 ColumnSchema::new("attname", DataType::Text, false),
12025 ColumnSchema::new("attnum", DataType::Int, false),
12026 ColumnSchema::new("atttypid", DataType::Text, false),
12027 ColumnSchema::new("attnotnull", DataType::Bool, false),
12028 ];
12029 let mut rows: Vec<Row> = Vec::new();
12030 for tname in cat.table_names() {
12031 let Some(t) = cat.get(&tname) else { continue };
12032 for (i, col) in t.schema().columns.iter().enumerate() {
12033 #[allow(clippy::cast_possible_wrap)]
12034 let ordinal = (i + 1) as i32;
12035 rows.push(Row::new(alloc::vec![
12036 Value::Text(tname.clone()),
12037 Value::Text(col.name.clone()),
12038 Value::Int(ordinal),
12039 Value::Text(pg_data_type_text(col.ty)),
12040 Value::Bool(!col.nullable),
12041 ]));
12042 }
12043 }
12044 (schema, rows)
12045}
12046
12047fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12064 let schema = alloc::vec![
12065 ColumnSchema::new("oid", DataType::BigInt, false),
12066 ColumnSchema::new("typname", DataType::Text, false),
12067 ColumnSchema::new("typlen", DataType::SmallInt, false),
12068 ColumnSchema::new("typtype", DataType::Text, false),
12069 ColumnSchema::new("typcategory", DataType::Text, false),
12070 ColumnSchema::new("typelem", DataType::BigInt, false),
12071 ColumnSchema::new("typarray", DataType::BigInt, false),
12072 ColumnSchema::new("typnamespace", DataType::BigInt, false),
12073 ];
12074 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
12077 (16, "bool", 1, "b", "B", 0, 1000),
12079 (17, "bytea", -1, "b", "U", 0, 1001),
12080 (18, "char", 1, "b", "S", 0, 1002),
12081 (19, "name", 64, "b", "S", 0, 1003),
12082 (20, "int8", 8, "b", "N", 0, 1016),
12083 (21, "int2", 2, "b", "N", 0, 1005),
12084 (23, "int4", 4, "b", "N", 0, 1007),
12085 (24, "regproc", 4, "b", "N", 0, 1008),
12086 (25, "text", -1, "b", "S", 0, 1009),
12087 (26, "oid", 4, "b", "N", 0, 1028),
12088 (114, "json", -1, "b", "U", 0, 199),
12089 (142, "xml", -1, "b", "U", 0, 143),
12090 (700, "float4", 4, "b", "N", 0, 1021),
12091 (701, "float8", 8, "b", "N", 0, 1022),
12092 (650, "cidr", -1, "b", "I", 0, 651),
12093 (869, "inet", -1, "b", "I", 0, 1041),
12094 (829, "macaddr", 6, "b", "U", 0, 1040),
12095 (1042, "bpchar", -1, "b", "S", 0, 1014),
12096 (1043, "varchar", -1, "b", "S", 0, 1015),
12097 (1082, "date", 4, "b", "D", 0, 1182),
12098 (1083, "time", 8, "b", "D", 0, 1183),
12099 (1114, "timestamp", 8, "b", "D", 0, 1115),
12100 (1184, "timestamptz", 8, "b", "D", 0, 1185),
12101 (1186, "interval", 16, "b", "T", 0, 1187),
12102 (1266, "timetz", 12, "b", "D", 0, 1270),
12103 (1700, "numeric", -1, "b", "N", 0, 1231),
12104 (790, "money", 8, "b", "N", 0, 791),
12105 (2950, "uuid", 16, "b", "U", 0, 2951),
12106 (3802, "jsonb", -1, "b", "U", 0, 3807),
12107 (3614, "tsvector", -1, "b", "U", 0, 3643),
12108 (3615, "tsquery", -1, "b", "U", 0, 3645),
12109 (3908, "tstzrange", -1, "r", "R", 0, 3909),
12111 (3910, "tsrange", -1, "r", "R", 0, 3911),
12112 (3904, "int4range", -1, "r", "R", 0, 3905),
12113 (3926, "int8range", -1, "r", "R", 0, 3927),
12114 (3906, "numrange", -1, "r", "R", 0, 3907),
12115 (3912, "daterange", -1, "r", "R", 0, 3913),
12116 ];
12117 let arrays: &[(i64, &str, i64)] = &[
12120 (1000, "_bool", 16),
12121 (1001, "_bytea", 17),
12122 (1002, "_char", 18),
12123 (1003, "_name", 19),
12124 (1016, "_int8", 20),
12125 (1005, "_int2", 21),
12126 (1007, "_int4", 23),
12127 (1008, "_regproc", 24),
12128 (1009, "_text", 25),
12129 (1028, "_oid", 26),
12130 (199, "_json", 114),
12131 (143, "_xml", 142),
12132 (1021, "_float4", 700),
12133 (1022, "_float8", 701),
12134 (651, "_cidr", 650),
12135 (1041, "_inet", 869),
12136 (1040, "_macaddr", 829),
12137 (1014, "_bpchar", 1042),
12138 (1015, "_varchar", 1043),
12139 (1182, "_date", 1082),
12140 (1183, "_time", 1083),
12141 (1115, "_timestamp", 1114),
12142 (1185, "_timestamptz", 1184),
12143 (1187, "_interval", 1186),
12144 (1270, "_timetz", 1266),
12145 (1231, "_numeric", 1700),
12146 (791, "_money", 790),
12147 (2951, "_uuid", 2950),
12148 (3807, "_jsonb", 3802),
12149 (3643, "_tsvector", 3614),
12150 (3645, "_tsquery", 3615),
12151 ];
12152 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
12153 for &(oid, name, len, ty, cat, elem, arr) in scalars {
12154 rows.push(Row::new(alloc::vec![
12155 Value::BigInt(oid),
12156 Value::Text(name.into()),
12157 Value::SmallInt(len),
12158 Value::Text(ty.into()),
12159 Value::Text(cat.into()),
12160 Value::BigInt(elem),
12161 Value::BigInt(arr),
12162 Value::BigInt(2200),
12163 ]));
12164 }
12165 for &(oid, name, elem) in arrays {
12166 rows.push(Row::new(alloc::vec![
12167 Value::BigInt(oid),
12168 Value::Text(name.into()),
12169 Value::SmallInt(-1),
12170 Value::Text("b".into()),
12171 Value::Text("A".into()),
12172 Value::BigInt(elem),
12173 Value::BigInt(0),
12174 Value::BigInt(2200),
12175 ]));
12176 }
12177 (schema, rows)
12178}
12179
12180fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12199 let schema = alloc::vec![
12200 ColumnSchema::new("tgname", DataType::Text, false),
12201 ColumnSchema::new("relname", DataType::Text, false),
12202 ColumnSchema::new("tgenabled", DataType::Text, false),
12203 ColumnSchema::new("timing", DataType::Text, false),
12204 ColumnSchema::new("events", DataType::Text, false),
12205 ColumnSchema::new("function", DataType::Text, false),
12206 ];
12207 let rows: Vec<Row> = cat
12208 .triggers()
12209 .iter()
12210 .map(|t| {
12211 Row::new(alloc::vec![
12212 Value::Text(t.name.clone()),
12213 Value::Text(t.table.clone()),
12214 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
12215 Value::Text(t.timing.clone()),
12216 Value::Text(t.events.join(" OR ")),
12217 Value::Text(t.function.clone()),
12218 ])
12219 })
12220 .collect();
12221 (schema, rows)
12222}
12223
12224fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12225 let schema = alloc::vec![
12226 ColumnSchema::new("oid", DataType::BigInt, false),
12227 ColumnSchema::new("proname", DataType::Text, false),
12228 ColumnSchema::new("pronamespace", DataType::BigInt, false),
12229 ColumnSchema::new("prokind", DataType::Text, false),
12230 ColumnSchema::new("pronargs", DataType::Int, false),
12231 ColumnSchema::new("prorettype", DataType::BigInt, false),
12232 ];
12233 let funcs: &[(i64, &str, &str, i32, i64)] = &[
12236 (1318, "length", "f", 1, 23),
12238 (871, "upper", "f", 1, 25),
12239 (870, "lower", "f", 1, 25),
12240 (936, "substring", "f", 3, 25),
12241 (937, "substring", "f", 2, 25),
12242 (3055, "btrim", "f", 1, 25),
12243 (885, "btrim", "f", 2, 25),
12244 (3056, "ltrim", "f", 1, 25),
12245 (875, "ltrim", "f", 2, 25),
12246 (3057, "rtrim", "f", 1, 25),
12247 (876, "rtrim", "f", 2, 25),
12248 (1397, "abs", "f", 1, 23),
12249 (1396, "abs", "f", 1, 20),
12250 (1606, "round", "f", 1, 1700),
12251 (1707, "round", "f", 2, 1700),
12252 (2308, "ceil", "f", 1, 701),
12253 (2309, "ceiling", "f", 1, 701),
12254 (2310, "floor", "f", 1, 701),
12255 (1376, "sqrt", "f", 1, 701),
12256 (1369, "ln", "f", 1, 701),
12257 (1373, "exp", "f", 1, 701),
12258 (1368, "power", "f", 2, 701),
12259 (2228, "random", "f", 0, 701),
12260 (1299, "now", "f", 0, 1184),
12262 (1274, "current_timestamp", "f", 0, 1184),
12263 (1140, "current_date", "f", 0, 1082),
12264 (2050, "current_time", "f", 0, 1083),
12265 (1158, "date_trunc", "f", 2, 1184),
12266 (1171, "date_part", "f", 2, 701),
12267 (1172, "age", "f", 1, 1186),
12268 (936, "to_char", "f", 2, 25),
12269 (861, "current_database", "f", 0, 19),
12271 (745, "current_user", "f", 0, 19),
12272 (745, "session_user", "f", 0, 19),
12273 (1402, "current_schema", "f", 0, 19),
12274 (3058, "concat", "f", -1, 25),
12276 (3059, "concat_ws", "f", -1, 25),
12277 (3539, "format", "f", -1, 25),
12278 (2877, "pg_typeof", "f", 1, 2206),
12280 (3198, "json_build_object", "f", -1, 114),
12282 (3199, "jsonb_build_object", "f", -1, 3802),
12283 (3271, "json_build_array", "f", -1, 114),
12284 (3272, "jsonb_build_array", "f", -1, 3802),
12285 (3253, "gen_random_uuid", "f", 0, 2950),
12287 (3252, "uuid_generate_v4", "f", 0, 2950),
12288 (2147, "count", "a", 0, 20),
12290 (2803, "count", "a", -1, 20),
12291 (2116, "max", "a", 1, 23),
12292 (2132, "min", "a", 1, 23),
12293 (2108, "sum", "a", 1, 20),
12294 (2100, "avg", "a", 1, 1700),
12295 (2517, "string_agg", "a", 2, 25),
12296 (2747, "array_agg", "a", 1, 1009),
12297 (2517, "bool_and", "a", 1, 16),
12298 (2518, "bool_or", "a", 1, 16),
12299 (2519, "every", "a", 1, 16),
12300 (3100, "row_number", "w", 0, 20),
12302 (3101, "rank", "w", 0, 20),
12303 (3102, "dense_rank", "w", 0, 20),
12304 (3103, "percent_rank", "w", 0, 701),
12305 (3104, "cume_dist", "w", 0, 701),
12306 (3105, "lag", "w", -1, 2283),
12307 (3106, "lead", "w", -1, 2283),
12308 (3107, "first_value", "w", 1, 2283),
12309 (3108, "last_value", "w", 1, 2283),
12310 (3109, "nth_value", "w", 2, 2283),
12311 ];
12312 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
12313 for &(oid, name, kind, nargs, rettype) in funcs {
12314 rows.push(Row::new(alloc::vec![
12315 Value::BigInt(oid),
12316 Value::Text(name.into()),
12317 Value::BigInt(11),
12318 Value::Text(kind.into()),
12319 Value::Int(nargs),
12320 Value::BigInt(rettype),
12321 ]));
12322 }
12323 (schema, rows)
12324}
12325
12326fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12332 let schema = alloc::vec![
12333 ColumnSchema::new("user", DataType::Text, false),
12334 ColumnSchema::new("host", DataType::Text, false),
12335 ColumnSchema::new("select_priv", DataType::Text, false),
12336 ];
12337 let mut rows: Vec<Row> = Vec::new();
12338 rows.push(Row::new(alloc::vec![
12339 Value::Text("root".into()),
12340 Value::Text("localhost".into()),
12341 Value::Text("Y".into()),
12342 ]));
12343 for (name, _) in engine.users.iter() {
12344 if name != "root" {
12345 rows.push(Row::new(alloc::vec![
12346 Value::Text(name.to_string()),
12347 Value::Text("%".into()),
12348 Value::Text("Y".into()),
12349 ]));
12350 }
12351 }
12352 (schema, rows)
12353}
12354
12355fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
12360 let schema = alloc::vec![
12361 ColumnSchema::new("host", DataType::Text, false),
12362 ColumnSchema::new("db", DataType::Text, false),
12363 ColumnSchema::new("user", DataType::Text, false),
12364 ColumnSchema::new("select_priv", DataType::Text, false),
12365 ];
12366 let rows = alloc::vec![Row::new(alloc::vec![
12367 Value::Text("localhost".into()),
12368 Value::Text("postgres".into()),
12369 Value::Text("root".into()),
12370 Value::Text("Y".into()),
12371 ])];
12372 (schema, rows)
12373}
12374
12375fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12388 let schema = alloc::vec![
12389 ColumnSchema::new("constraint_name", DataType::Text, false),
12390 ColumnSchema::new("table_name", DataType::Text, false),
12391 ColumnSchema::new("column_name", DataType::Text, false),
12392 ColumnSchema::new("ordinal_position", DataType::Int, false),
12393 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12394 ColumnSchema::new("referenced_column_name", DataType::Text, false),
12395 ];
12396 let mut rows: Vec<Row> = Vec::new();
12397 for tname in cat.table_names() {
12398 let Some(t) = cat.get(&tname) else { continue };
12399 let cols = &t.schema().columns;
12400 let col_name_at = |pos: usize| -> String {
12401 cols.get(pos)
12402 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12403 };
12404 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12406 let conname = fk
12407 .name
12408 .clone()
12409 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12410 for (i, (&local, &parent)) in fk
12411 .local_columns
12412 .iter()
12413 .zip(fk.parent_columns.iter())
12414 .enumerate()
12415 {
12416 let parent_name = cat
12417 .get(&fk.parent_table)
12418 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
12419 .unwrap_or_else(|| alloc::format!("col{parent}"));
12420 #[allow(clippy::cast_possible_wrap)]
12421 let ordinal = (i + 1) as i32;
12422 rows.push(Row::new(alloc::vec![
12423 Value::Text(conname.clone()),
12424 Value::Text(tname.clone()),
12425 Value::Text(col_name_at(local)),
12426 Value::Int(ordinal),
12427 Value::Text(fk.parent_table.clone()),
12428 Value::Text(parent_name),
12429 ]));
12430 }
12431 }
12432 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12434 let conname = if uc.is_primary_key {
12435 alloc::format!("{}_pkey", tname)
12436 } else {
12437 alloc::format!("{}_uniq{ci}", tname)
12438 };
12439 for (i, &local) in uc.columns.iter().enumerate() {
12440 #[allow(clippy::cast_possible_wrap)]
12441 let ordinal = (i + 1) as i32;
12442 rows.push(Row::new(alloc::vec![
12443 Value::Text(conname.clone()),
12444 Value::Text(tname.clone()),
12445 Value::Text(col_name_at(local)),
12446 Value::Int(ordinal),
12447 Value::Text(String::new()),
12448 Value::Text(String::new()),
12449 ]));
12450 }
12451 }
12452 }
12453 (schema, rows)
12454}
12455
12456fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12459 let schema = alloc::vec![
12460 ColumnSchema::new("constraint_name", DataType::Text, false),
12461 ColumnSchema::new("table_name", DataType::Text, false),
12462 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12463 ColumnSchema::new("update_rule", DataType::Text, false),
12464 ColumnSchema::new("delete_rule", DataType::Text, false),
12465 ];
12466 fn rule_name(a: spg_storage::FkAction) -> &'static str {
12467 match a {
12468 spg_storage::FkAction::Cascade => "CASCADE",
12469 spg_storage::FkAction::SetNull => "SET NULL",
12470 spg_storage::FkAction::SetDefault => "SET DEFAULT",
12471 spg_storage::FkAction::Restrict => "RESTRICT",
12472 spg_storage::FkAction::NoAction => "NO ACTION",
12473 }
12474 }
12475 let mut rows: Vec<Row> = Vec::new();
12476 for tname in cat.table_names() {
12477 let Some(t) = cat.get(&tname) else { continue };
12478 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12479 let conname = fk
12480 .name
12481 .clone()
12482 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12483 rows.push(Row::new(alloc::vec![
12484 Value::Text(conname),
12485 Value::Text(tname.clone()),
12486 Value::Text(fk.parent_table.clone()),
12487 Value::Text(rule_name(fk.on_update).into()),
12488 Value::Text(rule_name(fk.on_delete).into()),
12489 ]));
12490 }
12491 }
12492 (schema, rows)
12493}
12494
12495fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12499 let schema = alloc::vec![
12500 ColumnSchema::new("table_name", DataType::Text, false),
12501 ColumnSchema::new("index_name", DataType::Text, false),
12502 ColumnSchema::new("column_name", DataType::Text, false),
12503 ColumnSchema::new("seq_in_index", DataType::Int, false),
12504 ColumnSchema::new("non_unique", DataType::Int, false),
12505 ColumnSchema::new("index_type", DataType::Text, false),
12506 ];
12507 let mut rows: Vec<Row> = Vec::new();
12508 for tname in cat.table_names() {
12509 let Some(t) = cat.get(&tname) else { continue };
12510 for idx in t.indices() {
12511 let col = t
12512 .schema()
12513 .columns
12514 .get(idx.column_position)
12515 .map_or("?".into(), |c| c.name.clone());
12516 rows.push(Row::new(alloc::vec![
12517 Value::Text(tname.clone()),
12518 Value::Text(idx.name.clone()),
12519 Value::Text(col),
12520 Value::Int(1),
12521 Value::Int(i32::from(!idx.is_unique)),
12522 Value::Text("BTREE".into()),
12523 ]));
12524 }
12525 }
12526 (schema, rows)
12527}
12528
12529fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
12533 let schema = alloc::vec![
12534 ColumnSchema::new("routine_name", DataType::Text, false),
12535 ColumnSchema::new("routine_type", DataType::Text, false),
12536 ColumnSchema::new("data_type", DataType::Text, false),
12537 ];
12538 (schema, Vec::new())
12539}
12540
12541fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12556 let schema = alloc::vec![
12557 ColumnSchema::new("conname", DataType::Text, false),
12558 ColumnSchema::new("contype", DataType::Text, false),
12559 ColumnSchema::new("conrelid", DataType::Text, false),
12560 ColumnSchema::new("confrelid", DataType::Text, false),
12561 ColumnSchema::new("conkey", DataType::Text, false),
12562 ColumnSchema::new("confkey", DataType::Text, false),
12563 ];
12564 let mut rows: Vec<Row> = Vec::new();
12565 for tname in cat.table_names() {
12566 let Some(t) = cat.get(&tname) else { continue };
12567 let cols = &t.schema().columns;
12568 let col_name_at = |pos: usize| -> String {
12569 cols.get(pos)
12570 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12571 };
12572 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12574 let kind = if uc.is_primary_key { "p" } else { "u" };
12575 let conname = if uc.is_primary_key {
12576 alloc::format!("{}_pkey", tname)
12577 } else {
12578 alloc::format!("{}_uniq{ci}", tname)
12579 };
12580 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
12581 rows.push(Row::new(alloc::vec![
12582 Value::Text(conname),
12583 Value::Text(kind.into()),
12584 Value::Text(tname.clone()),
12585 Value::Text(String::new()),
12586 Value::Text(conkey.join(",")),
12587 Value::Text(String::new()),
12588 ]));
12589 }
12590 for idx in t.indices() {
12595 if !idx.is_unique {
12596 continue;
12597 }
12598 let is_primary = idx.name.ends_with("_pkey");
12599 let conname = idx.name.clone();
12600 let kind = if is_primary { "p" } else { "u" };
12601 let col_name = col_name_at(idx.column_position);
12602 let already = t
12605 .schema()
12606 .uniqueness_constraints
12607 .iter()
12608 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
12609 if already {
12610 continue;
12611 }
12612 rows.push(Row::new(alloc::vec![
12613 Value::Text(conname),
12614 Value::Text(kind.into()),
12615 Value::Text(tname.clone()),
12616 Value::Text(String::new()),
12617 Value::Text(col_name),
12618 Value::Text(String::new()),
12619 ]));
12620 }
12621 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12623 let conname = fk
12624 .name
12625 .clone()
12626 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12627 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
12628 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
12631 fk.parent_columns
12632 .iter()
12633 .map(|&p| {
12634 parent
12635 .schema()
12636 .columns
12637 .get(p)
12638 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
12639 })
12640 .collect()
12641 } else {
12642 fk.parent_columns
12643 .iter()
12644 .map(|p| alloc::format!("col{p}"))
12645 .collect()
12646 };
12647 rows.push(Row::new(alloc::vec![
12648 Value::Text(conname),
12649 Value::Text("f".into()),
12650 Value::Text(tname.clone()),
12651 Value::Text(fk.parent_table.clone()),
12652 Value::Text(conkey.join(",")),
12653 Value::Text(confkey.join(",")),
12654 ]));
12655 }
12656 }
12657 (schema, rows)
12658}
12659
12660fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12665 let schema = alloc::vec![
12666 ColumnSchema::new("oid", DataType::BigInt, false),
12667 ColumnSchema::new("datname", DataType::Text, false),
12668 ColumnSchema::new("datdba", DataType::BigInt, false),
12669 ColumnSchema::new("encoding", DataType::Int, false),
12670 ColumnSchema::new("datcollate", DataType::Text, false),
12671 ];
12672 let rows = alloc::vec![Row::new(alloc::vec![
12673 Value::BigInt(16384),
12674 Value::Text("postgres".into()),
12675 Value::BigInt(10),
12676 Value::Int(6), Value::Text("en_US.UTF-8".into()),
12678 ])];
12679 (schema, rows)
12680}
12681
12682fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12687 let schema = alloc::vec![
12688 ColumnSchema::new("oid", DataType::BigInt, false),
12689 ColumnSchema::new("rolname", DataType::Text, false),
12690 ColumnSchema::new("rolsuper", DataType::Bool, false),
12691 ColumnSchema::new("rolinherit", DataType::Bool, false),
12692 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
12693 ];
12694 let mut rows: Vec<Row> = Vec::new();
12695 let oid: i64 = 10;
12696 for (i, (name, _)) in engine.users.iter().enumerate() {
12697 rows.push(Row::new(alloc::vec![
12698 Value::BigInt(oid + (i as i64) + 1),
12699 Value::Text(name.to_string()),
12700 Value::Bool(false),
12701 Value::Bool(true),
12702 Value::Bool(true),
12703 ]));
12704 }
12705 if !rows
12708 .iter()
12709 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
12710 {
12711 rows.insert(
12712 0,
12713 Row::new(alloc::vec![
12714 Value::BigInt(10),
12715 Value::Text("postgres".into()),
12716 Value::Bool(true),
12717 Value::Bool(true),
12718 Value::Bool(true),
12719 ]),
12720 );
12721 }
12722 (schema, rows)
12723}
12724
12725fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
12734 let schema = alloc::vec![
12735 ColumnSchema::new("oid", DataType::BigInt, false),
12736 ColumnSchema::new("extname", DataType::Text, false),
12737 ColumnSchema::new("extversion", DataType::Text, false),
12738 ColumnSchema::new("extnamespace", DataType::Text, false),
12739 ];
12740 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
12741 let rows = exts
12742 .iter()
12743 .enumerate()
12744 .map(|(i, (name, ver))| {
12745 Row::new(alloc::vec![
12746 Value::BigInt(16384 + i as i64),
12747 Value::Text((*name).into()),
12748 Value::Text((*ver).into()),
12749 Value::Text("pg_catalog".into()),
12750 ])
12751 })
12752 .collect();
12753 (schema, rows)
12754}
12755
12756fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12757 let schema = alloc::vec![
12758 ColumnSchema::new("schemaname", DataType::Text, false),
12759 ColumnSchema::new("viewname", DataType::Text, false),
12760 ColumnSchema::new("definition", DataType::Text, false),
12761 ];
12762 let mut rows: Vec<Row> = Vec::new();
12763 for (name, def) in cat.views() {
12764 rows.push(Row::new(alloc::vec![
12765 Value::Text("public".into()),
12766 Value::Text(name.clone()),
12767 Value::Text(def.body.clone()),
12768 ]));
12769 }
12770 (schema, rows)
12771}
12772
12773fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12779 let schema = alloc::vec![
12780 ColumnSchema::new("name", DataType::Text, false),
12781 ColumnSchema::new("setting", DataType::Text, false),
12782 ColumnSchema::new("category", DataType::Text, false),
12783 ];
12784 let mut rows: Vec<Row> = Vec::new();
12785 let defaults: &[(&str, &str, &str)] = &[
12787 ("server_version", "16.0 (spg)", "Preset Options"),
12788 ("server_encoding", "UTF8", "Client Connection Defaults"),
12789 ("client_encoding", "UTF8", "Client Connection Defaults"),
12790 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
12791 ("TimeZone", "UTC", "Client Connection Defaults"),
12792 ("standard_conforming_strings", "on", "Compatibility"),
12793 ("integer_datetimes", "on", "Compatibility"),
12794 ("max_connections", "100", "Connections and Authentication"),
12795 ];
12796 for &(name, val, cat) in defaults {
12797 rows.push(Row::new(alloc::vec![
12798 Value::Text(name.into()),
12799 Value::Text(val.into()),
12800 Value::Text(cat.into()),
12801 ]));
12802 }
12803 for (k, v) in &engine.session_params {
12805 if !defaults
12806 .iter()
12807 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
12808 {
12809 rows.push(Row::new(alloc::vec![
12810 Value::Text(k.clone()),
12811 Value::Text(v.clone()),
12812 Value::Text("Session".into()),
12813 ]));
12814 }
12815 }
12816 (schema, rows)
12817}
12818
12819fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12830 let schema = alloc::vec![
12831 ColumnSchema::new("schemaname", DataType::Text, false),
12832 ColumnSchema::new("tablename", DataType::Text, false),
12833 ColumnSchema::new("indexname", DataType::Text, false),
12834 ColumnSchema::new("indexdef", DataType::Text, false),
12835 ];
12836 let mut rows: Vec<Row> = Vec::new();
12837 for tname in cat.table_names() {
12838 let Some(t) = cat.get(&tname) else { continue };
12839 for idx in t.indices() {
12840 let col_name = t
12841 .schema()
12842 .columns
12843 .get(idx.column_position)
12844 .map_or("?".into(), |c| c.name.clone());
12845 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
12846 let indexdef = alloc::format!(
12847 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
12848 idx.name,
12849 tname,
12850 col_name
12851 );
12852 rows.push(Row::new(alloc::vec![
12853 Value::Text("public".into()),
12854 Value::Text(tname.clone()),
12855 Value::Text(idx.name.clone()),
12856 Value::Text(indexdef),
12857 ]));
12858 }
12859 }
12860 (schema, rows)
12861}
12862
12863fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12875 let schema = alloc::vec![
12876 ColumnSchema::new("indexrelid", DataType::BigInt, false),
12877 ColumnSchema::new("indrelid", DataType::BigInt, false),
12878 ColumnSchema::new("indnatts", DataType::Int, false),
12879 ColumnSchema::new("indisunique", DataType::Bool, false),
12880 ColumnSchema::new("indisprimary", DataType::Bool, false),
12881 ];
12882 let mut rows: Vec<Row> = Vec::new();
12883 let mut idx_oid: i64 = 100_000;
12884 for (table_idx, tname) in cat.table_names().iter().enumerate() {
12885 let Some(t) = cat.get(tname) else { continue };
12886 for idx in t.indices() {
12887 idx_oid += 1;
12888 #[allow(clippy::cast_possible_wrap)]
12889 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
12890 let is_primary = idx.name.ends_with("_pkey");
12893 rows.push(Row::new(alloc::vec![
12894 Value::BigInt(idx_oid),
12895 Value::BigInt((table_idx + 1) as i64),
12896 Value::Int(nattrs),
12897 Value::Bool(idx.is_unique),
12898 Value::Bool(is_primary),
12899 ]));
12900 }
12901 }
12902 (schema, rows)
12903}
12904
12905fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12910 let schema = alloc::vec![
12911 ColumnSchema::new("oid", DataType::BigInt, false),
12912 ColumnSchema::new("nspname", DataType::Text, false),
12913 ColumnSchema::new("nspowner", DataType::BigInt, false),
12914 ];
12915 let rows = alloc::vec![
12916 Row::new(alloc::vec![
12917 Value::BigInt(11),
12918 Value::Text("pg_catalog".into()),
12919 Value::BigInt(10),
12920 ]),
12921 Row::new(alloc::vec![
12922 Value::BigInt(2200),
12923 Value::Text("public".into()),
12924 Value::BigInt(10),
12925 ]),
12926 Row::new(alloc::vec![
12927 Value::BigInt(13000),
12928 Value::Text("information_schema".into()),
12929 Value::BigInt(10),
12930 ]),
12931 ];
12932 (schema, rows)
12933}
12934
12935fn materialise_meta_view(
12938 catalog: &mut Catalog,
12939 name: &str,
12940 columns: Vec<ColumnSchema>,
12941 rows: Vec<Row>,
12942) -> Result<(), EngineError> {
12943 let schema = TableSchema::new(name.to_string(), columns);
12944 catalog.create_table(schema).map_err(EngineError::Storage)?;
12945 let table = catalog
12946 .get_mut(name)
12947 .expect("just-created meta view must exist");
12948 for row in rows {
12949 table.insert(row).map_err(EngineError::Storage)?;
12950 }
12951 Ok(())
12952}
12953
12954fn collect_view_refs(
12967 tref: &spg_sql::ast::TableRef,
12968 cat: &spg_storage::Catalog,
12969 into: &mut Vec<String>,
12970) {
12971 if cat.views().contains_key(&tref.name)
12972 && cat.get(&tref.name).is_none()
12973 && !into.iter().any(|n| n == &tref.name)
12974 {
12975 into.push(tref.name.clone());
12976 }
12977}
12978
12979fn select_references_meta_view(stmt: &SelectStatement) -> bool {
12980 fn is_meta(name: &str) -> bool {
12981 name.starts_with("__spg_info_")
12982 || name.starts_with("__spg_pg_")
12983 || name.starts_with("__spg_mysql_")
12984 }
12985 if let Some(from) = &stmt.from {
12986 if is_meta(&from.primary.name) {
12987 return true;
12988 }
12989 for j in &from.joins {
12990 if is_meta(&j.table.name) {
12991 return true;
12992 }
12993 }
12994 }
12995 for cte in &stmt.ctes {
12996 if select_references_meta_view(&cte.body) {
12997 return true;
12998 }
12999 }
13000 false
13001}
13002
13003fn collect_meta_view_names(
13008 stmt: &SelectStatement,
13009 into: &mut alloc::collections::BTreeSet<String>,
13010) {
13011 fn is_meta(name: &str) -> bool {
13012 name.starts_with("__spg_info_")
13013 || name.starts_with("__spg_pg_")
13014 || name.starts_with("__spg_mysql_")
13015 }
13016 if let Some(from) = &stmt.from {
13017 if is_meta(&from.primary.name) {
13018 into.insert(from.primary.name.clone());
13019 }
13020 for j in &from.joins {
13021 if is_meta(&j.table.name) {
13022 into.insert(j.table.name.clone());
13023 }
13024 }
13025 }
13026 for cte in &stmt.ctes {
13027 collect_meta_view_names(&cte.body, into);
13028 }
13029}
13030
13031fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
13032 let mut out = columns.to_vec();
13033 for (col_idx, col) in out.iter_mut().enumerate() {
13034 if col.ty != DataType::Text {
13035 continue;
13036 }
13037 let mut inferred: Option<DataType> = None;
13038 let mut all_null = true;
13039 for row in rows {
13040 let Some(v) = row.values.get(col_idx) else {
13041 continue;
13042 };
13043 let ty = match v {
13044 Value::Null => continue,
13045 Value::SmallInt(_) => DataType::SmallInt,
13046 Value::Int(_) => DataType::Int,
13047 Value::BigInt(_) => DataType::BigInt,
13048 Value::Float(_) => DataType::Float,
13049 Value::Bool(_) => DataType::Bool,
13050 Value::Vector(_) => DataType::Vector {
13051 dim: 0,
13052 encoding: VecEncoding::F32,
13053 },
13054 _ => DataType::Text,
13055 };
13056 all_null = false;
13057 inferred = Some(match inferred {
13058 None => ty,
13059 Some(prev) if prev == ty => prev,
13060 Some(_) => DataType::Text,
13061 });
13062 }
13063 if let Some(t) = inferred {
13064 col.ty = t;
13065 col.nullable = true;
13066 } else if all_null {
13067 col.nullable = true;
13068 }
13069 }
13070 out
13071}
13072
13073#[allow(clippy::too_many_lines, clippy::format_push_string)]
13078fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
13095 use alloc::collections::BTreeSet;
13096 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
13097 let mut out: Vec<String> = Vec::new();
13098 let cat = engine.active_catalog();
13099 let Some(from) = &stmt.from else {
13103 return out;
13104 };
13105 let mut tables: Vec<String> = Vec::new();
13106 tables.push(from.primary.name.clone());
13107 for j in &from.joins {
13108 tables.push(j.table.name.clone());
13109 }
13110 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
13113 if let Some(w) = &stmt.where_ {
13114 collect_column_refs(w, &mut col_refs);
13115 }
13116 for j in &from.joins {
13117 if let Some(on) = &j.on {
13118 collect_column_refs(on, &mut col_refs);
13119 }
13120 }
13121 for cn in &col_refs {
13122 let owner: Option<String> = if let Some(q) = &cn.qualifier {
13125 tables.iter().find(|t| t == &q).cloned()
13126 } else {
13127 tables.iter().find_map(|t| {
13128 cat.get(t).and_then(|tbl| {
13129 if tbl.schema().column_position(&cn.name).is_some() {
13130 Some(t.clone())
13131 } else {
13132 None
13133 }
13134 })
13135 })
13136 };
13137 let Some(owner) = owner else {
13138 continue;
13139 };
13140 let Some(tbl) = cat.get(&owner) else {
13141 continue;
13142 };
13143 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
13144 continue;
13145 };
13146 let already_indexed = tbl.indices().iter().any(|i| {
13149 matches!(i.kind, spg_storage::IndexKind::BTree(_))
13150 && i.column_position == col_pos
13151 && i.expression.is_none()
13152 && i.partial_predicate.is_none()
13153 });
13154 if already_indexed {
13155 continue;
13156 }
13157 if seen.insert((owner.clone(), cn.name.clone())) {
13158 out.push(alloc::format!(
13159 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
13160 owner,
13161 cn.name,
13162 owner,
13163 cn.name
13164 ));
13165 }
13166 }
13167 out
13168}
13169
13170fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
13173 match expr {
13174 Expr::Column(cn) => out.push(cn.clone()),
13175 Expr::FunctionCall { args, .. } => {
13176 for a in args {
13177 collect_column_refs(a, out);
13178 }
13179 }
13180 Expr::Binary { lhs, rhs, .. } => {
13181 collect_column_refs(lhs, out);
13182 collect_column_refs(rhs, out);
13183 }
13184 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
13185 _ => {}
13186 }
13187}
13188
13189fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
13190 let catalog = engine.active_catalog();
13191 let cold_ids = catalog.cold_segment_ids_global();
13192 let any_cold = !cold_ids.is_empty();
13193 let cold_ids_repr = if any_cold {
13194 let mut s = alloc::string::String::from("[");
13195 for (i, id) in cold_ids.iter().enumerate() {
13196 if i > 0 {
13197 s.push(',');
13198 }
13199 s.push_str(&alloc::format!("{id}"));
13200 }
13201 s.push(']');
13202 s
13203 } else {
13204 alloc::string::String::new()
13205 };
13206 for (idx, line) in lines.iter_mut().enumerate() {
13207 let trimmed = line.trim_start();
13208 let is_top_level = idx == 0;
13209 if is_top_level {
13210 line.push_str(&alloc::format!(" (rows={total_rows})"));
13211 continue;
13212 }
13213 if let Some(rest) = trimmed.strip_prefix("From: ") {
13214 let (name, scan_kind) = match rest.split_once(" [") {
13215 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
13216 None => (rest.trim(), ""),
13217 };
13218 let bare = name.split_whitespace().next().unwrap_or(name);
13219 let hot = catalog.get(bare).map(|t| t.rows().len());
13220 let annot = match (hot, scan_kind) {
13225 (Some(h), "full scan") => {
13226 let mut s = alloc::format!(" (hot_rows={h}");
13227 if any_cold {
13228 s.push_str(&alloc::format!(
13229 ", cold_tier=present, cold_segments={cold_ids_repr}"
13230 ));
13231 }
13232 s.push(')');
13233 s
13234 }
13235 (Some(h), "index seek") => {
13236 let mut s = alloc::format!(" (hot_rows≤{h}");
13237 if any_cold {
13238 s.push_str(&alloc::format!(
13239 ", cold_tier=present, cold_segments={cold_ids_repr}"
13240 ));
13241 }
13242 s.push(')');
13243 s
13244 }
13245 _ => " (rows=—)".to_string(),
13246 };
13247 line.push_str(&annot);
13248 continue;
13249 }
13250 line.push_str(" (rows=—)");
13252 }
13253}
13254
13255fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
13256 let pad = " ".repeat(depth);
13257 let top = if !stmt.ctes.is_empty() {
13259 if stmt.ctes.iter().any(|c| c.recursive) {
13260 "CTEScan (WITH RECURSIVE)"
13261 } else {
13262 "CTEScan (WITH)"
13263 }
13264 } else if !stmt.unions.is_empty() {
13265 "UnionScan"
13266 } else if select_has_window(stmt) {
13267 "WindowAgg"
13268 } else if aggregate::uses_aggregate(stmt) {
13269 "Aggregate"
13270 } else if stmt.distinct {
13271 "Distinct"
13272 } else if stmt.from.is_some() {
13273 "TableScan"
13274 } else {
13275 "Result"
13276 };
13277 out.push(alloc::format!("{pad}{top}"));
13278 let child = " ".repeat(depth + 1);
13279 for cte in &stmt.ctes {
13281 let head = if cte.recursive {
13282 alloc::format!("{child}CTE (recursive): {}", cte.name)
13283 } else {
13284 alloc::format!("{child}CTE: {}", cte.name)
13285 };
13286 out.push(head);
13287 explain_select(&cte.body, engine, depth + 2, out);
13288 }
13289 if let Some(from) = &stmt.from {
13291 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
13292 if let Some(alias) = &from.primary.alias {
13293 tag.push_str(&alloc::format!(" AS {alias}"));
13294 }
13295 if let Some(w) = &stmt.where_
13298 && let Some(table) = engine.active_catalog().get(&from.primary.name)
13299 {
13300 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
13301 let cols = &table.schema().columns;
13302 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
13303 tag.push_str(" [index seek]");
13304 } else {
13305 tag.push_str(" [full scan]");
13306 }
13307 } else {
13308 tag.push_str(" [full scan]");
13309 }
13310 out.push(tag);
13311 for j in &from.joins {
13312 let kind = match j.kind {
13313 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
13314 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
13315 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
13316 };
13317 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
13318 if let Some(alias) = &j.table.alias {
13319 s.push_str(&alloc::format!(" AS {alias}"));
13320 }
13321 if j.on.is_some() {
13322 s.push_str(" (ON …)");
13323 }
13324 out.push(s);
13325 }
13326 }
13327 if let Some(w) = &stmt.where_ {
13329 let mut s = alloc::format!("{child}Filter: {w}");
13330 if expr_has_subquery(w) {
13331 s.push_str(" [subquery]");
13332 }
13333 out.push(s);
13334 }
13335 if let Some(gs) = &stmt.group_by {
13336 let mut parts = Vec::new();
13337 for g in gs {
13338 parts.push(alloc::format!("{g}"));
13339 }
13340 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
13341 }
13342 if let Some(h) = &stmt.having {
13343 out.push(alloc::format!("{child}Having: {h}"));
13344 }
13345 for o in &stmt.order_by {
13346 let dir = if o.desc { "DESC" } else { "ASC" };
13347 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
13348 }
13349 if let Some(lim) = stmt.limit {
13350 out.push(alloc::format!("{child}Limit: {lim}"));
13351 }
13352 if let Some(off) = stmt.offset {
13353 out.push(alloc::format!("{child}Offset: {off}"));
13354 }
13355 if stmt
13357 .items
13358 .iter()
13359 .any(|it| matches!(it, SelectItem::Wildcard))
13360 {
13361 out.push(alloc::format!("{child}Project: *"));
13362 } else {
13363 out.push(alloc::format!(
13364 "{child}Project: {} item(s)",
13365 stmt.items.len()
13366 ));
13367 }
13368 for (kind, peer) in &stmt.unions {
13370 let label = match kind {
13371 UnionKind::All => "UNION ALL",
13372 UnionKind::Distinct => "UNION",
13373 };
13374 out.push(alloc::format!("{child}{label}"));
13375 explain_select(peer, engine, depth + 2, out);
13376 }
13377}
13378
13379fn is_correlation_error(e: &EngineError) -> bool {
13384 matches!(
13385 e,
13386 EngineError::Eval(
13387 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
13388 )
13389 )
13390}
13391
13392struct JoinedPeer<'a> {
13403 eager_rows: Option<Vec<Row>>,
13404 cols: Vec<ColumnSchema>,
13405 alias: String,
13406 kind: JoinKind,
13407 on: Option<&'a Expr>,
13408 lateral: Option<&'a SelectStatement>,
13409 join_table: Option<String>,
13412}
13413
13414fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
13421 match expr {
13422 Expr::Column(c) => c.name.clone(),
13424 Expr::FunctionCall { name, .. } => name.clone(),
13427 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
13429 _ => alloc::format!("column{}", idx + 1),
13431 }
13432}
13433
13434fn substitute_outer_columns_multi(
13441 stmt: &mut SelectStatement,
13442 outer_row: &Row,
13443 outer_schema: &[ColumnSchema],
13444) {
13445 substitute_outer_in_select(stmt, outer_row, outer_schema);
13446}
13447
13448fn substitute_outer_in_select(
13449 stmt: &mut SelectStatement,
13450 outer_row: &Row,
13451 outer_schema: &[ColumnSchema],
13452) {
13453 for item in &mut stmt.items {
13454 if let SelectItem::Expr { expr, .. } = item {
13455 substitute_outer_in_expr(expr, outer_row, outer_schema);
13456 }
13457 }
13458 if let Some(w) = &mut stmt.where_ {
13459 substitute_outer_in_expr(w, outer_row, outer_schema);
13460 }
13461 if let Some(gs) = &mut stmt.group_by {
13462 for g in gs {
13463 substitute_outer_in_expr(g, outer_row, outer_schema);
13464 }
13465 }
13466 if let Some(h) = &mut stmt.having {
13467 substitute_outer_in_expr(h, outer_row, outer_schema);
13468 }
13469 for o in &mut stmt.order_by {
13470 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
13471 }
13472 for (_, peer) in &mut stmt.unions {
13473 substitute_outer_in_select(peer, outer_row, outer_schema);
13474 }
13475}
13476
13477fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
13478 if let Expr::Column(c) = e
13479 && let Some(qual) = &c.qualifier
13480 {
13481 let composite = alloc::format!("{qual}.{}", c.name);
13482 if let Some(idx) = outer_schema
13483 .iter()
13484 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13485 {
13486 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
13487 if let Ok(lit) = value_to_literal_expr(v) {
13488 *e = lit;
13489 return;
13490 }
13491 }
13492 }
13493 match e {
13494 Expr::Binary { lhs, rhs, .. } => {
13495 substitute_outer_in_expr(lhs, outer_row, outer_schema);
13496 substitute_outer_in_expr(rhs, outer_row, outer_schema);
13497 }
13498 Expr::Unary { expr: inner, .. } => {
13499 substitute_outer_in_expr(inner, outer_row, outer_schema);
13500 }
13501 Expr::FunctionCall { args, .. } => {
13502 for a in args {
13503 substitute_outer_in_expr(a, outer_row, outer_schema);
13504 }
13505 }
13506 Expr::Cast { expr: inner, .. } => {
13507 substitute_outer_in_expr(inner, outer_row, outer_schema);
13508 }
13509 Expr::Case {
13510 operand,
13511 branches,
13512 else_branch,
13513 } => {
13514 if let Some(op) = operand {
13515 substitute_outer_in_expr(op, outer_row, outer_schema);
13516 }
13517 for (cond, val) in branches {
13518 substitute_outer_in_expr(cond, outer_row, outer_schema);
13519 substitute_outer_in_expr(val, outer_row, outer_schema);
13520 }
13521 if let Some(e) = else_branch {
13522 substitute_outer_in_expr(e, outer_row, outer_schema);
13523 }
13524 }
13525 _ => {}
13526 }
13527}
13528
13529impl Engine {
13530 fn try_batch_correlated_scalar(
13539 &self,
13540 inner: &SelectStatement,
13541 cancel: CancelToken<'_>,
13542 ) -> Result<Option<memoize::GroupMap>, EngineError> {
13543 use spg_sql::ast::{BinOp, SelectItem as SI};
13544 if !inner.ctes.is_empty()
13545 || !inner.unions.is_empty()
13546 || inner.group_by.is_some()
13547 || inner.having.is_some()
13548 || inner.distinct
13549 || inner.items.len() != 1
13550 || inner.order_by.len() > 1
13551 || inner.offset.is_some()
13552 {
13553 return Ok(None);
13554 }
13555 if let Some(le) = inner.limit
13557 && le.as_literal() != Some(1)
13558 {
13559 return Ok(None);
13560 }
13561 let Some(from) = &inner.from else {
13562 return Ok(None);
13563 };
13564 if from.primary.lateral_subquery.is_some() || from.primary.unnest_expr.is_some() {
13565 return Ok(None);
13566 }
13567 let mut inner_aliases: Vec<String> = Vec::new();
13569 inner_aliases.push(
13570 from.primary
13571 .alias
13572 .clone()
13573 .unwrap_or_else(|| from.primary.name.clone()),
13574 );
13575 for j in &from.joins {
13576 if j.table.lateral_subquery.is_some() || j.table.unnest_expr.is_some() {
13577 return Ok(None);
13578 }
13579 inner_aliases.push(
13580 j.table
13581 .alias
13582 .clone()
13583 .unwrap_or_else(|| j.table.name.clone()),
13584 );
13585 }
13586 let is_inner = |c: &spg_sql::ast::ColumnName| -> bool {
13587 match &c.qualifier {
13588 Some(q) => inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
13589 None => false,
13590 }
13591 };
13592 let is_outer = |c: &spg_sql::ast::ColumnName| -> bool {
13593 match &c.qualifier {
13594 Some(q) => !inner_aliases.iter().any(|a| a.eq_ignore_ascii_case(q)),
13595 None => c.name.starts_with("__grp_") || c.name.starts_with("__agg_"),
13598 }
13599 };
13600 let all_inner = |e: &Expr| -> bool {
13603 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
13604 let mut subs: Vec<&SelectStatement> = Vec::new();
13605 visit_expr_columns_and_subqueries(e, &mut |c| cols.push(c.clone()), &mut |sub| {
13606 subs.push(sub)
13607 });
13608 subs.is_empty() && cols.iter().all(|c| is_inner(c) && !c.name.is_empty())
13609 };
13610 let Some(w) = &inner.where_ else {
13611 return Ok(None);
13612 };
13613 let conjuncts = reorder::split_and_conjunctions(w);
13614 let mut corr: Option<(spg_sql::ast::ColumnName, spg_sql::ast::ColumnName)> = None; let mut rest: Vec<&Expr> = Vec::new();
13616 for c in conjuncts {
13617 if let Expr::Binary {
13618 lhs,
13619 op: BinOp::Eq,
13620 rhs,
13621 } = c
13622 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
13623 {
13624 let pair = if is_inner(a) && is_outer(b) {
13625 Some((a.clone(), b.clone()))
13626 } else if is_inner(b) && is_outer(a) {
13627 Some((b.clone(), a.clone()))
13628 } else {
13629 None
13630 };
13631 if let Some(p) = pair {
13632 if corr.is_some() {
13633 return Ok(None); }
13635 corr = Some(p);
13636 continue;
13637 }
13638 }
13639 if !all_inner(c) {
13640 return Ok(None);
13641 }
13642 rest.push(c);
13643 }
13644 let Some((inner_col, outer_col)) = corr else {
13645 return Ok(None);
13646 };
13647 let SI::Expr { expr: out_expr, .. } = &inner.items[0] else {
13648 return Ok(None);
13649 };
13650 if !all_inner(out_expr) {
13651 return Ok(None);
13652 }
13653 let order = inner.order_by.first();
13654 if let Some(o) = order
13655 && !all_inner(&o.expr)
13656 {
13657 return Ok(None);
13658 }
13659 let mut batch = inner.clone();
13662 batch.limit = None;
13663 batch.offset = None;
13664 batch.order_by = Vec::new();
13665 batch.where_ = rest
13666 .iter()
13667 .map(|e| (*e).clone())
13668 .reduce(|a, b| Expr::Binary {
13669 lhs: alloc::boxed::Box::new(a),
13670 op: BinOp::And,
13671 rhs: alloc::boxed::Box::new(b),
13672 });
13673 let mut items: Vec<SI> = alloc::vec![SI::Expr {
13674 expr: Expr::Column(inner_col),
13675 alias: None,
13676 }];
13677 if let Some(o) = order {
13678 items.push(SI::Expr {
13679 expr: o.expr.clone(),
13680 alias: None,
13681 });
13682 }
13683 items.push(SI::Expr {
13684 expr: out_expr.clone(),
13685 alias: None,
13686 });
13687 batch.items = items;
13688 let r = self.exec_select_cancel(&batch, cancel)?;
13689 let QueryResult::Rows { rows, .. } = r else {
13690 return Ok(None);
13691 };
13692 let has_order = order.is_some();
13693 let (desc, nf) = order
13694 .map(|o| (o.desc, o.nulls_first))
13695 .unwrap_or((false, None));
13696 let mut best: alloc::collections::BTreeMap<String, (Option<Value>, Value)> =
13697 alloc::collections::BTreeMap::new();
13698 for row in rows {
13699 let key_v = row.values.first().cloned().unwrap_or(Value::Null);
13700 if matches!(key_v, Value::Null) {
13701 continue;
13702 }
13703 let key = aggregate::encode_key(core::slice::from_ref(&key_v));
13704 let (ord_v, out_v) = if has_order {
13705 (
13706 Some(row.values.get(1).cloned().unwrap_or(Value::Null)),
13707 row.values.get(2).cloned().unwrap_or(Value::Null),
13708 )
13709 } else {
13710 (None, row.values.get(1).cloned().unwrap_or(Value::Null))
13711 };
13712 match best.get(&key) {
13713 None => {
13714 best.insert(key, (ord_v, out_v));
13715 }
13716 Some((cur_ord, _)) if has_order => {
13717 let cand = ord_v.clone().unwrap_or(Value::Null);
13721 let cur = cur_ord.clone().unwrap_or(Value::Null);
13722 if order_by_value_cmp(desc, nf, &cand, &cur) == core::cmp::Ordering::Less {
13723 best.insert(key, (ord_v, out_v));
13724 }
13725 }
13726 Some(_) => {} }
13728 }
13729 let map = best.into_iter().map(|(k, (_, v))| (k, v)).collect();
13730 Ok(Some((outer_col, map)))
13731 }
13732}
13733
13734fn collect_scalar_subqueries<'a>(e: &'a Expr, out: &mut Vec<&'a SelectStatement>) {
13738 match e {
13739 Expr::ScalarSubquery(s) => out.push(s),
13740 Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13741 Expr::Binary { lhs, rhs, .. } => {
13742 collect_scalar_subqueries(lhs, out);
13743 collect_scalar_subqueries(rhs, out);
13744 }
13745 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13746 collect_scalar_subqueries(expr, out);
13747 }
13748 Expr::Like { expr, pattern, .. } => {
13749 collect_scalar_subqueries(expr, out);
13750 collect_scalar_subqueries(pattern, out);
13751 }
13752 Expr::FunctionCall { args, .. } => {
13753 for a in args {
13754 collect_scalar_subqueries(a, out);
13755 }
13756 }
13757 Expr::AggregateOrdered { call, order_by, .. } => {
13758 collect_scalar_subqueries(call, out);
13759 for o in order_by {
13760 collect_scalar_subqueries(&o.expr, out);
13761 }
13762 }
13763 Expr::Case {
13764 operand,
13765 branches,
13766 else_branch,
13767 } => {
13768 if let Some(op) = operand {
13769 collect_scalar_subqueries(op, out);
13770 }
13771 for (w, t) in branches {
13772 collect_scalar_subqueries(w, out);
13773 collect_scalar_subqueries(t, out);
13774 }
13775 if let Some(eb) = else_branch {
13776 collect_scalar_subqueries(eb, out);
13777 }
13778 }
13779 Expr::ArraySubscript { target, index } => {
13780 collect_scalar_subqueries(target, out);
13781 collect_scalar_subqueries(index, out);
13782 }
13783 Expr::InList { expr, list, .. } => {
13784 collect_scalar_subqueries(expr, out);
13785 for item in list {
13786 collect_scalar_subqueries(item, out);
13787 }
13788 }
13789 _ => {}
13790 }
13791}
13792
13793fn hollow_scalar_subqueries(e: &mut Expr) {
13796 match e {
13797 Expr::ScalarSubquery(s) => {
13798 let hollow = SelectStatement {
13799 items: Vec::new(),
13800 ..SelectStatement::default()
13801 };
13802 **s = hollow;
13803 }
13804 Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13805 Expr::Binary { lhs, rhs, .. } => {
13806 hollow_scalar_subqueries(lhs);
13807 hollow_scalar_subqueries(rhs);
13808 }
13809 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13810 hollow_scalar_subqueries(expr);
13811 }
13812 Expr::Like { expr, pattern, .. } => {
13813 hollow_scalar_subqueries(expr);
13814 hollow_scalar_subqueries(pattern);
13815 }
13816 Expr::FunctionCall { args, .. } => {
13817 for a in args.iter_mut() {
13818 hollow_scalar_subqueries(a);
13819 }
13820 }
13821 Expr::AggregateOrdered { call, order_by, .. } => {
13822 hollow_scalar_subqueries(call);
13823 for o in order_by.iter_mut() {
13824 hollow_scalar_subqueries(&mut o.expr);
13825 }
13826 }
13827 Expr::Case {
13828 operand,
13829 branches,
13830 else_branch,
13831 } => {
13832 if let Some(op) = operand {
13833 hollow_scalar_subqueries(op);
13834 }
13835 for (w, t) in branches.iter_mut() {
13836 hollow_scalar_subqueries(w);
13837 hollow_scalar_subqueries(t);
13838 }
13839 if let Some(eb) = else_branch {
13840 hollow_scalar_subqueries(eb);
13841 }
13842 }
13843 Expr::ArraySubscript { target, index } => {
13844 hollow_scalar_subqueries(target);
13845 hollow_scalar_subqueries(index);
13846 }
13847 Expr::InList { expr, list, .. } => {
13848 hollow_scalar_subqueries(expr);
13849 for item in list.iter_mut() {
13850 hollow_scalar_subqueries(item);
13851 }
13852 }
13853 _ => {}
13854 }
13855}
13856
13857fn splice_planned_subqueries(
13862 e: &mut Expr,
13863 plan: &[Option<alloc::rc::Rc<memoize::GroupMap>>],
13864 idx: &mut usize,
13865 row: &Row,
13866 ctx: &EvalContext<'_>,
13867) -> Result<bool, EngineError> {
13868 match e {
13869 Expr::ScalarSubquery(_) => {
13870 let Some(Some(gm)) = plan.get(*idx) else {
13871 return Ok(false);
13872 };
13873 *idx += 1;
13874 let (outer_col, map) = gm.as_ref();
13875 let key_v = eval::eval_expr(&Expr::Column(outer_col.clone()), row, ctx)
13876 .map_err(EngineError::Eval)?;
13877 let v = if matches!(key_v, Value::Null) {
13878 Value::Null
13879 } else {
13880 map.get(&aggregate::encode_key(core::slice::from_ref(&key_v)))
13881 .cloned()
13882 .unwrap_or(Value::Null)
13883 };
13884 *e = value_to_literal_expr(v)?;
13885 Ok(true)
13886 }
13887 Expr::Exists { .. } | Expr::InSubquery { .. } => Ok(true),
13888 Expr::Binary { lhs, rhs, .. } => Ok(splice_planned_subqueries(lhs, plan, idx, row, ctx)?
13889 && splice_planned_subqueries(rhs, plan, idx, row, ctx)?),
13890 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13891 splice_planned_subqueries(expr, plan, idx, row, ctx)
13892 }
13893 Expr::Like { expr, pattern, .. } => {
13894 Ok(splice_planned_subqueries(expr, plan, idx, row, ctx)?
13895 && splice_planned_subqueries(pattern, plan, idx, row, ctx)?)
13896 }
13897 Expr::FunctionCall { args, .. } => {
13898 for a in args.iter_mut() {
13899 if !splice_planned_subqueries(a, plan, idx, row, ctx)? {
13900 return Ok(false);
13901 }
13902 }
13903 Ok(true)
13904 }
13905 Expr::AggregateOrdered { call, order_by, .. } => {
13906 if !splice_planned_subqueries(call, plan, idx, row, ctx)? {
13907 return Ok(false);
13908 }
13909 for o in order_by.iter_mut() {
13910 if !splice_planned_subqueries(&mut o.expr, plan, idx, row, ctx)? {
13911 return Ok(false);
13912 }
13913 }
13914 Ok(true)
13915 }
13916 Expr::Case {
13917 operand,
13918 branches,
13919 else_branch,
13920 } => {
13921 if let Some(op) = operand {
13922 if !splice_planned_subqueries(op, plan, idx, row, ctx)? {
13923 return Ok(false);
13924 }
13925 }
13926 for (w, t) in branches.iter_mut() {
13927 if !splice_planned_subqueries(w, plan, idx, row, ctx)?
13928 || !splice_planned_subqueries(t, plan, idx, row, ctx)?
13929 {
13930 return Ok(false);
13931 }
13932 }
13933 if let Some(eb) = else_branch {
13934 if !splice_planned_subqueries(eb, plan, idx, row, ctx)? {
13935 return Ok(false);
13936 }
13937 }
13938 Ok(true)
13939 }
13940 Expr::ArraySubscript { target, index } => {
13941 Ok(splice_planned_subqueries(target, plan, idx, row, ctx)?
13942 && splice_planned_subqueries(index, plan, idx, row, ctx)?)
13943 }
13944 Expr::InList { expr, list, .. } => {
13945 if !splice_planned_subqueries(expr, plan, idx, row, ctx)? {
13946 return Ok(false);
13947 }
13948 for item in list.iter_mut() {
13949 if !splice_planned_subqueries(item, plan, idx, row, ctx)? {
13950 return Ok(false);
13951 }
13952 }
13953 Ok(true)
13954 }
13955 _ => Ok(true),
13956 }
13957}
13958
13959const INLIST_SET_THRESHOLD: usize = 64;
13963
13964fn expr_may_use_in_set(e: &Expr) -> bool {
13968 match e {
13969 Expr::InList { list, .. } => list.len() >= INLIST_SET_THRESHOLD,
13970 Expr::Binary {
13971 lhs,
13972 op: BinOp::And,
13973 rhs,
13974 } => expr_may_use_in_set(lhs) || expr_may_use_in_set(rhs),
13975 _ => false,
13976 }
13977}
13978
13979fn build_in_list_set(list: &[Expr]) -> Option<memoize::InListSetEntry> {
13982 let mut has_null = false;
13983 let mut ints: alloc::collections::BTreeSet<i64> = alloc::collections::BTreeSet::new();
13984 let mut texts: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
13985 for item in list {
13986 let Expr::Literal(lit) = item else {
13987 return None;
13988 };
13989 match lit {
13990 Literal::Null => has_null = true,
13991 Literal::Integer(i) => {
13992 ints.insert(*i);
13993 }
13994 Literal::String(s) => {
13995 texts.insert(s.clone());
13996 }
13997 _ => return None,
13998 }
13999 if !ints.is_empty() && !texts.is_empty() {
14000 return None;
14001 }
14002 }
14003 let set = if !ints.is_empty() {
14004 memoize::InListSet::Int(ints)
14005 } else if !texts.is_empty() {
14006 memoize::InListSet::Text(texts)
14007 } else {
14008 return None;
14009 };
14010 Some(memoize::InListSetEntry { set, has_null })
14011}
14012
14013fn eval_with_in_sets(
14019 e: &Expr,
14020 row: &Row,
14021 ctx: &EvalContext<'_>,
14022 m: &mut memoize::MemoizeCache,
14023) -> Result<Value, EngineError> {
14024 match e {
14025 Expr::Binary {
14026 lhs,
14027 op: BinOp::And,
14028 rhs,
14029 } => {
14030 let l = eval_with_in_sets(lhs, row, ctx, m)?;
14033 let r = eval_with_in_sets(rhs, row, ctx, m)?;
14034 eval::and_3vl(l, r).map_err(EngineError::Eval)
14035 }
14036 Expr::InList {
14037 expr: lhs,
14038 list,
14039 negated,
14040 } if list.len() >= INLIST_SET_THRESHOLD => {
14041 let key = core::ptr::from_ref::<Expr>(e) as usize;
14042 let Some(entry) = m
14043 .in_sets
14044 .entry(key)
14045 .or_insert_with(|| build_in_list_set(list))
14046 else {
14047 return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval);
14048 };
14049 let needle = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
14050 let contained = match (&needle, &entry.set) {
14051 (Value::Null, _) => return Ok(Value::Null),
14054 (Value::SmallInt(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
14055 (Value::Int(n), memoize::InListSet::Int(s)) => s.contains(&i64::from(*n)),
14056 (Value::BigInt(n), memoize::InListSet::Int(s)) => s.contains(n),
14057 (Value::Text(t), memoize::InListSet::Text(s)) => s.contains(t.as_str()),
14058 _ => return eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
14061 };
14062 let inner = if contained {
14063 Value::Bool(true)
14064 } else if entry.has_null {
14065 Value::Null
14066 } else {
14067 Value::Bool(false)
14068 };
14069 Ok(match (negated, inner) {
14070 (true, Value::Bool(b)) => Value::Bool(!b),
14071 (_, v) => v,
14072 })
14073 }
14074 _ => eval::eval_expr(e, row, ctx).map_err(EngineError::Eval),
14075 }
14076}
14077
14078fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
14079 let outer_alias = ctx.table_alias.unwrap_or("");
14086 substitute_in_select(stmt, row, ctx, outer_alias);
14087}
14088
14089fn substitute_in_select(
14090 stmt: &mut SelectStatement,
14091 row: &Row,
14092 ctx: &EvalContext<'_>,
14093 outer_alias: &str,
14094) {
14095 for item in &mut stmt.items {
14096 if let SelectItem::Expr { expr, .. } = item {
14097 substitute_in_expr(expr, row, ctx, outer_alias);
14098 }
14099 }
14100 if let Some(w) = &mut stmt.where_ {
14101 substitute_in_expr(w, row, ctx, outer_alias);
14102 }
14103 if let Some(gs) = &mut stmt.group_by {
14104 for g in gs {
14105 substitute_in_expr(g, row, ctx, outer_alias);
14106 }
14107 }
14108 if let Some(h) = &mut stmt.having {
14109 substitute_in_expr(h, row, ctx, outer_alias);
14110 }
14111 for o in &mut stmt.order_by {
14112 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
14113 }
14114 for (_, peer) in &mut stmt.unions {
14115 substitute_in_select(peer, row, ctx, outer_alias);
14116 }
14117}
14118
14119fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
14120 if let Expr::Column(c) = e
14126 && c.qualifier.is_none()
14127 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
14128 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
14129 {
14130 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
14131 if let Ok(lit) = value_to_literal_expr(v) {
14132 *e = lit;
14133 return;
14134 }
14135 }
14136 if let Expr::Column(c) = e
14137 && let Some(qual) = &c.qualifier
14138 {
14139 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
14143 ctx.columns
14144 .iter()
14145 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
14146 } else {
14147 None
14148 }
14149 .or_else(|| {
14150 let composite = alloc::format!("{qual}.{name}", name = c.name);
14151 ctx.columns
14152 .iter()
14153 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
14154 });
14155 if let Some(idx) = idx {
14156 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
14157 if let Ok(lit) = value_to_literal_expr(v) {
14158 *e = lit;
14159 return;
14160 }
14161 }
14162 }
14163 match e {
14164 Expr::AggregateOrdered { call, order_by, .. } => {
14165 substitute_in_expr(call, row, ctx, outer_alias);
14166 for o in order_by.iter_mut() {
14167 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
14168 }
14169 }
14170 Expr::Binary { lhs, rhs, .. } => {
14171 substitute_in_expr(lhs, row, ctx, outer_alias);
14172 substitute_in_expr(rhs, row, ctx, outer_alias);
14173 }
14174 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14175 substitute_in_expr(expr, row, ctx, outer_alias);
14176 }
14177 Expr::Like { expr, pattern, .. } => {
14178 substitute_in_expr(expr, row, ctx, outer_alias);
14179 substitute_in_expr(pattern, row, ctx, outer_alias);
14180 }
14181 Expr::FunctionCall { args, .. } => {
14182 for a in args {
14183 substitute_in_expr(a, row, ctx, outer_alias);
14184 }
14185 }
14186 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
14187 Expr::WindowFunction {
14188 args,
14189 partition_by,
14190 order_by,
14191 ..
14192 } => {
14193 for a in args {
14194 substitute_in_expr(a, row, ctx, outer_alias);
14195 }
14196 for p in partition_by {
14197 substitute_in_expr(p, row, ctx, outer_alias);
14198 }
14199 for (o, _, _) in order_by {
14200 substitute_in_expr(o, row, ctx, outer_alias);
14201 }
14202 }
14203 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
14204 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
14205 substitute_in_select(subquery, row, ctx, outer_alias);
14206 }
14207 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14208 Expr::Array(items) => {
14209 for elem in items {
14210 substitute_in_expr(elem, row, ctx, outer_alias);
14211 }
14212 }
14213 Expr::ArraySubscript { target, index } => {
14214 substitute_in_expr(target, row, ctx, outer_alias);
14215 substitute_in_expr(index, row, ctx, outer_alias);
14216 }
14217 Expr::AnyAll { expr, array, .. } => {
14218 substitute_in_expr(expr, row, ctx, outer_alias);
14219 substitute_in_expr(array, row, ctx, outer_alias);
14220 }
14221 Expr::InList { expr, list, .. } => {
14222 substitute_in_expr(expr, row, ctx, outer_alias);
14223 for item in list {
14224 substitute_in_expr(item, row, ctx, outer_alias);
14225 }
14226 }
14227 Expr::Case {
14228 operand,
14229 branches,
14230 else_branch,
14231 } => {
14232 if let Some(o) = operand {
14233 substitute_in_expr(o, row, ctx, outer_alias);
14234 }
14235 for (w, t) in branches {
14236 substitute_in_expr(w, row, ctx, outer_alias);
14237 substitute_in_expr(t, row, ctx, outer_alias);
14238 }
14239 if let Some(e) = else_branch {
14240 substitute_in_expr(e, row, ctx, outer_alias);
14241 }
14242 }
14243 }
14244}
14245
14246fn encode_row_key(row: &Row) -> Vec<u8> {
14250 let mut out = Vec::new();
14251 for v in &row.values {
14252 let s = alloc::format!("{v:?}|");
14253 out.extend_from_slice(s.as_bytes());
14254 }
14255 out
14256}
14257
14258fn select_has_window(stmt: &SelectStatement) -> bool {
14259 for item in &stmt.items {
14260 if let SelectItem::Expr { expr, .. } = item
14261 && expr_has_window(expr)
14262 {
14263 return true;
14264 }
14265 }
14266 false
14267}
14268
14269fn expr_has_window(e: &Expr) -> bool {
14270 match e {
14271 Expr::WindowFunction { .. } => true,
14272 Expr::AggregateOrdered { call, order_by, .. } => {
14273 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
14274 }
14275 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
14276 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14277 expr_has_window(expr)
14278 }
14279 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
14280 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
14281 Expr::Extract { source, .. } => expr_has_window(source),
14282 Expr::ScalarSubquery(_)
14283 | Expr::Exists { .. }
14284 | Expr::InSubquery { .. }
14285 | Expr::Literal(_)
14286 | Expr::Placeholder(_)
14287 | Expr::Column(_) => false,
14288 Expr::Array(items) => items.iter().any(expr_has_window),
14289 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
14290 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
14291 Expr::InList { expr, list, .. } => {
14292 expr_has_window(expr) || list.iter().any(expr_has_window)
14293 }
14294 Expr::Case {
14295 operand,
14296 branches,
14297 else_branch,
14298 } => {
14299 operand.as_deref().is_some_and(expr_has_window)
14300 || branches
14301 .iter()
14302 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
14303 || else_branch.as_deref().is_some_and(expr_has_window)
14304 }
14305 }
14306}
14307
14308fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
14309 if let Expr::WindowFunction { .. } = e {
14310 if !out.iter().any(|x| x == e) {
14315 out.push(e.clone());
14316 }
14317 return;
14318 }
14319 match e {
14320 Expr::WindowFunction { .. } => unreachable!(),
14322 Expr::Binary { lhs, rhs, .. } => {
14323 collect_window_nodes(lhs, out);
14324 collect_window_nodes(rhs, out);
14325 }
14326 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14327 collect_window_nodes(expr, out);
14328 }
14329 Expr::FunctionCall { args, .. } => {
14330 for a in args {
14331 collect_window_nodes(a, out);
14332 }
14333 }
14334 Expr::Like { expr, pattern, .. } => {
14335 collect_window_nodes(expr, out);
14336 collect_window_nodes(pattern, out);
14337 }
14338 Expr::Extract { source, .. } => collect_window_nodes(source, out),
14339 _ => {}
14340 }
14341}
14342
14343fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
14344 if let Expr::WindowFunction { .. } = e
14345 && let Some(idx) = window_nodes.iter().position(|w| w == e)
14346 {
14347 *e = Expr::Column(spg_sql::ast::ColumnName {
14348 qualifier: None,
14349 name: alloc::format!("__win_{idx}"),
14350 });
14351 return;
14352 }
14353 match e {
14354 Expr::Binary { lhs, rhs, .. } => {
14355 rewrite_window_to_columns(lhs, window_nodes);
14356 rewrite_window_to_columns(rhs, window_nodes);
14357 }
14358 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14359 rewrite_window_to_columns(expr, window_nodes);
14360 }
14361 Expr::FunctionCall { args, .. } => {
14362 for a in args {
14363 rewrite_window_to_columns(a, window_nodes);
14364 }
14365 }
14366 Expr::Like { expr, pattern, .. } => {
14367 rewrite_window_to_columns(expr, window_nodes);
14368 rewrite_window_to_columns(pattern, window_nodes);
14369 }
14370 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
14371 _ => {}
14372 }
14373}
14374
14375fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
14379 for (x, y) in a.iter().zip(b.iter()) {
14380 let c = value_cmp(x, y);
14381 if c != core::cmp::Ordering::Equal {
14382 return c;
14383 }
14384 }
14385 a.len().cmp(&b.len())
14386}
14387
14388fn order_key_cmp(
14389 a: &[(Value, bool, Option<bool>)],
14390 b: &[(Value, bool, Option<bool>)],
14391) -> core::cmp::Ordering {
14392 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
14395 let c = order_by_value_cmp(*desc, *nf, va, vb);
14396 if c != core::cmp::Ordering::Equal {
14397 return c;
14398 }
14399 }
14400 a.len().cmp(&b.len())
14401}
14402
14403const fn value_is_integer(v: &Value) -> bool {
14409 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
14410}
14411
14412const fn value_to_i64(v: &Value) -> i64 {
14416 match v {
14417 Value::SmallInt(n) => *n as i64,
14418 Value::Int(n) => *n as i64,
14419 Value::BigInt(n) => *n,
14420 _ => panic!("value_to_i64 called on non-integer Value"),
14421 }
14422}
14423
14424fn generate_series_integers(
14430 start: i64,
14431 stop: i64,
14432 step: i64,
14433 cancel: &CancelToken<'_>,
14434) -> Result<alloc::vec::Vec<Row>, EngineError> {
14435 if step == 0 {
14436 return Err(EngineError::Unsupported(
14437 "generate_series(): step argument cannot be zero".into(),
14438 ));
14439 }
14440 let mut out = alloc::vec::Vec::new();
14441 let mut cur = start;
14442 const MAX_ROWS: usize = 10_000_000;
14446 loop {
14447 cancel.check()?;
14448 if step > 0 && cur > stop {
14449 break;
14450 }
14451 if step < 0 && cur < stop {
14452 break;
14453 }
14454 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
14455 if out.len() > MAX_ROWS {
14456 return Err(EngineError::Unsupported(alloc::format!(
14457 "generate_series(): exceeded {MAX_ROWS} rows; \
14458 narrow start/stop or use a larger step"
14459 )));
14460 }
14461 cur = match cur.checked_add(step) {
14462 Some(n) => n,
14463 None => break,
14464 };
14465 }
14466 Ok(out)
14467}
14468
14469fn generate_series_timestamps(
14474 start: i64,
14475 stop: i64,
14476 step: Value,
14477 cancel: &CancelToken<'_>,
14478) -> Result<alloc::vec::Vec<Row>, EngineError> {
14479 let (months, micros) = match &step {
14480 Value::Interval { months, micros } => (*months, *micros),
14481 _ => unreachable!("caller guards step.is_interval"),
14482 };
14483 if months == 0 && micros == 0 {
14484 return Err(EngineError::Unsupported(
14485 "generate_series(): INTERVAL step cannot be zero".into(),
14486 ));
14487 }
14488 let ascending = months > 0 || micros > 0;
14489 let mut out = alloc::vec::Vec::new();
14490 let mut cur = Value::Timestamp(start);
14491 const MAX_ROWS: usize = 10_000_000;
14492 loop {
14493 cancel.check()?;
14494 let cur_t = match cur {
14495 Value::Timestamp(t) => t,
14496 _ => unreachable!("loop invariant: cur is Timestamp"),
14497 };
14498 if ascending && cur_t > stop {
14499 break;
14500 }
14501 if !ascending && cur_t < stop {
14502 break;
14503 }
14504 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
14505 if out.len() > MAX_ROWS {
14506 return Err(EngineError::Unsupported(alloc::format!(
14507 "generate_series(): exceeded {MAX_ROWS} rows; \
14508 narrow start/stop or use a larger step"
14509 )));
14510 }
14511 let next = eval::apply_binary_interval(
14512 spg_sql::ast::BinOp::Add,
14513 &cur,
14514 &Value::Interval { months, micros },
14515 )
14516 .map_err(EngineError::Eval)?;
14517 cur = match next {
14518 Some(v) => v,
14519 None => break,
14520 };
14521 }
14522 Ok(out)
14523}
14524
14525#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
14531 desc: bool,
14532 nulls_first: Option<bool>,
14533 a: &Value,
14534 b: &Value,
14535) -> core::cmp::Ordering {
14536 use core::cmp::Ordering;
14537 let nf = nulls_first.unwrap_or(desc);
14538 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
14539 (true, true) => Ordering::Equal,
14540 (true, false) => {
14541 if nf {
14542 Ordering::Less
14543 } else {
14544 Ordering::Greater
14545 }
14546 }
14547 (false, true) => {
14548 if nf {
14549 Ordering::Greater
14550 } else {
14551 Ordering::Less
14552 }
14553 }
14554 (false, false) => {
14555 let c = value_cmp(a, b);
14556 if desc { c.reverse() } else { c }
14557 }
14558 }
14559}
14560
14561fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
14562 use core::cmp::Ordering;
14563 match (a, b) {
14564 (Value::Null, Value::Null) => Ordering::Equal,
14565 (Value::Null, _) => Ordering::Less,
14566 (_, Value::Null) => Ordering::Greater,
14567 (Value::Int(x), Value::Int(y)) => x.cmp(y),
14568 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
14569 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
14570 (Value::Text(x), Value::Text(y)) => x.cmp(y),
14571 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
14572 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
14573 (Value::Date(x), Value::Date(y)) => x.cmp(y),
14574 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
14575 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
14578 }
14579}
14580
14581#[allow(
14587 clippy::too_many_arguments,
14588 clippy::cast_possible_truncation,
14589 clippy::cast_possible_wrap,
14590 clippy::cast_precision_loss,
14591 clippy::cast_sign_loss,
14592 clippy::doc_markdown,
14593 clippy::too_many_lines,
14594 clippy::type_complexity,
14595 clippy::match_same_arms
14596)]
14597fn compute_window_partition(
14598 name: &str,
14599 args: &[Expr],
14600 ordered: bool,
14601 frame: Option<&WindowFrame>,
14602 null_treatment: spg_sql::ast::NullTreatment,
14603 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14604 filtered_rows: &[&Row],
14605 ctx: &EvalContext<'_>,
14606 out_vals: &mut [Value],
14607) -> Result<(), EngineError> {
14608 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
14609 let lower = name.to_ascii_lowercase();
14610 match lower.as_str() {
14611 "row_number" => {
14612 for (rank, (_, _, idx)) in slice.iter().enumerate() {
14613 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
14614 }
14615 Ok(())
14616 }
14617 "rank" => {
14618 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14619 let mut current_rank: i64 = 1;
14620 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14621 if let Some(p) = prev_key
14622 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14623 {
14624 current_rank = (i + 1) as i64;
14625 }
14626 if prev_key.is_none() {
14627 current_rank = 1;
14628 }
14629 out_vals[*idx] = Value::BigInt(current_rank);
14630 prev_key = Some(okey.as_slice());
14631 }
14632 Ok(())
14633 }
14634 "dense_rank" => {
14635 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14636 let mut current_rank: i64 = 0;
14637 for (_, okey, idx) in slice {
14638 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
14639 current_rank += 1;
14640 }
14641 out_vals[*idx] = Value::BigInt(current_rank);
14642 prev_key = Some(okey.as_slice());
14643 }
14644 Ok(())
14645 }
14646 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
14647 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
14650 slice.iter().map(|_| Value::Null).collect()
14651 } else {
14652 slice
14653 .iter()
14654 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14655 .collect::<Result<_, _>>()
14656 .map_err(EngineError::Eval)?
14657 };
14658 let eff = effective_frame(frame, ordered)?;
14662 #[allow(clippy::needless_range_loop)]
14663 for i in 0..slice.len() {
14664 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14665 let mut sum: f64 = 0.0;
14666 let mut count: i64 = 0;
14667 let mut min_v: Option<f64> = None;
14668 let mut max_v: Option<f64> = None;
14669 let mut row_count: i64 = 0;
14670 if lo <= hi {
14671 for j in lo..=hi {
14672 let v = &arg_values[j];
14673 match lower.as_str() {
14674 "count_star" => row_count += 1,
14675 "count" => {
14676 if !v.is_null() {
14677 count += 1;
14678 }
14679 }
14680 _ => {
14681 if let Some(x) = value_to_f64(v) {
14682 sum += x;
14683 count += 1;
14684 min_v = Some(min_v.map_or(x, |m| m.min(x)));
14685 max_v = Some(max_v.map_or(x, |m| m.max(x)));
14686 }
14687 }
14688 }
14689 }
14690 }
14691 let value = match lower.as_str() {
14692 "count_star" => Value::BigInt(row_count),
14693 "count" => Value::BigInt(count),
14694 "sum" => Value::Float(sum),
14695 "avg" => {
14696 if count == 0 {
14697 Value::Null
14698 } else {
14699 Value::Float(sum / count as f64)
14700 }
14701 }
14702 "min" => min_v.map_or(Value::Null, Value::Float),
14703 "max" => max_v.map_or(Value::Null, Value::Float),
14704 _ => unreachable!(),
14705 };
14706 let (_, _, idx) = &slice[i];
14707 out_vals[*idx] = value;
14708 }
14709 Ok(())
14710 }
14711 "lag" | "lead" => {
14712 if args.is_empty() {
14715 return Err(EngineError::Unsupported(alloc::format!(
14716 "{lower}() requires at least one argument"
14717 )));
14718 }
14719 let offset: i64 = if args.len() >= 2 {
14720 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
14721 .map_err(EngineError::Eval)?;
14722 match v {
14723 Value::SmallInt(n) => i64::from(n),
14724 Value::Int(n) => i64::from(n),
14725 Value::BigInt(n) => n,
14726 _ => {
14727 return Err(EngineError::Unsupported(alloc::format!(
14728 "{lower}() offset must be integer"
14729 )));
14730 }
14731 }
14732 } else {
14733 1
14734 };
14735 let default: Value = if args.len() >= 3 {
14736 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
14737 .map_err(EngineError::Eval)?
14738 } else {
14739 Value::Null
14740 };
14741 let values: Vec<Value> = slice
14742 .iter()
14743 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14744 .collect::<Result<_, _>>()
14745 .map_err(EngineError::Eval)?;
14746 let n = slice.len();
14747 for (i, (_, _, idx)) in slice.iter().enumerate() {
14748 let signed_offset = if lower == "lag" { -offset } else { offset };
14749 let v = if ignore_nulls {
14750 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
14754 let needed: i64 = signed_offset.abs();
14755 if needed == 0 {
14756 values[i].clone()
14757 } else {
14758 let mut j: i64 = i as i64;
14759 let mut hits: i64 = 0;
14760 let mut found: Option<Value> = None;
14761 loop {
14762 j += step;
14763 if j < 0 || j >= n as i64 {
14764 break;
14765 }
14766 #[allow(clippy::cast_sign_loss)]
14767 let v = &values[j as usize];
14768 if !v.is_null() {
14769 hits += 1;
14770 if hits == needed {
14771 found = Some(v.clone());
14772 break;
14773 }
14774 }
14775 }
14776 found.unwrap_or_else(|| default.clone())
14777 }
14778 } else {
14779 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
14780 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
14781 default.clone()
14782 } else {
14783 #[allow(clippy::cast_sign_loss)]
14784 {
14785 values[target_signed as usize].clone()
14786 }
14787 }
14788 };
14789 out_vals[*idx] = v;
14790 }
14791 Ok(())
14792 }
14793 "first_value" | "last_value" | "nth_value" => {
14794 if args.is_empty() {
14795 return Err(EngineError::Unsupported(alloc::format!(
14796 "{lower}() requires at least one argument"
14797 )));
14798 }
14799 let values: Vec<Value> = slice
14800 .iter()
14801 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
14802 .collect::<Result<_, _>>()
14803 .map_err(EngineError::Eval)?;
14804 let nth: usize = if lower == "nth_value" {
14805 if args.len() < 2 {
14806 return Err(EngineError::Unsupported(
14807 "nth_value() requires (expr, n)".into(),
14808 ));
14809 }
14810 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
14811 .map_err(EngineError::Eval)?;
14812 let raw = match v {
14813 Value::SmallInt(n) => i64::from(n),
14814 Value::Int(n) => i64::from(n),
14815 Value::BigInt(n) => n,
14816 _ => {
14817 return Err(EngineError::Unsupported(
14818 "nth_value() n must be integer".into(),
14819 ));
14820 }
14821 };
14822 if raw < 1 {
14823 return Err(EngineError::Unsupported(
14824 "nth_value() n must be >= 1".into(),
14825 ));
14826 }
14827 #[allow(clippy::cast_sign_loss)]
14828 {
14829 raw as usize
14830 }
14831 } else {
14832 0
14833 };
14834 let eff = effective_frame(frame, ordered)?;
14835 for i in 0..slice.len() {
14836 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14837 let (_, _, idx) = &slice[i];
14838 let v = if lo > hi {
14839 Value::Null
14840 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
14841 if lower == "first_value" {
14844 (lo..=hi)
14845 .find_map(|j| {
14846 let v = &values[j];
14847 (!v.is_null()).then(|| v.clone())
14848 })
14849 .unwrap_or(Value::Null)
14850 } else {
14851 (lo..=hi)
14852 .rev()
14853 .find_map(|j| {
14854 let v = &values[j];
14855 (!v.is_null()).then(|| v.clone())
14856 })
14857 .unwrap_or(Value::Null)
14858 }
14859 } else {
14860 match lower.as_str() {
14861 "first_value" => values[lo].clone(),
14862 "last_value" => values[hi].clone(),
14863 "nth_value" => {
14864 let pos = lo + nth - 1;
14865 if pos > hi {
14866 Value::Null
14867 } else {
14868 values[pos].clone()
14869 }
14870 }
14871 _ => unreachable!(),
14872 }
14873 };
14874 out_vals[*idx] = v;
14875 }
14876 Ok(())
14877 }
14878 "ntile" => {
14879 if args.is_empty() {
14880 return Err(EngineError::Unsupported(
14881 "ntile(n) requires an integer argument".into(),
14882 ));
14883 }
14884 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
14885 .map_err(EngineError::Eval)?;
14886 let bucket_count: i64 = match v {
14887 Value::SmallInt(n) => i64::from(n),
14888 Value::Int(n) => i64::from(n),
14889 Value::BigInt(n) => n,
14890 _ => {
14891 return Err(EngineError::Unsupported(
14892 "ntile() argument must be integer".into(),
14893 ));
14894 }
14895 };
14896 if bucket_count < 1 {
14897 return Err(EngineError::Unsupported(
14898 "ntile() argument must be >= 1".into(),
14899 ));
14900 }
14901 #[allow(clippy::cast_sign_loss)]
14902 let buckets = bucket_count as usize;
14903 let n = slice.len();
14904 let base = n / buckets;
14907 let extras = n % buckets;
14908 let mut bucket: usize = 1;
14909 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
14910 let mut buckets_with_extra_remaining = extras;
14911 for (_, _, idx) in slice {
14912 if remaining_in_bucket == 0 {
14913 bucket += 1;
14914 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
14915 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
14916 base + 1
14917 } else {
14918 base
14919 };
14920 if remaining_in_bucket == 0 {
14923 remaining_in_bucket = 1;
14924 }
14925 }
14926 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
14927 remaining_in_bucket -= 1;
14928 }
14929 Ok(())
14930 }
14931 "percent_rank" => {
14932 let n = slice.len();
14935 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14936 let mut current_rank: i64 = 1;
14937 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14938 if let Some(p) = prev_key
14939 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14940 {
14941 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
14942 }
14943 if prev_key.is_none() {
14944 current_rank = 1;
14945 }
14946 #[allow(clippy::cast_precision_loss)]
14947 let pr = if n <= 1 {
14948 0.0
14949 } else {
14950 (current_rank - 1) as f64 / (n - 1) as f64
14951 };
14952 out_vals[*idx] = Value::Float(pr);
14953 prev_key = Some(okey.as_slice());
14954 }
14955 Ok(())
14956 }
14957 "cume_dist" => {
14958 let n = slice.len();
14960 for i in 0..slice.len() {
14962 let peer_end = peer_group_end(slice, i);
14963 #[allow(clippy::cast_precision_loss)]
14964 let cd = (peer_end + 1) as f64 / n as f64;
14965 let (_, _, idx) = &slice[i];
14966 out_vals[*idx] = Value::Float(cd);
14967 }
14968 Ok(())
14969 }
14970 other => Err(EngineError::Unsupported(alloc::format!(
14971 "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)"
14972 ))),
14973 }
14974}
14975
14976fn effective_frame(
14983 frame: Option<&WindowFrame>,
14984 ordered: bool,
14985) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
14986 match frame {
14987 None => {
14988 if ordered {
14989 Ok((
14990 FrameKind::Range,
14991 FrameBound::UnboundedPreceding,
14992 FrameBound::CurrentRow,
14993 ))
14994 } else {
14995 Ok((
14996 FrameKind::Rows,
14997 FrameBound::UnboundedPreceding,
14998 FrameBound::UnboundedFollowing,
14999 ))
15000 }
15001 }
15002 Some(fr) => {
15003 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
15004 if matches!(fr.start, FrameBound::UnboundedFollowing)
15006 || matches!(end, FrameBound::UnboundedPreceding)
15007 {
15008 return Err(EngineError::Unsupported(alloc::format!(
15009 "invalid frame: start={:?} end={:?}",
15010 fr.start,
15011 end
15012 )));
15013 }
15014 if fr.kind == FrameKind::Range
15019 && (matches!(
15020 fr.start,
15021 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
15022 ) || matches!(
15023 end,
15024 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
15025 ))
15026 {
15027 return Err(EngineError::Unsupported(
15028 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
15029 ));
15030 }
15031 Ok((fr.kind, fr.start.clone(), end))
15032 }
15033 }
15034}
15035
15036#[allow(clippy::type_complexity)]
15040fn frame_bounds_for_row(
15041 eff: &(FrameKind, FrameBound, FrameBound),
15042 i: usize,
15043 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
15044) -> (usize, usize) {
15045 let (kind, start, end) = eff;
15046 let n = slice.len();
15047 let last = n.saturating_sub(1);
15048 let (mut lo, mut hi) = match kind {
15049 FrameKind::Rows => {
15050 let lo = match start {
15051 FrameBound::UnboundedPreceding => 0,
15052 FrameBound::OffsetPreceding(k) => {
15053 let k = usize::try_from(*k).unwrap_or(usize::MAX);
15054 i.saturating_sub(k)
15055 }
15056 FrameBound::CurrentRow => i,
15057 FrameBound::OffsetFollowing(k) => {
15058 let k = usize::try_from(*k).unwrap_or(usize::MAX);
15059 i.saturating_add(k).min(last)
15060 }
15061 FrameBound::UnboundedFollowing => last,
15062 };
15063 let hi = match end {
15064 FrameBound::UnboundedPreceding => 0,
15065 FrameBound::OffsetPreceding(k) => {
15066 let k = usize::try_from(*k).unwrap_or(usize::MAX);
15067 i.saturating_sub(k)
15068 }
15069 FrameBound::CurrentRow => i,
15070 FrameBound::OffsetFollowing(k) => {
15071 let k = usize::try_from(*k).unwrap_or(usize::MAX);
15072 i.saturating_add(k).min(last)
15073 }
15074 FrameBound::UnboundedFollowing => last,
15075 };
15076 (lo, hi)
15077 }
15078 FrameKind::Range => {
15079 let lo = match start {
15085 FrameBound::UnboundedPreceding => 0,
15086 FrameBound::CurrentRow => peer_group_start(slice, i),
15087 FrameBound::UnboundedFollowing => last,
15088 _ => unreachable!("offset bounds rejected for RANGE"),
15089 };
15090 let hi = match end {
15091 FrameBound::UnboundedPreceding => 0,
15092 FrameBound::CurrentRow => peer_group_end(slice, i),
15093 FrameBound::UnboundedFollowing => last,
15094 _ => unreachable!("offset bounds rejected for RANGE"),
15095 };
15096 (lo, hi)
15097 }
15098 };
15099 if hi >= n {
15100 hi = last;
15101 }
15102 if lo >= n {
15103 lo = last;
15104 }
15105 (lo, hi)
15106}
15107
15108#[allow(clippy::type_complexity)]
15112fn peer_group_start(
15113 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
15114 i: usize,
15115) -> usize {
15116 let key = &slice[i].1;
15117 let mut j = i;
15118 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
15119 j -= 1;
15120 }
15121 j
15122}
15123
15124#[allow(clippy::type_complexity)]
15127fn peer_group_end(
15128 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
15129 i: usize,
15130) -> usize {
15131 let key = &slice[i].1;
15132 let mut j = i;
15133 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
15134 j += 1;
15135 }
15136 j
15137}
15138
15139fn value_to_f64(v: &Value) -> Option<f64> {
15140 match v {
15141 Value::SmallInt(n) => Some(f64::from(*n)),
15142 Value::Int(n) => Some(f64::from(*n)),
15143 #[allow(clippy::cast_precision_loss)]
15144 Value::BigInt(n) => Some(*n as f64),
15145 Value::Float(x) => Some(*x),
15146 _ => None,
15147 }
15148}
15149
15150fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
15154 let mut any = false;
15155 for item in &stmt.items {
15156 if let SelectItem::Expr { expr, .. } = item {
15157 any = any || expr_has_subquery(expr);
15158 }
15159 }
15160 if let Some(w) = &stmt.where_ {
15161 any = any || expr_has_subquery(w);
15162 }
15163 if let Some(h) = &stmt.having {
15164 any = any || expr_has_subquery(h);
15165 }
15166 for o in &stmt.order_by {
15167 any = any || expr_has_subquery(&o.expr);
15168 }
15169 for (_, peer) in &stmt.unions {
15170 any = any || expr_tree_has_subquery(peer);
15171 }
15172 any
15173}
15174
15175pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
15176 match e {
15177 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
15178 Expr::AggregateOrdered { call, order_by, .. } => {
15179 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
15180 }
15181 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
15182 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15183 expr_has_subquery(expr)
15184 }
15185 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
15186 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
15187 Expr::Extract { source, .. } => expr_has_subquery(source),
15188 Expr::WindowFunction {
15189 args,
15190 partition_by,
15191 order_by,
15192 ..
15193 } => {
15194 args.iter().any(expr_has_subquery)
15195 || partition_by.iter().any(expr_has_subquery)
15196 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
15197 }
15198 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
15199 Expr::Array(items) => items.iter().any(expr_has_subquery),
15200 Expr::ArraySubscript { target, index } => {
15201 expr_has_subquery(target) || expr_has_subquery(index)
15202 }
15203 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
15204 Expr::InList { expr, list, .. } => {
15205 expr_has_subquery(expr) || list.iter().any(expr_has_subquery)
15206 }
15207 Expr::Case {
15208 operand,
15209 branches,
15210 else_branch,
15211 } => {
15212 operand.as_deref().is_some_and(expr_has_subquery)
15213 || branches
15214 .iter()
15215 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
15216 || else_branch.as_deref().is_some_and(expr_has_subquery)
15217 }
15218 }
15219}
15220
15221fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
15228 let lit = match v {
15229 Value::Null => Literal::Null,
15230 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15231 Value::Int(n) => Literal::Integer(i64::from(n)),
15232 Value::BigInt(n) => Literal::Integer(n),
15233 Value::Float(x) => Literal::Float(x),
15234 Value::Text(s) | Value::Json(s) => Literal::String(s),
15235 Value::Bool(b) => Literal::Bool(b),
15236 other => {
15237 return Err(EngineError::Unsupported(alloc::format!(
15238 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
15239 other.data_type()
15240 )));
15241 }
15242 };
15243 Ok(Expr::Literal(lit))
15244}
15245
15246fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
15252 let lit = match v {
15253 Value::Null => Literal::Null,
15254 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15255 Value::Int(n) => Literal::Integer(i64::from(n)),
15256 Value::BigInt(n) => Literal::Integer(n),
15257 Value::Float(x) => Literal::Float(x),
15258 Value::Text(s) | Value::Json(s) => Literal::String(s),
15259 Value::Bool(b) => Literal::Bool(b),
15260 Value::Vector(xs) => Literal::Vector(xs),
15261 Value::Date(days) => {
15265 let micros = (i64::from(days)) * 86_400_000_000;
15266 Literal::String(format_timestamp_micros_as_date(micros))
15267 }
15268 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
15269 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
15270 other => {
15271 return Err(EngineError::Unsupported(alloc::format!(
15272 "INSERT … SELECT cannot materialise value of type {:?}; \
15273 add an explicit CAST in the inner SELECT",
15274 other.data_type()
15275 )));
15276 }
15277 };
15278 Ok(Expr::Literal(lit))
15279}
15280
15281fn format_timestamp_micros(us: i64) -> String {
15282 let days = us.div_euclid(86_400_000_000);
15284 let intra_day = us.rem_euclid(86_400_000_000);
15285 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
15286 let secs = intra_day / 1_000_000;
15287 let us_rem = intra_day % 1_000_000;
15288 let h = (secs / 3600) % 24;
15289 let m = (secs / 60) % 60;
15290 let s = secs % 60;
15291 if us_rem == 0 {
15292 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
15293 } else {
15294 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
15295 }
15296}
15297
15298fn format_timestamp_micros_as_date(us: i64) -> String {
15299 let days = us.div_euclid(86_400_000_000);
15302 let jdn = days + 2_440_588;
15304 let (y, mo, d) = jdn_to_ymd(jdn);
15305 alloc::format!("{y:04}-{mo:02}-{d:02}")
15306}
15307
15308fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
15309 let l = jdn + 68569;
15311 let n = (4 * l) / 146_097;
15312 let l = l - (146_097 * n + 3) / 4;
15313 let i = (4000 * (l + 1)) / 1_461_001;
15314 let l = l - (1461 * i) / 4 + 31;
15315 let j = (80 * l) / 2447;
15316 let day = (l - (2447 * j) / 80) as u32;
15317 let l = j / 11;
15318 let month = (j + 2 - 12 * l) as u32;
15319 let year = 100 * (n - 49) + i + l;
15320 (year, month, day)
15321}
15322
15323fn format_numeric(scaled: i128, scale: u8) -> String {
15324 if scale == 0 {
15325 return alloc::format!("{scaled}");
15326 }
15327 let abs = scaled.unsigned_abs();
15328 let divisor = 10u128.pow(u32::from(scale));
15329 let whole = abs / divisor;
15330 let frac = abs % divisor;
15331 let sign = if scaled < 0 { "-" } else { "" };
15332 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
15333}
15334
15335fn rewrite_column_in_source(
15359 src: &str,
15360 old: &str,
15361 new: &str,
15362) -> Result<alloc::string::String, EngineError> {
15363 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15364 EngineError::Unsupported(alloc::format!(
15365 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
15366 failed to parse for rewrite ({e})"
15367 ))
15368 })?;
15369 rewrite_column_in_expr(&mut expr, old, new);
15370 Ok(alloc::format!("{expr}"))
15371}
15372
15373fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
15381 match e {
15382 Expr::AggregateOrdered { call, order_by, .. } => {
15383 rewrite_column_in_expr(call, old, new);
15384 for o in order_by.iter_mut() {
15385 rewrite_column_in_expr(&mut o.expr, old, new);
15386 }
15387 }
15388 Expr::Column(c) => {
15389 if c.name.eq_ignore_ascii_case(old) {
15390 c.name = new.to_string();
15391 }
15392 }
15393 Expr::Binary { lhs, rhs, .. } => {
15394 rewrite_column_in_expr(lhs, old, new);
15395 rewrite_column_in_expr(rhs, old, new);
15396 }
15397 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15398 rewrite_column_in_expr(expr, old, new);
15399 }
15400 Expr::FunctionCall { args, .. } => {
15401 for a in args {
15402 rewrite_column_in_expr(a, old, new);
15403 }
15404 }
15405 Expr::Like { expr, pattern, .. } => {
15406 rewrite_column_in_expr(expr, old, new);
15407 rewrite_column_in_expr(pattern, old, new);
15408 }
15409 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
15410 Expr::WindowFunction {
15411 args,
15412 partition_by,
15413 order_by,
15414 ..
15415 } => {
15416 for a in args {
15417 rewrite_column_in_expr(a, old, new);
15418 }
15419 for p in partition_by {
15420 rewrite_column_in_expr(p, old, new);
15421 }
15422 for (o, _, _) in order_by {
15423 rewrite_column_in_expr(o, old, new);
15424 }
15425 }
15426 Expr::Array(items) => {
15427 for elem in items {
15428 rewrite_column_in_expr(elem, old, new);
15429 }
15430 }
15431 Expr::ArraySubscript { target, index } => {
15432 rewrite_column_in_expr(target, old, new);
15433 rewrite_column_in_expr(index, old, new);
15434 }
15435 Expr::AnyAll { expr, array, .. } => {
15436 rewrite_column_in_expr(expr, old, new);
15437 rewrite_column_in_expr(array, old, new);
15438 }
15439 Expr::InList { expr, list, .. } => {
15440 rewrite_column_in_expr(expr, old, new);
15441 for item in list {
15442 rewrite_column_in_expr(item, old, new);
15443 }
15444 }
15445 Expr::Case {
15446 operand,
15447 branches,
15448 else_branch,
15449 } => {
15450 if let Some(o) = operand {
15451 rewrite_column_in_expr(o, old, new);
15452 }
15453 for (w, t) in branches {
15454 rewrite_column_in_expr(w, old, new);
15455 rewrite_column_in_expr(t, old, new);
15456 }
15457 if let Some(e) = else_branch {
15458 rewrite_column_in_expr(e, old, new);
15459 }
15460 }
15461 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
15465 Expr::Literal(_) | Expr::Placeholder(_) => {}
15466 }
15467}
15468
15469pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
15477 match stmt {
15478 Statement::Select(s) => substitute_select(s, params)?,
15479 Statement::Insert(ins) => {
15480 for row in &mut ins.rows {
15481 for e in row {
15482 substitute_expr(e, params)?;
15483 }
15484 }
15485 if let Some(clause) = &mut ins.on_conflict
15489 && let spg_sql::ast::OnConflictAction::Update {
15490 assignments,
15491 where_,
15492 } = &mut clause.action
15493 {
15494 for (_, e) in assignments.iter_mut() {
15495 substitute_expr(e, params)?;
15496 }
15497 if let Some(w) = where_ {
15498 substitute_expr(w, params)?;
15499 }
15500 }
15501 }
15502 Statement::Update(u) => {
15503 for (_, e) in &mut u.assignments {
15504 substitute_expr(e, params)?;
15505 }
15506 if let Some(w) = &mut u.where_ {
15507 substitute_expr(w, params)?;
15508 }
15509 }
15510 Statement::Delete(d) => {
15511 if let Some(w) = &mut d.where_ {
15512 substitute_expr(w, params)?;
15513 }
15514 }
15515 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
15516 _ => {}
15519 }
15520 Ok(())
15521}
15522
15523pub(crate) fn walk_select_exprs_mut(
15533 s: &mut SelectStatement,
15534 f: &mut impl FnMut(&mut Expr) -> Result<(), EngineError>,
15535) -> Result<(), EngineError> {
15536 for cte in &mut s.ctes {
15537 walk_select_exprs_mut(&mut cte.body, f)?;
15538 }
15539 for item in &mut s.items {
15540 if let SelectItem::Expr { expr, .. } = item {
15541 f(expr)?;
15542 }
15543 }
15544 if let Some(from) = &mut s.from {
15545 if let Some(sub) = &mut from.primary.lateral_subquery {
15546 walk_select_exprs_mut(sub, f)?;
15547 }
15548 for j in &mut from.joins {
15549 if let Some(sub) = &mut j.table.lateral_subquery {
15550 walk_select_exprs_mut(sub, f)?;
15551 }
15552 if let Some(on) = &mut j.on {
15553 f(on)?;
15554 }
15555 }
15556 }
15557 if let Some(w) = &mut s.where_ {
15558 f(w)?;
15559 }
15560 if let Some(gs) = &mut s.group_by {
15561 for g in gs {
15562 f(g)?;
15563 }
15564 }
15565 if let Some(h) = &mut s.having {
15566 f(h)?;
15567 }
15568 for o in &mut s.order_by {
15569 f(&mut o.expr)?;
15570 }
15571 for (_, peer) in &mut s.unions {
15572 walk_select_exprs_mut(peer, f)?;
15573 }
15574 Ok(())
15575}
15576
15577fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
15578 walk_select_exprs_mut(s, &mut |e| substitute_expr(e, params))?;
15579 for cte in &mut s.ctes {
15584 resolve_limit_offset_placeholders(&mut cte.body, params)?;
15585 }
15586 for (_, peer) in &mut s.unions {
15587 resolve_limit_offset_placeholders(peer, params)?;
15588 }
15589 if let Some(le) = s.limit {
15594 s.limit = Some(resolve_limit_placeholder(le, params)?);
15595 }
15596 if let Some(le) = s.offset {
15597 s.offset = Some(resolve_limit_placeholder(le, params)?);
15598 }
15599 Ok(())
15600}
15601
15602fn resolve_limit_offset_placeholders(
15605 s: &mut SelectStatement,
15606 params: &[Value],
15607) -> Result<(), EngineError> {
15608 if let Some(le) = s.limit {
15609 s.limit = Some(resolve_limit_placeholder(le, params)?);
15610 }
15611 if let Some(le) = s.offset {
15612 s.offset = Some(resolve_limit_placeholder(le, params)?);
15613 }
15614 for cte in &mut s.ctes {
15615 resolve_limit_offset_placeholders(&mut cte.body, params)?;
15616 }
15617 for (_, peer) in &mut s.unions {
15618 resolve_limit_offset_placeholders(peer, params)?;
15619 }
15620 Ok(())
15621}
15622
15623fn resolve_limit_placeholder(
15624 le: spg_sql::ast::LimitExpr,
15625 params: &[Value],
15626) -> Result<spg_sql::ast::LimitExpr, EngineError> {
15627 use spg_sql::ast::LimitExpr;
15628 match le {
15629 LimitExpr::Literal(_) => Ok(le),
15630 LimitExpr::Placeholder(n) => {
15631 let idx = usize::from(n).saturating_sub(1);
15632 let v = params.get(idx).ok_or_else(|| {
15633 EngineError::Eval(EvalError::PlaceholderOutOfRange {
15634 n,
15635 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
15636 })
15637 })?;
15638 let int = match v {
15639 Value::SmallInt(x) => Some(i64::from(*x)),
15640 Value::Int(x) => Some(i64::from(*x)),
15641 Value::BigInt(x) => Some(*x),
15642 _ => None,
15643 }
15644 .ok_or_else(|| {
15645 EngineError::Unsupported(alloc::format!(
15646 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
15647 ))
15648 })?;
15649 if int < 0 {
15650 return Err(EngineError::Unsupported(alloc::format!(
15651 "LIMIT/OFFSET ${n} bound to negative value {int}"
15652 )));
15653 }
15654 let bounded = u32::try_from(int).map_err(|_| {
15655 EngineError::Unsupported(alloc::format!(
15656 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
15657 ))
15658 })?;
15659 Ok(LimitExpr::Literal(bounded))
15660 }
15661 }
15662}
15663
15664fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
15665 if let Expr::Placeholder(n) = e {
15666 let idx = usize::from(*n).saturating_sub(1);
15667 let v = params.get(idx).ok_or_else(|| {
15668 EngineError::Eval(EvalError::PlaceholderOutOfRange {
15669 n: *n,
15670 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
15671 })
15672 })?;
15673 *e = Expr::Literal(value_to_literal(v.clone()));
15674 return Ok(());
15675 }
15676 match e {
15677 Expr::AggregateOrdered { call, order_by, .. } => {
15678 substitute_expr(call, params)?;
15679 for o in order_by.iter_mut() {
15680 substitute_expr(&mut o.expr, params)?;
15681 }
15682 }
15683 Expr::Binary { lhs, rhs, .. } => {
15684 substitute_expr(lhs, params)?;
15685 substitute_expr(rhs, params)?;
15686 }
15687 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15688 substitute_expr(expr, params)?;
15689 }
15690 Expr::FunctionCall { args, .. } => {
15691 for a in args {
15692 substitute_expr(a, params)?;
15693 }
15694 }
15695 Expr::Like { expr, pattern, .. } => {
15696 substitute_expr(expr, params)?;
15697 substitute_expr(pattern, params)?;
15698 }
15699 Expr::Extract { source, .. } => substitute_expr(source, params)?,
15700 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
15701 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
15702 Expr::InSubquery { expr, subquery, .. } => {
15703 substitute_expr(expr, params)?;
15704 substitute_select(subquery, params)?;
15705 }
15706 Expr::WindowFunction {
15707 args,
15708 partition_by,
15709 order_by,
15710 ..
15711 } => {
15712 for a in args {
15713 substitute_expr(a, params)?;
15714 }
15715 for p in partition_by {
15716 substitute_expr(p, params)?;
15717 }
15718 for (e, _, _) in order_by {
15719 substitute_expr(e, params)?;
15720 }
15721 }
15722 Expr::Literal(_) | Expr::Column(_) => {}
15723 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
15725 Expr::Array(items) => {
15726 for elem in items {
15727 substitute_expr(elem, params)?;
15728 }
15729 }
15730 Expr::ArraySubscript { target, index } => {
15731 substitute_expr(target, params)?;
15732 substitute_expr(index, params)?;
15733 }
15734 Expr::AnyAll { expr, array, .. } => {
15735 substitute_expr(expr, params)?;
15736 substitute_expr(array, params)?;
15737 }
15738 Expr::InList { expr, list, .. } => {
15739 substitute_expr(expr, params)?;
15740 for item in list {
15741 substitute_expr(item, params)?;
15742 }
15743 }
15744 Expr::Case {
15745 operand,
15746 branches,
15747 else_branch,
15748 } => {
15749 if let Some(o) = operand {
15750 substitute_expr(o, params)?;
15751 }
15752 for (w, t) in branches {
15753 substitute_expr(w, params)?;
15754 substitute_expr(t, params)?;
15755 }
15756 if let Some(e) = else_branch {
15757 substitute_expr(e, params)?;
15758 }
15759 }
15760 }
15761 Ok(())
15762}
15763
15764fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
15782 use core::cmp::Ordering;
15783 match (a, b) {
15784 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
15785 (Value::Int(a), Value::Int(b)) => a.cmp(b),
15786 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
15787 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
15788 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
15789 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
15790 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
15791 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
15792 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
15793 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
15794 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
15795 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
15796 (Value::Date(a), Value::Date(b)) => a.cmp(b),
15797 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
15798 (Value::SmallInt(n), Value::Float(x)) => {
15800 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
15801 }
15802 (Value::Float(x), Value::SmallInt(n)) => {
15803 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
15804 }
15805 (Value::Int(n), Value::Float(x)) => {
15806 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
15807 }
15808 (Value::Float(x), Value::Int(n)) => {
15809 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
15810 }
15811 (Value::BigInt(n), Value::Float(x)) => {
15812 #[allow(clippy::cast_precision_loss)]
15813 let nf = *n as f64;
15814 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
15815 }
15816 (Value::Float(x), Value::BigInt(n)) => {
15817 #[allow(clippy::cast_precision_loss)]
15818 let nf = *n as f64;
15819 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
15820 }
15821 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
15824 }
15825}
15826
15827fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
15834 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
15835 out.push('[');
15836 for (i, b) in bounds.iter().enumerate() {
15837 if i > 0 {
15838 out.push_str(", ");
15839 }
15840 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
15841 if needs_quote {
15842 out.push('"');
15843 for ch in b.chars() {
15844 if ch == '"' || ch == '\\' {
15845 out.push('\\');
15846 }
15847 out.push(ch);
15848 }
15849 out.push('"');
15850 } else {
15851 out.push_str(b);
15852 }
15853 }
15854 out.push(']');
15855 out
15856}
15857
15858pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
15868 match v {
15869 Value::Null => "NULL".to_string(),
15870 Value::SmallInt(n) => alloc::format!("{n}"),
15871 Value::Int(n) => alloc::format!("{n}"),
15872 Value::BigInt(n) => alloc::format!("{n}"),
15873 Value::Float(x) => alloc::format!("{x:?}"),
15874 Value::Text(s) | Value::Json(s) => s.clone(),
15875 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
15876 Value::Date(d) => eval::format_date(*d),
15877 Value::Timestamp(t) => eval::format_timestamp(*t),
15878 Value::Time(us) => eval::format_time(*us),
15880 Value::Year(y) => alloc::format!("{y:04}"),
15882 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
15884 Value::Money(c) => eval::format_money(*c),
15886 v @ Value::Range { .. } => format_range_str(v),
15888 Value::Hstore(pairs) => format_hstore_str(pairs),
15890 Value::IntArray2D(rows) => format_int_2d_text(rows),
15892 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
15893 Value::TextArray2D(rows) => format_text_2d_text(rows),
15894 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
15895 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
15896 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
15897 alloc::format!("{v:?}")
15901 }
15902 _ => alloc::format!("{v:?}"),
15906 }
15907}
15908
15909const fn is_internal_table_name(_name: &str) -> bool {
15916 false
15917}
15918
15919fn value_to_literal(v: Value) -> Literal {
15920 match v {
15921 Value::Null => Literal::Null,
15922 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15923 Value::Int(n) => Literal::Integer(i64::from(n)),
15924 Value::BigInt(n) => Literal::Integer(n),
15925 Value::Float(x) => Literal::Float(x),
15926 Value::Text(s) | Value::Json(s) => Literal::String(s),
15927 Value::Bool(b) => Literal::Bool(b),
15928 Value::Vector(v) => Literal::Vector(v),
15929 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
15930 Value::Date(d) => Literal::String(eval::format_date(d)),
15931 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
15932 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
15938 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
15943 Value::TextArray(items) => Literal::TextArray(items),
15948 Value::IntArray(items) => Literal::IntArray(items),
15949 Value::BigIntArray(items) => Literal::BigIntArray(items),
15950 Value::Interval { months, micros } => Literal::Interval {
15951 months,
15952 micros,
15953 text: eval::format_interval(months, micros),
15954 },
15955 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
15958 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
15959 v => Literal::String(alloc::format!("{v:?}")),
15963 }
15964}
15965
15966fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
15967 let Some(now) = now_micros else {
15968 return;
15969 };
15970 match stmt {
15971 Statement::Select(s) => rewrite_select_clock(s, now),
15972 Statement::Insert(ins) => {
15973 for row in &mut ins.rows {
15974 for e in row {
15975 rewrite_expr_clock(e, now);
15976 }
15977 }
15978 if let Some(clause) = &mut ins.on_conflict
15982 && let spg_sql::ast::OnConflictAction::Update {
15983 assignments,
15984 where_,
15985 } = &mut clause.action
15986 {
15987 for (_, e) in assignments.iter_mut() {
15988 rewrite_expr_clock(e, now);
15989 }
15990 if let Some(w) = where_ {
15991 rewrite_expr_clock(w, now);
15992 }
15993 }
15994 }
15995 Statement::Update(u) => {
15999 for (_, e) in &mut u.assignments {
16000 rewrite_expr_clock(e, now);
16001 }
16002 if let Some(w) = &mut u.where_ {
16003 rewrite_expr_clock(w, now);
16004 }
16005 }
16006 Statement::Delete(d) => {
16007 if let Some(w) = &mut d.where_ {
16008 rewrite_expr_clock(w, now);
16009 }
16010 }
16011 _ => {}
16012 }
16013}
16014
16015fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
16016 let _ = walk_select_exprs_mut(s, &mut |e| {
16021 rewrite_expr_clock(e, now);
16022 Ok(())
16023 });
16024}
16025
16026fn rewrite_expr_clock(e: &mut Expr, now: i64) {
16034 if let Some(replacement) = clock_replacement_for(e, now) {
16038 *e = replacement;
16039 return;
16040 }
16041 match e {
16042 Expr::AggregateOrdered { call, order_by, .. } => {
16043 rewrite_expr_clock(call, now);
16044 for o in order_by.iter_mut() {
16045 rewrite_expr_clock(&mut o.expr, now);
16046 }
16047 }
16048 Expr::Binary { lhs, rhs, .. } => {
16049 rewrite_expr_clock(lhs, now);
16050 rewrite_expr_clock(rhs, now);
16051 }
16052 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
16053 rewrite_expr_clock(expr, now);
16054 }
16055 Expr::FunctionCall { args, .. } => {
16056 for a in args {
16057 rewrite_expr_clock(a, now);
16058 }
16059 }
16060 Expr::Like { expr, pattern, .. } => {
16061 rewrite_expr_clock(expr, now);
16062 rewrite_expr_clock(pattern, now);
16063 }
16064 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
16065 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
16069 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
16070 Expr::InSubquery { expr, subquery, .. } => {
16071 rewrite_expr_clock(expr, now);
16072 rewrite_select_clock(subquery, now);
16073 }
16074 Expr::WindowFunction {
16077 args,
16078 partition_by,
16079 order_by,
16080 ..
16081 } => {
16082 for a in args {
16083 rewrite_expr_clock(a, now);
16084 }
16085 for p in partition_by {
16086 rewrite_expr_clock(p, now);
16087 }
16088 for (e, _, _) in order_by {
16089 rewrite_expr_clock(e, now);
16090 }
16091 }
16092 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
16093 Expr::Array(items) => {
16094 for elem in items {
16095 rewrite_expr_clock(elem, now);
16096 }
16097 }
16098 Expr::ArraySubscript { target, index } => {
16099 rewrite_expr_clock(target, now);
16100 rewrite_expr_clock(index, now);
16101 }
16102 Expr::AnyAll { expr, array, .. } => {
16103 rewrite_expr_clock(expr, now);
16104 rewrite_expr_clock(array, now);
16105 }
16106 Expr::InList { expr, list, .. } => {
16107 rewrite_expr_clock(expr, now);
16108 for item in list {
16109 rewrite_expr_clock(item, now);
16110 }
16111 }
16112 Expr::Case {
16113 operand,
16114 branches,
16115 else_branch,
16116 } => {
16117 if let Some(o) = operand {
16118 rewrite_expr_clock(o, now);
16119 }
16120 for (w, t) in branches {
16121 rewrite_expr_clock(w, now);
16122 rewrite_expr_clock(t, now);
16123 }
16124 if let Some(e) = else_branch {
16125 rewrite_expr_clock(e, now);
16126 }
16127 }
16128 }
16129}
16130
16131fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
16138 let (kind, name) = match e {
16139 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
16140 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
16141 _ => return None,
16142 };
16143 enum ClockShape {
16151 Timestamp,
16152 Date,
16153 UnixSeconds,
16154 }
16155 let shape = match name.len() {
16156 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
16157 Some(ClockShape::Timestamp)
16158 }
16159 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
16160 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
16161 Some(ClockShape::UnixSeconds)
16162 }
16163 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
16164 _ => None,
16165 };
16166 let shape = shape?;
16167 let payload = match shape {
16168 ClockShape::Timestamp => now,
16169 ClockShape::Date => now.div_euclid(86_400_000_000),
16170 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
16171 };
16172 let target = match shape {
16173 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
16174 ClockShape::Date => spg_sql::ast::CastTarget::Date,
16175 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
16176 };
16177 Some(Expr::Cast {
16178 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
16179 target,
16180 })
16181}
16182
16183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16184enum ClockSite {
16185 Fn,
16186 BareIdent,
16187}
16188
16189fn expand_group_by_all(s: &mut SelectStatement) {
16200 if !s.group_by_all {
16201 for (_, peer) in &mut s.unions {
16202 expand_group_by_all(peer);
16203 }
16204 return;
16205 }
16206 let mut groups: Vec<Expr> = Vec::new();
16207 for item in &s.items {
16208 if let SelectItem::Expr { expr, .. } = item
16209 && !aggregate::contains_aggregate(expr)
16210 {
16211 groups.push(expr.clone());
16212 }
16213 }
16214 s.group_by = Some(groups);
16215 s.group_by_all = false;
16216 for (_, peer) in &mut s.unions {
16217 expand_group_by_all(peer);
16218 }
16219}
16220
16221fn resolve_order_by_position(s: &mut SelectStatement) {
16222 for order in &mut s.order_by {
16227 match &order.expr {
16228 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
16229 if let Ok(idx_one_based) = usize::try_from(*n) {
16230 let idx = idx_one_based - 1;
16231 if idx < s.items.len()
16232 && let SelectItem::Expr { expr, .. } = &s.items[idx]
16233 {
16234 order.expr = expr.clone();
16235 }
16236 }
16237 }
16238 Expr::Column(c) if c.qualifier.is_none() => {
16239 for item in &s.items {
16241 if let SelectItem::Expr {
16242 expr,
16243 alias: Some(a),
16244 } = item
16245 && a == &c.name
16246 {
16247 order.expr = expr.clone();
16248 break;
16249 }
16250 }
16251 }
16252 _ => {}
16253 }
16254 }
16255 for (_, peer) in &mut s.unions {
16256 resolve_order_by_position(peer);
16257 }
16258}
16259
16260fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
16273 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
16274 match keep {
16275 Some(k) if k < tagged.len() && k > 0 => {
16276 let pivot = k - 1;
16277 tagged.select_nth_unstable_by(pivot, cmp);
16278 tagged[..k].sort_by(cmp);
16279 tagged.truncate(k);
16280 }
16281 _ => {
16282 tagged.sort_by(cmp);
16283 }
16284 }
16285}
16286
16287fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
16288 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
16289}
16290
16291fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
16295 use core::cmp::Ordering;
16296 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
16297 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
16298 let ord = if descs.get(i).copied().unwrap_or(false) {
16299 ord.reverse()
16300 } else {
16301 ord
16302 };
16303 if ord != Ordering::Equal {
16304 return ord;
16305 }
16306 }
16307 Ordering::Equal
16308}
16309
16310fn build_order_keys(
16313 order_by: &[OrderBy],
16314 row: &Row,
16315 ctx: &EvalContext,
16316) -> Result<Vec<f64>, EngineError> {
16317 let mut keys = Vec::with_capacity(order_by.len());
16318 for o in order_by {
16319 let v = eval::eval_expr(&o.expr, row, ctx)?;
16320 if matches!(v, Value::Null) {
16327 let nf = o.nulls_first.unwrap_or(o.desc);
16328 keys.push(if nf == o.desc {
16329 f64::INFINITY
16330 } else {
16331 f64::NEG_INFINITY
16332 });
16333 } else {
16334 keys.push(value_to_order_key(&v)?);
16335 }
16336 }
16337 Ok(keys)
16338}
16339
16340fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
16344 if let Some(off) = offset {
16345 let off = off as usize;
16346 if off >= rows.len() {
16347 rows.clear();
16348 } else {
16349 rows.drain(..off);
16350 }
16351 }
16352 if let Some(n) = limit {
16353 rows.truncate(n as usize);
16354 }
16355}
16356
16357fn apply_offset_and_limit_tagged(
16368 tagged: &mut Vec<(Vec<f64>, Row)>,
16369 offset: Option<u32>,
16370 limit: Option<u32>,
16371 with_ties: bool,
16372) {
16373 if let Some(off) = offset {
16374 let off = off as usize;
16375 if off >= tagged.len() {
16376 tagged.clear();
16377 } else {
16378 tagged.drain(..off);
16379 }
16380 }
16381 if let Some(n) = limit {
16382 let n = n as usize;
16383 if with_ties && n > 0 && n < tagged.len() {
16384 let cutoff_key = tagged[n - 1].0.clone();
16385 let mut end = n;
16386 while end < tagged.len() && tagged[end].0 == cutoff_key {
16387 end += 1;
16388 }
16389 tagged.truncate(end);
16390 } else {
16391 tagged.truncate(n);
16392 }
16393 }
16394}
16395
16396fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
16402 if stmt.limit_with_ties && stmt.order_by.is_empty() {
16403 return Err(EngineError::Unsupported(alloc::string::String::from(
16404 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
16405 )));
16406 }
16407 Ok(())
16408}
16409
16410fn resolve_foreign_key(
16424 local_table_name: &str,
16425 local_cols: &[ColumnSchema],
16426 fk: spg_sql::ast::ForeignKeyConstraint,
16427 catalog: &Catalog,
16428) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
16429 let mut local_columns = Vec::with_capacity(fk.columns.len());
16431 for name in &fk.columns {
16432 let pos = local_cols
16433 .iter()
16434 .position(|c| c.name == *name)
16435 .ok_or_else(|| {
16436 EngineError::Unsupported(alloc::format!(
16437 "FOREIGN KEY references unknown local column {name:?}"
16438 ))
16439 })?;
16440 local_columns.push(pos);
16441 }
16442 let is_self_ref = fk.parent_table == local_table_name;
16446 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
16447 (local_cols, local_table_name)
16448 } else {
16449 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
16450 EngineError::Storage(StorageError::TableNotFound {
16451 name: fk.parent_table.clone(),
16452 })
16453 })?;
16454 (
16455 parent_table.schema().columns.as_slice(),
16456 fk.parent_table.as_str(),
16457 )
16458 };
16459 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
16464 if fk.columns.len() != 1 {
16465 return Err(EngineError::Unsupported(
16466 "composite FOREIGN KEY without explicit parent column list is not supported \
16467 — list the parent columns explicitly"
16468 .into(),
16469 ));
16470 }
16471 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
16473 .ok_or_else(|| {
16474 EngineError::Unsupported(alloc::format!(
16475 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
16476 to default the FOREIGN KEY against"
16477 ))
16478 })?;
16479 alloc::vec![pos]
16480 } else {
16481 let mut out = Vec::with_capacity(fk.parent_columns.len());
16482 for name in &fk.parent_columns {
16483 let pos = parent_cols_for_lookup
16484 .iter()
16485 .position(|c| c.name == *name)
16486 .ok_or_else(|| {
16487 EngineError::Unsupported(alloc::format!(
16488 "FOREIGN KEY references unknown parent column \
16489 {name:?} on table {parent_table_str:?}"
16490 ))
16491 })?;
16492 out.push(pos);
16493 }
16494 out
16495 };
16496 if parent_columns.len() != local_columns.len() {
16497 return Err(EngineError::Unsupported(alloc::format!(
16498 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
16499 local_columns.len(),
16500 parent_columns.len()
16501 )));
16502 }
16503 if !is_self_ref {
16513 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
16514 let primary_parent_col = parent_columns[0];
16515 let has_btree = parent_table
16516 .schema()
16517 .columns
16518 .get(primary_parent_col)
16519 .is_some()
16520 && parent_table.indices().iter().any(|idx| {
16521 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16522 && idx.column_position == primary_parent_col
16523 && idx.partial_predicate.is_none()
16524 });
16525 if !has_btree {
16526 return Err(EngineError::Unsupported(alloc::format!(
16527 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
16528 index — create one with `CREATE INDEX ... ON {} ({})` first",
16529 parent_table_str,
16530 parent_table_str,
16531 parent_table.schema().columns[primary_parent_col].name,
16532 )));
16533 }
16534 }
16535 let on_delete = fk_action_sql_to_storage(fk.on_delete);
16536 let on_update = fk_action_sql_to_storage(fk.on_update);
16537 Ok(spg_storage::ForeignKeyConstraint {
16538 name: fk.name,
16539 local_columns,
16540 parent_table: fk.parent_table,
16541 parent_columns,
16542 on_delete,
16543 on_update,
16544 })
16545}
16546
16547fn pick_pk_index_column(
16553 catalog: &Catalog,
16554 parent_name: &str,
16555 is_self_ref: bool,
16556 local_cols: &[ColumnSchema],
16557) -> Option<usize> {
16558 if is_self_ref {
16559 let _ = local_cols;
16563 return Some(0);
16564 }
16565 let parent = catalog.get(parent_name)?;
16566 parent.indices().iter().find_map(|idx| {
16567 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16568 && idx.partial_predicate.is_none()
16569 && idx.included_columns.is_empty()
16570 && idx.expression.is_none()
16571 {
16572 Some(idx.column_position)
16573 } else {
16574 None
16575 }
16576 })
16577}
16578
16579fn resolve_on_conflict_columns(
16590 catalog: &Catalog,
16591 table_name: &str,
16592 target: &[String],
16593) -> Result<(Vec<usize>, bool), EngineError> {
16594 let table = catalog.get(table_name).ok_or_else(|| {
16595 EngineError::Storage(StorageError::TableNotFound {
16596 name: table_name.into(),
16597 })
16598 })?;
16599 if target.is_empty() {
16600 if let Some(uc) = table.schema().uniqueness_constraints.first() {
16610 return Ok((uc.columns.clone(), uc.nulls_not_distinct));
16611 }
16612 let pos = table
16613 .indices()
16614 .iter()
16615 .find_map(|idx| {
16616 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16617 && idx.partial_predicate.is_none()
16618 && idx.included_columns.is_empty()
16619 && idx.expression.is_none()
16620 {
16621 Some(idx.column_position)
16622 } else {
16623 None
16624 }
16625 })
16626 .ok_or_else(|| {
16627 EngineError::Unsupported(alloc::format!(
16628 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
16629 ))
16630 })?;
16631 return Ok((alloc::vec![pos], false));
16632 }
16633 let mut out = Vec::with_capacity(target.len());
16634 for name in target {
16635 let pos = table
16636 .schema()
16637 .columns
16638 .iter()
16639 .position(|c| c.name == *name)
16640 .ok_or_else(|| {
16641 EngineError::Unsupported(alloc::format!(
16642 "ON CONFLICT target column {name:?} not found on {table_name:?}"
16643 ))
16644 })?;
16645 out.push(pos);
16646 }
16647 let mut sorted = out.clone();
16650 sorted.sort_unstable();
16651 let nnd = table.schema().uniqueness_constraints.iter().any(|uc| {
16652 let mut u = uc.columns.clone();
16653 u.sort_unstable();
16654 u == sorted && uc.nulls_not_distinct
16655 });
16656 Ok((out, nnd))
16657}
16658
16659fn on_conflict_key_exists(
16662 catalog: &Catalog,
16663 table_name: &str,
16664 column_pos: usize,
16665 key: &Value,
16666) -> bool {
16667 let Some(table) = catalog.get(table_name) else {
16668 return false;
16669 };
16670 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
16671 return false;
16672 };
16673 table.indices().iter().any(|idx| {
16674 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16675 && idx.column_position == column_pos
16676 && idx.partial_predicate.is_none()
16677 && !idx.lookup_eq(&idx_key).is_empty()
16678 })
16679}
16680
16681fn lookup_row_position_by_keys(
16687 catalog: &Catalog,
16688 table_name: &str,
16689 column_positions: &[usize],
16690 key: &[&Value],
16691) -> Option<usize> {
16692 let table = catalog.get(table_name)?;
16693 table.rows().iter().position(|r| {
16694 column_positions
16695 .iter()
16696 .enumerate()
16697 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
16698 })
16699}
16700
16701fn on_conflict_keys_exist(
16706 catalog: &Catalog,
16707 table_name: &str,
16708 column_positions: &[usize],
16709 key: &[&Value],
16710) -> bool {
16711 if column_positions.len() == 1 {
16712 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
16713 }
16714 let Some(table) = catalog.get(table_name) else {
16715 return false;
16716 };
16717 table.rows().iter().any(|r| {
16718 column_positions
16719 .iter()
16720 .enumerate()
16721 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
16722 })
16723}
16724
16725fn apply_on_conflict_assignments(
16738 catalog: &Catalog,
16739 table_name: &str,
16740 target_pos: usize,
16741 incoming: &[Value],
16742 assignments: &[(String, Expr)],
16743 where_: Option<&Expr>,
16744) -> Result<Option<Vec<Value>>, EngineError> {
16745 let table = catalog.get(table_name).ok_or_else(|| {
16746 EngineError::Storage(StorageError::TableNotFound {
16747 name: table_name.into(),
16748 })
16749 })?;
16750 let schema_cols = table.schema().columns.clone();
16751 let existing = table
16752 .rows()
16753 .get(target_pos)
16754 .ok_or_else(|| {
16755 EngineError::Unsupported(alloc::format!(
16756 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
16757 ))
16758 })?
16759 .clone();
16760 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
16761 if let Some(w) = where_ {
16763 let pred = w.clone();
16764 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
16765 let v = eval::eval_expr(&pred, &existing, &ctx)?;
16766 if !matches!(v, Value::Bool(true)) {
16767 return Ok(None);
16768 }
16769 }
16770 let mut new_values = existing.values.clone();
16771 for (col_name, expr) in assignments {
16772 let target_idx = schema_cols
16773 .iter()
16774 .position(|c| c.name == *col_name)
16775 .ok_or_else(|| {
16776 EngineError::Eval(EvalError::ColumnNotFound {
16777 name: col_name.clone(),
16778 })
16779 })?;
16780 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
16781 let v = eval::eval_expr(&sub, &existing, &ctx)?;
16782 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
16783 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
16784 new_values[target_idx] = coerced;
16785 }
16786 Ok(Some(new_values))
16787}
16788
16789fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
16794 use spg_sql::ast::ColumnName;
16795 match expr {
16796 Expr::Column(ColumnName { qualifier, name })
16797 if qualifier
16798 .as_deref()
16799 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
16800 {
16801 let pos = schema_cols.iter().position(|c| c.name == name);
16802 match pos {
16803 Some(p) => {
16804 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
16805 value_to_literal_expr(v)
16806 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
16807 }
16808 None => Expr::Column(ColumnName { qualifier, name }),
16809 }
16810 }
16811 Expr::Binary { op, lhs, rhs } => Expr::Binary {
16812 op,
16813 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
16814 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
16815 },
16816 Expr::Unary { op, expr } => Expr::Unary {
16817 op,
16818 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
16819 },
16820 Expr::FunctionCall { name, args } => Expr::FunctionCall {
16821 name,
16822 args: args
16823 .into_iter()
16824 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
16825 .collect(),
16826 },
16827 other => other,
16828 }
16829}
16830
16831fn enforce_uniqueness_inserts(
16854 catalog: &Catalog,
16855 child_table: &str,
16856 constraints: &[spg_storage::UniquenessConstraint],
16857 rows: &[Vec<Value>],
16858) -> Result<(), EngineError> {
16859 if constraints.is_empty() {
16860 return Ok(());
16861 }
16862 let table = catalog.get(child_table).ok_or_else(|| {
16863 EngineError::Storage(StorageError::TableNotFound {
16864 name: child_table.into(),
16865 })
16866 })?;
16867 let schema = table.schema();
16868 for uc in constraints {
16878 let fold_key = |values: &[Value]| -> Vec<Value> {
16879 uc.columns
16880 .iter()
16881 .map(|&i| {
16882 let v = values.get(i).cloned().unwrap_or(Value::Null);
16883 collated_key_cell(&v, i, schema)
16884 })
16885 .collect()
16886 };
16887 let mut seen: hashbrown::HashSet<String> =
16888 hashbrown::HashSet::with_capacity(table.rows().len() + rows.len());
16889 for prow in table.rows() {
16890 let key = fold_key(&prow.values);
16891 if key.iter().any(|v| matches!(v, Value::Null)) && !uc.nulls_not_distinct {
16892 continue;
16893 }
16894 seen.insert(aggregate::encode_key(&key));
16895 }
16896 for (batch_idx, row_values) in rows.iter().enumerate() {
16897 let key = fold_key(row_values);
16898 if key.iter().any(|v| matches!(v, Value::Null)) && !uc.nulls_not_distinct {
16899 continue;
16900 }
16901 if !seen.insert(aggregate::encode_key(&key)) {
16902 let kind = if uc.is_primary_key {
16903 "PRIMARY KEY"
16904 } else {
16905 "UNIQUE"
16906 };
16907 let col_names: Vec<String> = uc
16908 .columns
16909 .iter()
16910 .map(|&i| table.schema().columns[i].name.clone())
16911 .collect();
16912 return Err(EngineError::Unsupported(alloc::format!(
16913 "{kind} violation on {child_table:?} columns {col_names:?}: \
16914 row #{batch_idx} duplicates an existing key"
16915 )));
16916 }
16917 }
16918 }
16919 Ok(())
16920}
16921
16922fn collated_key_cell(
16929 v: &spg_storage::Value,
16930 column_position: usize,
16931 schema: &spg_storage::TableSchema,
16932) -> spg_storage::Value {
16933 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
16934 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
16935 spg_storage::Value::Text(s.to_ascii_lowercase())
16936 }
16937 _ => v.clone(),
16938 }
16939}
16940
16941fn predicate_truthy(v: &spg_storage::Value) -> bool {
16949 use spg_storage::Value as V;
16950 match v {
16951 V::Bool(b) => *b,
16952 V::Int(n) => *n != 0,
16953 V::BigInt(n) => *n != 0,
16954 V::SmallInt(n) => *n != 0,
16955 _ => false,
16956 }
16957}
16958
16959fn check_existing_unique_violation(
16964 idx: &spg_storage::Index,
16965 schema: &spg_storage::TableSchema,
16966 rows: &[spg_storage::Row],
16967) -> Result<(), EngineError> {
16968 let predicate_expr = match idx.partial_predicate.as_deref() {
16969 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16970 EngineError::Unsupported(alloc::format!(
16971 "stored partial predicate {s:?} failed to re-parse: {e:?}"
16972 ))
16973 })?),
16974 None => None,
16975 };
16976 let ctx = eval::EvalContext::new(&schema.columns, None);
16977 let key_positions = unique_key_positions(idx);
16978 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
16979 for row in rows {
16980 if let Some(expr) = &predicate_expr {
16981 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
16982 EngineError::Unsupported(alloc::format!(
16983 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
16984 ))
16985 })?;
16986 if !predicate_truthy(&v) {
16987 continue;
16988 }
16989 }
16990 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
16991 .iter()
16992 .map(|&p| {
16993 let v = row
16994 .values
16995 .get(p)
16996 .cloned()
16997 .unwrap_or(spg_storage::Value::Null);
16998 collated_key_cell(&v, p, schema)
16999 })
17000 .collect();
17001 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
17002 continue;
17003 }
17004 if seen.iter().any(|other| *other == key) {
17005 return Err(EngineError::Unsupported(alloc::format!(
17006 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
17007 idx.name
17008 )));
17009 }
17010 seen.push(key);
17011 }
17012 Ok(())
17013}
17014
17015fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
17019 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
17020 out.push(idx.column_position);
17021 out.extend_from_slice(&idx.extra_column_positions);
17022 out
17023}
17024
17025fn enforce_unique_index_inserts(
17033 catalog: &Catalog,
17034 table_name: &str,
17035 rows: &[alloc::vec::Vec<spg_storage::Value>],
17036) -> Result<(), EngineError> {
17037 let table = catalog.get(table_name).ok_or_else(|| {
17038 EngineError::Storage(StorageError::TableNotFound {
17039 name: table_name.into(),
17040 })
17041 })?;
17042 let schema = table.schema();
17043 let ctx = eval::EvalContext::new(&schema.columns, None);
17044 for idx in table.indices() {
17045 if !idx.is_unique {
17046 continue;
17047 }
17048 let predicate_expr = match idx.partial_predicate.as_deref() {
17050 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
17051 EngineError::Unsupported(alloc::format!(
17052 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
17053 idx.name
17054 ))
17055 })?),
17056 None => None,
17057 };
17058 let key_positions = unique_key_positions(idx);
17059 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
17060 key_positions
17061 .iter()
17062 .map(|&p| {
17063 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
17064 collated_key_cell(&v, p, schema)
17065 })
17066 .collect()
17067 };
17068 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
17069 let Some(expr) = &predicate_expr else {
17070 return Ok(true);
17071 };
17072 let tmp_row = spg_storage::Row {
17073 values: values.to_vec(),
17074 };
17075 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
17076 EngineError::Unsupported(alloc::format!(
17077 "UNIQUE INDEX {:?} predicate eval: {e:?}",
17078 idx.name
17079 ))
17080 })?;
17081 Ok(predicate_truthy(&v))
17082 };
17083 let mut seen: hashbrown::HashSet<String> =
17088 hashbrown::HashSet::with_capacity(table.rows().len() + rows.len());
17089 for prow in table.rows() {
17090 if !participates(&prow.values)? {
17091 continue;
17092 }
17093 let key = key_of(&prow.values);
17094 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
17095 continue;
17096 }
17097 seen.insert(aggregate::encode_key(&key));
17098 }
17099 for (batch_idx, row_values) in rows.iter().enumerate() {
17100 if !participates(row_values)? {
17101 continue;
17102 }
17103 let key = key_of(row_values);
17104 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
17105 continue;
17106 }
17107 if !seen.insert(aggregate::encode_key(&key)) {
17108 return Err(EngineError::Unsupported(alloc::format!(
17109 "UNIQUE INDEX {:?} violation on {table_name:?}: \
17110 row #{batch_idx} duplicates an existing key",
17111 idx.name
17112 )));
17113 }
17114 }
17115 }
17116 Ok(())
17117}
17118
17119fn any_column_changed(
17127 filter_cols: &[String],
17128 schema_cols: &[ColumnSchema],
17129 old_row: &Row,
17130 new_row: &Row,
17131) -> bool {
17132 for col_name in filter_cols {
17133 let Some(pos) = schema_cols
17134 .iter()
17135 .position(|c| c.name.eq_ignore_ascii_case(col_name))
17136 else {
17137 continue;
17138 };
17139 let old_v = old_row.values.get(pos);
17140 let new_v = new_row.values.get(pos);
17141 if old_v != new_v {
17142 return true;
17143 }
17144 }
17145 false
17146}
17147
17148fn enforce_check_constraints(
17153 catalog: &Catalog,
17154 table_name: &str,
17155 rows: &[alloc::vec::Vec<spg_storage::Value>],
17156) -> Result<(), EngineError> {
17157 let table = catalog.get(table_name).ok_or_else(|| {
17158 EngineError::Storage(StorageError::TableNotFound {
17159 name: table_name.into(),
17160 })
17161 })?;
17162 let schema = table.schema();
17163 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
17167 alloc::vec::Vec::new();
17168 for (idx, col) in schema.columns.iter().enumerate() {
17169 let Some(dname) = &col.user_domain_type else {
17170 continue;
17171 };
17172 let Some(dom) = catalog.domain_types().get(dname) else {
17173 continue;
17174 };
17175 let mut parsed_for_col: alloc::vec::Vec<Expr> =
17176 alloc::vec::Vec::with_capacity(dom.checks.len());
17177 for src in &dom.checks {
17178 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
17179 EngineError::Unsupported(alloc::format!(
17180 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
17181 col.name
17182 ))
17183 })?;
17184 parsed_for_col.push(expr);
17185 }
17186 if !parsed_for_col.is_empty() {
17187 domain_checks_per_col.push((idx, parsed_for_col));
17188 }
17189 }
17190 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
17191 return Ok(());
17192 }
17193 let ctx = eval::EvalContext::new(&schema.columns, None);
17194 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
17195 for (i, src) in schema.checks.iter().enumerate() {
17196 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
17197 EngineError::Unsupported(alloc::format!(
17198 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
17199 ))
17200 })?;
17201 parsed.push((i, expr));
17202 }
17203 for (batch_idx, row_values) in rows.iter().enumerate() {
17204 let tmp_row = spg_storage::Row {
17205 values: row_values.clone(),
17206 };
17207 for (i, expr) in &parsed {
17208 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
17209 EngineError::Unsupported(alloc::format!(
17210 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
17211 ))
17212 })?;
17213 if matches!(v, spg_storage::Value::Bool(false)) {
17215 return Err(EngineError::Unsupported(alloc::format!(
17216 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
17217 schema.checks[*i]
17218 )));
17219 }
17220 }
17221 for (col_idx, checks) in &domain_checks_per_col {
17227 let cell = row_values
17228 .get(*col_idx)
17229 .cloned()
17230 .unwrap_or(spg_storage::Value::Null);
17231 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
17232 "value",
17233 schema.columns[*col_idx].ty,
17234 schema.columns[*col_idx].nullable,
17235 )];
17236 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
17237 let synth_row = spg_storage::Row {
17238 values: alloc::vec![cell],
17239 };
17240 for (ci, expr) in checks.iter().enumerate() {
17241 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
17242 EngineError::Unsupported(alloc::format!(
17243 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
17244 schema.columns[*col_idx].name
17245 ))
17246 })?;
17247 if matches!(v, spg_storage::Value::Bool(false)) {
17248 return Err(EngineError::Unsupported(alloc::format!(
17249 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
17250 schema.columns[*col_idx].name
17251 )));
17252 }
17253 }
17254 }
17255 }
17256 Ok(())
17257}
17258
17259fn enforce_fk_inserts(
17260 catalog: &Catalog,
17261 child_table: &str,
17262 fks: &[spg_storage::ForeignKeyConstraint],
17263 rows: &[Vec<Value>],
17264) -> Result<(), EngineError> {
17265 for fk in fks {
17266 let parent_is_self = fk.parent_table == child_table;
17267 let parent = if parent_is_self {
17268 catalog.get(child_table).ok_or_else(|| {
17271 EngineError::Storage(StorageError::TableNotFound {
17272 name: child_table.into(),
17273 })
17274 })?
17275 } else {
17276 catalog.get(&fk.parent_table).ok_or_else(|| {
17277 EngineError::Storage(StorageError::TableNotFound {
17278 name: fk.parent_table.clone(),
17279 })
17280 })?
17281 };
17282 for (batch_idx, row_values) in rows.iter().enumerate() {
17283 if fk.local_columns.len() == 1 {
17287 let v = &row_values[fk.local_columns[0]];
17288 if matches!(v, Value::Null) {
17289 continue;
17290 }
17291 let parent_col = fk.parent_columns[0];
17292 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
17293 EngineError::Unsupported(alloc::format!(
17294 "FOREIGN KEY column value of type {:?} is not index-eligible",
17295 v.data_type()
17296 ))
17297 })?;
17298 let present_committed = parent.indices().iter().any(|idx| {
17299 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
17300 && idx.column_position == parent_col
17301 && idx.partial_predicate.is_none()
17302 && !idx.lookup_eq(&key).is_empty()
17303 });
17304 let present_in_batch = parent_is_self
17308 && rows[..batch_idx]
17309 .iter()
17310 .any(|earlier| earlier.get(parent_col) == Some(v));
17311 if !(present_committed || present_in_batch) {
17312 return Err(EngineError::Unsupported(alloc::format!(
17313 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
17314 fk.parent_table,
17315 parent
17316 .schema()
17317 .columns
17318 .get(parent_col)
17319 .map_or("?", |c| c.name.as_str()),
17320 v,
17321 )));
17322 }
17323 } else {
17324 if fk
17328 .local_columns
17329 .iter()
17330 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
17331 {
17332 continue;
17333 }
17334 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
17335 let parent_match_committed = parent.rows().iter().any(|prow| {
17336 fk.parent_columns
17337 .iter()
17338 .enumerate()
17339 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
17340 });
17341 let parent_match_in_batch = parent_is_self
17342 && rows[..batch_idx].iter().any(|earlier| {
17343 fk.parent_columns
17344 .iter()
17345 .enumerate()
17346 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
17347 });
17348 if !(parent_match_committed || parent_match_in_batch) {
17349 return Err(EngineError::Unsupported(alloc::format!(
17350 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
17351 fk.parent_table,
17352 )));
17353 }
17354 }
17355 }
17356 }
17357 Ok(())
17358}
17359
17360#[derive(Debug, Clone)]
17364struct FkChildStep {
17365 child_table: String,
17366 action: FkChildAction,
17367}
17368
17369#[derive(Debug, Clone)]
17370enum FkChildAction {
17371 Delete { positions: Vec<usize> },
17373 SetNull {
17377 positions: Vec<usize>,
17378 columns: Vec<usize>,
17379 },
17380 SetDefault {
17384 positions: Vec<usize>,
17385 columns: Vec<usize>,
17386 defaults: Vec<Value>,
17387 },
17388}
17389
17390fn plan_fk_parent_deletions(
17406 catalog: &Catalog,
17407 parent_table_name: &str,
17408 to_delete_positions: &[usize],
17409 to_delete_rows: &[Vec<Value>],
17410) -> Result<Vec<FkChildStep>, EngineError> {
17411 use alloc::collections::{BTreeMap, BTreeSet};
17412 if to_delete_rows.is_empty() {
17413 return Ok(Vec::new());
17414 }
17415 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
17416 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
17418 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17419 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
17420 for &p in to_delete_positions {
17421 visited.insert((parent_table_name.to_string(), p));
17422 }
17423 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
17424 .iter()
17425 .map(|r| (parent_table_name.to_string(), r.clone()))
17426 .collect();
17427 while let Some((cur_parent, parent_row)) = work.pop() {
17428 for child_name in catalog.table_names() {
17429 let child = catalog
17430 .get(&child_name)
17431 .expect("table_names → catalog.get round-trip is total");
17432 for fk in &child.schema().foreign_keys {
17433 if fk.parent_table != cur_parent {
17434 continue;
17435 }
17436 let parent_key: Vec<&Value> = fk
17437 .parent_columns
17438 .iter()
17439 .map(|&pi| &parent_row[pi])
17440 .collect();
17441 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
17442 continue;
17443 }
17444 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
17445 if child_name == cur_parent
17446 && visited.contains(&(child_name.clone(), child_row_idx))
17447 {
17448 continue;
17449 }
17450 let matches_key = fk
17451 .local_columns
17452 .iter()
17453 .enumerate()
17454 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
17455 if !matches_key {
17456 continue;
17457 }
17458 match fk.on_delete {
17459 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
17460 return Err(EngineError::Unsupported(alloc::format!(
17461 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
17462 restricted by FK from {child_name:?}.{:?}",
17463 fk.local_columns,
17464 )));
17465 }
17466 spg_storage::FkAction::Cascade => {
17467 if visited.insert((child_name.clone(), child_row_idx)) {
17468 delete_plan
17469 .entry(child_name.clone())
17470 .or_default()
17471 .insert(child_row_idx);
17472 work.push((child_name.clone(), child_row.values.clone()));
17473 }
17474 }
17475 spg_storage::FkAction::SetNull => {
17476 for &li in &fk.local_columns {
17478 let col = child.schema().columns.get(li).ok_or_else(|| {
17479 EngineError::Unsupported(alloc::format!(
17480 "FK local column {li} missing in {child_name:?}"
17481 ))
17482 })?;
17483 if !col.nullable {
17484 return Err(EngineError::Unsupported(alloc::format!(
17485 "FOREIGN KEY ON DELETE SET NULL: column \
17486 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
17487 col.name,
17488 )));
17489 }
17490 }
17491 let entry = setnull_plan.entry(child_name.clone()).or_default();
17492 for &li in &fk.local_columns {
17493 entry.insert((child_row_idx, li));
17494 }
17495 }
17496 spg_storage::FkAction::SetDefault => {
17497 let entry = setdefault_plan.entry(child_name.clone()).or_default();
17499 for &li in &fk.local_columns {
17500 let col = child.schema().columns.get(li).ok_or_else(|| {
17501 EngineError::Unsupported(alloc::format!(
17502 "FK local column {li} missing in {child_name:?}"
17503 ))
17504 })?;
17505 let default = col.default.clone().ok_or_else(|| {
17506 EngineError::Unsupported(alloc::format!(
17507 "FOREIGN KEY ON DELETE SET DEFAULT: column \
17508 {child_name:?}.{:?} has no DEFAULT declared",
17509 col.name,
17510 ))
17511 })?;
17512 entry.insert((child_row_idx, li), default);
17513 }
17514 }
17515 }
17516 }
17517 }
17518 }
17519 }
17520 let mut steps: Vec<FkChildStep> = Vec::new();
17528 for (child_table, entries) in setnull_plan {
17529 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
17530 steps.push(FkChildStep {
17531 child_table,
17532 action: FkChildAction::SetNull { positions, columns },
17533 });
17534 }
17535 for (child_table, entries) in setdefault_plan {
17536 let mut positions = Vec::with_capacity(entries.len());
17537 let mut columns = Vec::with_capacity(entries.len());
17538 let mut defaults = Vec::with_capacity(entries.len());
17539 for ((p, c), v) in entries {
17540 positions.push(p);
17541 columns.push(c);
17542 defaults.push(v);
17543 }
17544 steps.push(FkChildStep {
17545 child_table,
17546 action: FkChildAction::SetDefault {
17547 positions,
17548 columns,
17549 defaults,
17550 },
17551 });
17552 }
17553 for (child_table, positions) in delete_plan {
17554 steps.push(FkChildStep {
17555 child_table,
17556 action: FkChildAction::Delete {
17557 positions: positions.into_iter().collect(),
17558 },
17559 });
17560 }
17561 Ok(steps)
17562}
17563
17564fn plan_fk_parent_updates(
17581 catalog: &Catalog,
17582 parent_table_name: &str,
17583 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
17584) -> Result<Vec<FkChildStep>, EngineError> {
17585 use alloc::collections::BTreeMap;
17586 if plan_with_old.is_empty() {
17587 return Ok(Vec::new());
17588 }
17589 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
17594 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
17595 BTreeMap::new();
17596 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17597 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
17599
17600 for child_name in catalog.table_names() {
17601 let child = catalog
17602 .get(&child_name)
17603 .expect("table_names → catalog.get total");
17604 for fk in &child.schema().foreign_keys {
17605 if fk.parent_table != parent_table_name {
17606 continue;
17607 }
17608 for (_pos, old_row, new_row) in plan_with_old {
17609 let key_changed = fk
17611 .parent_columns
17612 .iter()
17613 .any(|&pi| old_row.get(pi) != new_row.get(pi));
17614 if !key_changed {
17615 continue;
17616 }
17617 let old_key: Vec<&Value> =
17619 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
17620 if old_key.iter().any(|v| matches!(v, Value::Null)) {
17621 continue;
17623 }
17624 let new_key: Vec<&Value> =
17625 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
17626 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
17627 if child_name == parent_table_name
17630 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
17631 {
17632 continue;
17633 }
17634 let matches_key = fk
17635 .local_columns
17636 .iter()
17637 .enumerate()
17638 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
17639 if !matches_key {
17640 continue;
17641 }
17642 match fk.on_update {
17643 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
17644 return Err(EngineError::Unsupported(alloc::format!(
17645 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
17646 restricted by FK from {child_name:?}.{:?}",
17647 fk.local_columns,
17648 )));
17649 }
17650 spg_storage::FkAction::Cascade => {
17651 let entry = cascade_plan.entry(child_name.clone()).or_default();
17653 for (i, &li) in fk.local_columns.iter().enumerate() {
17654 entry.insert((child_row_idx, li), new_key[i].clone());
17655 }
17656 }
17657 spg_storage::FkAction::SetNull => {
17658 for &li in &fk.local_columns {
17659 let col = child.schema().columns.get(li).ok_or_else(|| {
17660 EngineError::Unsupported(alloc::format!(
17661 "FK local column {li} missing in {child_name:?}"
17662 ))
17663 })?;
17664 if !col.nullable {
17665 return Err(EngineError::Unsupported(alloc::format!(
17666 "FOREIGN KEY ON UPDATE SET NULL: column \
17667 {child_name:?}.{:?} is NOT NULL",
17668 col.name,
17669 )));
17670 }
17671 }
17672 let entry = setnull_plan.entry(child_name.clone()).or_default();
17673 for &li in &fk.local_columns {
17674 entry.insert((child_row_idx, li));
17675 }
17676 }
17677 spg_storage::FkAction::SetDefault => {
17678 let entry = setdefault_plan.entry(child_name.clone()).or_default();
17679 for &li in &fk.local_columns {
17680 let col = child.schema().columns.get(li).ok_or_else(|| {
17681 EngineError::Unsupported(alloc::format!(
17682 "FK local column {li} missing in {child_name:?}"
17683 ))
17684 })?;
17685 let default = col.default.clone().ok_or_else(|| {
17686 EngineError::Unsupported(alloc::format!(
17687 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
17688 {child_name:?}.{:?} has no DEFAULT",
17689 col.name,
17690 ))
17691 })?;
17692 entry.insert((child_row_idx, li), default);
17693 }
17694 }
17695 }
17696 }
17697 }
17698 }
17699 }
17700 let mut steps: Vec<FkChildStep> = Vec::new();
17703 for (child_table, entries) in cascade_plan {
17704 let mut positions = Vec::with_capacity(entries.len());
17705 let mut columns = Vec::with_capacity(entries.len());
17706 let mut defaults = Vec::with_capacity(entries.len());
17707 for ((p, c), v) in entries {
17708 positions.push(p);
17709 columns.push(c);
17710 defaults.push(v);
17711 }
17712 steps.push(FkChildStep {
17717 child_table,
17718 action: FkChildAction::SetDefault {
17719 positions,
17720 columns,
17721 defaults,
17722 },
17723 });
17724 }
17725 for (child_table, entries) in setnull_plan {
17726 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
17727 steps.push(FkChildStep {
17728 child_table,
17729 action: FkChildAction::SetNull { positions, columns },
17730 });
17731 }
17732 for (child_table, entries) in setdefault_plan {
17733 let mut positions = Vec::with_capacity(entries.len());
17734 let mut columns = Vec::with_capacity(entries.len());
17735 let mut defaults = Vec::with_capacity(entries.len());
17736 for ((p, c), v) in entries {
17737 positions.push(p);
17738 columns.push(c);
17739 defaults.push(v);
17740 }
17741 steps.push(FkChildStep {
17742 child_table,
17743 action: FkChildAction::SetDefault {
17744 positions,
17745 columns,
17746 defaults,
17747 },
17748 });
17749 }
17750 let _ = delete_plan; Ok(steps)
17752}
17753
17754fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
17758 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
17759 EngineError::Storage(StorageError::TableNotFound {
17760 name: step.child_table.clone(),
17761 })
17762 })?;
17763 match &step.action {
17764 FkChildAction::Delete { positions } => {
17765 let _ = child.delete_rows(positions);
17766 }
17767 FkChildAction::SetNull { positions, columns } => {
17768 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
17769 }
17770 FkChildAction::SetDefault {
17771 positions,
17772 columns,
17773 defaults,
17774 } => {
17775 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
17776 }
17777 }
17778 Ok(())
17779}
17780
17781fn apply_per_cell_writes(
17787 child: &mut spg_storage::Table,
17788 positions: &[usize],
17789 columns: &[usize],
17790 mut value_for: impl FnMut(usize) -> Value,
17791) -> Result<(), EngineError> {
17792 use alloc::collections::BTreeMap;
17793 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
17794 for i in 0..positions.len() {
17795 by_row
17796 .entry(positions[i])
17797 .or_default()
17798 .push((columns[i], value_for(i)));
17799 }
17800 for (pos, mutations) in by_row {
17801 let mut new_values = child.rows()[pos].values.clone();
17802 for (col, v) in mutations {
17803 if let Some(slot) = new_values.get_mut(col) {
17804 *slot = v;
17805 }
17806 }
17807 child
17808 .update_row(pos, new_values)
17809 .map_err(EngineError::Storage)?;
17810 }
17811 Ok(())
17812}
17813
17814fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
17815 match a {
17816 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
17817 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
17818 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
17819 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
17820 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
17821 }
17822}
17823
17824fn resolve_column_default_free(
17830 col: &ColumnSchema,
17831 clock_fn: Option<ClockFn>,
17832) -> Result<Value, EngineError> {
17833 if let Some(rt) = &col.runtime_default {
17834 return eval_runtime_default_free(rt, col.ty, clock_fn);
17835 }
17836 Ok(col.default.clone().unwrap_or(Value::Null))
17837}
17838
17839fn eval_runtime_default_free(
17840 rt: &str,
17841 ty: DataType,
17842 clock_fn: Option<ClockFn>,
17843) -> Result<Value, EngineError> {
17844 let s = rt.trim().to_ascii_lowercase();
17845 let with_no_parens = s.trim_end_matches("()");
17851 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
17852 if with_no_parens.ends_with(')') {
17853 &with_no_parens[..open_idx]
17854 } else {
17855 with_no_parens
17856 }
17857 } else {
17858 with_no_parens
17859 };
17860 let now_us = match clock_fn {
17861 Some(f) => f(),
17862 None => 0,
17863 };
17864 let v = match canonical {
17865 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
17866 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
17867 "current_time" | "localtime" => Value::Timestamp(now_us),
17868 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
17874 other => {
17875 return Err(EngineError::Unsupported(alloc::format!(
17876 "runtime DEFAULT expression {other:?} not supported \
17877 (v7.17.0 whitelist: now() / current_timestamp / \
17878 current_date / current_time / localtimestamp / \
17879 localtime / gen_random_uuid() / \
17880 uuid_generate_v4())"
17881 )));
17882 }
17883 };
17884 coerce_value(v, ty, "DEFAULT", 0)
17885}
17886
17887fn is_runtime_default_expr(expr: &Expr) -> bool {
17893 match expr {
17894 Expr::FunctionCall { .. } => true,
17895 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
17896 _ => false,
17897 }
17898}
17899
17900fn canonicalize_set_value(
17913 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17914 col_idx: usize,
17915 col_name: &str,
17916 value: Value,
17917) -> Result<Value, EngineError> {
17918 let Some(variants) = lookup.get(&col_idx) else {
17919 return Ok(value);
17920 };
17921 match value {
17922 Value::Null => Ok(Value::Null),
17923 Value::Text(s) => {
17924 if s.is_empty() {
17925 return Ok(Value::Text(alloc::string::String::new()));
17926 }
17927 let mut present = alloc::vec![false; variants.len()];
17930 for raw in s.split(',') {
17931 let tok = raw.trim();
17932 if tok.is_empty() {
17933 continue;
17934 }
17935 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
17936 EngineError::Unsupported(alloc::format!(
17937 "column {col_name:?}: invalid SET token {tok:?}; \
17938 allowed: {variants:?}"
17939 ))
17940 })?;
17941 present[idx] = true;
17942 }
17943 let mut out = alloc::string::String::new();
17945 let mut first = true;
17946 for (i, keep) in present.iter().enumerate() {
17947 if !keep {
17948 continue;
17949 }
17950 if !first {
17951 out.push(',');
17952 }
17953 first = false;
17954 out.push_str(&variants[i]);
17955 }
17956 Ok(Value::Text(out))
17957 }
17958 other => Err(EngineError::Unsupported(alloc::format!(
17959 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
17960 other.data_type()
17961 ))),
17962 }
17963}
17964
17965fn enforce_enum_label(
17966 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17967 col_idx: usize,
17968 col_name: &str,
17969 value: &Value,
17970) -> Result<(), EngineError> {
17971 if let Some(labels) = lookup.get(&col_idx) {
17972 match value {
17973 Value::Null => Ok(()),
17974 Value::Text(s) => {
17975 if labels.iter().any(|l| l == s) {
17976 Ok(())
17977 } else {
17978 Err(EngineError::Unsupported(alloc::format!(
17979 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
17980 )))
17981 }
17982 }
17983 other => Err(EngineError::Unsupported(alloc::format!(
17984 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
17985 other.data_type()
17986 ))),
17987 }
17988 } else {
17989 Ok(())
17990 }
17991}
17992
17993fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
17994 let ty = column_type_to_data_type(c.ty);
17995 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
17996 if let Some(name) = c.user_type_ref {
18003 schema.user_enum_type = Some(name);
18004 }
18005 if let Some(expr) = c.on_update_runtime {
18008 schema.on_update_runtime = Some(alloc::format!("{expr}"));
18009 }
18010 schema.collation = match c.collation {
18014 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
18015 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
18016 };
18017 schema.is_unsigned = c.is_unsigned;
18020 schema.inline_enum_variants = c.inline_enum_variants;
18024 schema.inline_set_variants = c.inline_set_variants;
18028 if let Some(default_expr) = c.default {
18029 if is_runtime_default_expr(&default_expr) {
18035 let display = alloc::format!("{default_expr}");
18036 schema = schema.with_runtime_default(display);
18037 } else {
18038 let raw = literal_expr_to_value(default_expr)?;
18039 let coerced = coerce_value(raw, ty, &c.name, 0)?;
18040 schema = schema.with_default(coerced);
18041 }
18042 }
18043 if c.auto_increment {
18044 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
18046 return Err(EngineError::Unsupported(alloc::format!(
18047 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
18048 )));
18049 }
18050 schema = schema.with_auto_increment();
18051 }
18052 Ok(schema)
18053}
18054
18055fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
18060 let s = s.trim();
18061 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
18062 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
18064 if cleaned.len() % 2 != 0 {
18065 return Err("odd-length hex literal");
18066 }
18067 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
18068 let cleaned_bytes = cleaned.as_bytes();
18069 for i in (0..cleaned_bytes.len()).step_by(2) {
18070 let hi = hex_nibble(cleaned_bytes[i])?;
18071 let lo = hex_nibble(cleaned_bytes[i + 1])?;
18072 out.push((hi << 4) | lo);
18073 }
18074 return Ok(out);
18075 }
18076 let bytes = s.as_bytes();
18079 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
18080 let mut i = 0;
18081 while i < bytes.len() {
18082 let b = bytes[i];
18083 if b == b'\\' && i + 1 < bytes.len() {
18084 let n = bytes[i + 1];
18085 if n == b'\\' {
18086 out.push(b'\\');
18087 i += 2;
18088 continue;
18089 }
18090 if n.is_ascii_digit()
18091 && i + 3 < bytes.len()
18092 && bytes[i + 2].is_ascii_digit()
18093 && bytes[i + 3].is_ascii_digit()
18094 {
18095 let oct = |x: u8| (x - b'0') as u32;
18096 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
18097 if v <= 0xFF {
18098 out.push(v as u8);
18099 i += 4;
18100 continue;
18101 }
18102 }
18103 }
18104 out.push(b);
18105 i += 1;
18106 }
18107 Ok(out)
18108}
18109
18110fn hex_nibble(b: u8) -> Result<u8, &'static str> {
18111 match b {
18112 b'0'..=b'9' => Ok(b - b'0'),
18113 b'a'..=b'f' => Ok(b - b'a' + 10),
18114 b'A'..=b'F' => Ok(b - b'A' + 10),
18115 _ => Err("invalid hex digit"),
18116 }
18117}
18118
18119fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
18133 let mut has_text = false;
18134 let mut has_bigint = false;
18135 let mut has_int = false;
18136 for v in &items {
18137 match v {
18138 Value::Null => {}
18139 Value::Text(_) | Value::Json(_) => has_text = true,
18140 Value::BigInt(_) => has_bigint = true,
18141 Value::Int(_) | Value::SmallInt(_) => has_int = true,
18142 _ => has_text = true,
18143 }
18144 }
18145 if has_text || (!has_bigint && !has_int) {
18146 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
18147 .into_iter()
18148 .map(|v| match v {
18149 Value::Null => None,
18150 Value::Text(s) | Value::Json(s) => Some(s),
18151 other => Some(alloc::format!("{other:?}")),
18152 })
18153 .collect();
18154 return Value::TextArray(out);
18155 }
18156 if has_bigint {
18157 let out: alloc::vec::Vec<Option<i64>> = items
18158 .into_iter()
18159 .map(|v| match v {
18160 Value::Null => None,
18161 Value::Int(n) => Some(i64::from(n)),
18162 Value::SmallInt(n) => Some(i64::from(n)),
18163 Value::BigInt(n) => Some(n),
18164 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
18165 })
18166 .collect();
18167 return Value::BigIntArray(out);
18168 }
18169 let out: alloc::vec::Vec<Option<i32>> = items
18170 .into_iter()
18171 .map(|v| match v {
18172 Value::Null => None,
18173 Value::Int(n) => Some(n),
18174 Value::SmallInt(n) => Some(i32::from(n)),
18175 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
18176 })
18177 .collect();
18178 Value::IntArray(out)
18179}
18180
18181fn decode_text_array_literal(
18182 s: &str,
18183) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
18184 let trimmed = s.trim();
18185 let inner = trimmed
18186 .strip_prefix('{')
18187 .and_then(|x| x.strip_suffix('}'))
18188 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
18189 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
18190 if inner.trim().is_empty() {
18191 return Ok(out);
18192 }
18193 let bytes = inner.as_bytes();
18194 let mut i = 0;
18195 while i <= bytes.len() {
18196 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
18198 i += 1;
18199 }
18200 if i < bytes.len() && bytes[i] == b'"' {
18202 i += 1; let mut buf = alloc::string::String::new();
18204 while i < bytes.len() && bytes[i] != b'"' {
18205 if bytes[i] == b'\\' && i + 1 < bytes.len() {
18206 buf.push(bytes[i + 1] as char);
18207 i += 2;
18208 } else {
18209 buf.push(bytes[i] as char);
18210 i += 1;
18211 }
18212 }
18213 if i >= bytes.len() {
18214 return Err("unterminated quoted element");
18215 }
18216 i += 1; out.push(Some(buf));
18218 } else {
18219 let start = i;
18221 while i < bytes.len() && bytes[i] != b',' {
18222 i += 1;
18223 }
18224 let raw = inner[start..i].trim();
18225 if raw.eq_ignore_ascii_case("NULL") {
18226 out.push(None);
18227 } else {
18228 out.push(Some(alloc::string::ToString::to_string(raw)));
18229 }
18230 }
18231 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
18233 i += 1;
18234 }
18235 if i >= bytes.len() {
18236 break;
18237 }
18238 if bytes[i] != b',' {
18239 return Err("expected ',' between TEXT[] elements");
18240 }
18241 i += 1;
18242 }
18243 Ok(out)
18244}
18245
18246fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
18251 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
18252 out.push('{');
18253 for (i, item) in items.iter().enumerate() {
18254 if i > 0 {
18255 out.push(',');
18256 }
18257 match item {
18258 None => out.push_str("NULL"),
18259 Some(s) => {
18260 let needs_quote = s.is_empty()
18261 || s.eq_ignore_ascii_case("NULL")
18262 || s.chars()
18263 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
18264 if needs_quote {
18265 out.push('"');
18266 for c in s.chars() {
18267 if c == '"' || c == '\\' {
18268 out.push('\\');
18269 }
18270 out.push(c);
18271 }
18272 out.push('"');
18273 } else {
18274 out.push_str(s);
18275 }
18276 }
18277 }
18278 }
18279 out.push('}');
18280 out
18281}
18282
18283fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
18287 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
18288 out.push_str("\\x");
18289 for byte in b {
18290 let hi = byte >> 4;
18291 let lo = byte & 0x0F;
18292 out.push(hex_digit(hi));
18293 out.push(hex_digit(lo));
18294 }
18295 out
18296}
18297
18298const fn hex_digit(n: u8) -> char {
18299 match n {
18300 0..=9 => (b'0' + n) as char,
18301 10..=15 => (b'a' + n - 10) as char,
18302 _ => '?',
18303 }
18304}
18305
18306fn parse_hstore_str(
18318 s: &str,
18319) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
18320 let bytes = s.as_bytes();
18321 let mut i = 0;
18322 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
18323 let skip_ws = |bytes: &[u8], i: &mut usize| {
18324 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
18325 *i += 1;
18326 }
18327 };
18328 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
18329 if *i >= bytes.len() {
18330 return None;
18331 }
18332 if bytes[*i] == b'"' {
18333 *i += 1;
18334 let mut out = alloc::string::String::new();
18335 while *i < bytes.len() {
18336 match bytes[*i] {
18337 b'"' => {
18338 *i += 1;
18339 return Some(out);
18340 }
18341 b'\\' if *i + 1 < bytes.len() => {
18342 out.push(bytes[*i + 1] as char);
18343 *i += 2;
18344 }
18345 c => {
18346 out.push(c as char);
18347 *i += 1;
18348 }
18349 }
18350 }
18351 None
18352 } else {
18353 let start = *i;
18354 while *i < bytes.len()
18355 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
18356 {
18357 *i += 1;
18358 }
18359 if *i == start {
18360 return None;
18361 }
18362 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
18363 }
18364 };
18365 skip_ws(bytes, &mut i);
18366 while i < bytes.len() {
18367 let key = parse_token(bytes, &mut i)?;
18368 skip_ws(bytes, &mut i);
18369 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
18370 return None;
18371 }
18372 i += 2;
18373 skip_ws(bytes, &mut i);
18374 let val_token = if i + 4 <= bytes.len()
18376 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
18377 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
18378 {
18379 i += 4;
18380 None
18381 } else {
18382 Some(parse_token(bytes, &mut i)?)
18383 };
18384 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
18386 out[pos] = (key, val_token);
18387 } else {
18388 out.push((key, val_token));
18389 }
18390 skip_ws(bytes, &mut i);
18391 if i >= bytes.len() {
18392 break;
18393 }
18394 if bytes[i] == b',' {
18395 i += 1;
18396 skip_ws(bytes, &mut i);
18397 continue;
18398 }
18399 return None;
18400 }
18401 Some(out)
18402}
18403
18404fn format_hstore_str(
18408 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
18409) -> alloc::string::String {
18410 let mut out = alloc::string::String::new();
18411 for (i, (k, v)) in pairs.iter().enumerate() {
18412 if i > 0 {
18413 out.push_str(", ");
18414 }
18415 out.push('"');
18416 out.push_str(k);
18417 out.push_str("\"=>");
18418 match v {
18419 None => out.push_str("NULL"),
18420 Some(val) => {
18421 out.push('"');
18422 out.push_str(val);
18423 out.push('"');
18424 }
18425 }
18426 }
18427 out
18428}
18429
18430pub fn format_hstore_text(
18433 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
18434) -> alloc::string::String {
18435 format_hstore_str(pairs)
18436}
18437
18438fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
18443 let s = s.trim();
18444 let outer = s
18445 .strip_prefix('{')
18446 .and_then(|x| x.strip_suffix('}'))
18447 .ok_or("missing outer '{...}' braces")?;
18448 let trimmed = outer.trim();
18449 if trimmed.is_empty() {
18450 return Ok(Vec::new());
18451 }
18452 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
18453 let mut i = 0;
18454 let bytes = trimmed.as_bytes();
18455 while i < bytes.len() {
18456 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
18457 i += 1;
18458 }
18459 if i >= bytes.len() {
18460 break;
18461 }
18462 if bytes[i] != b'{' {
18463 return Err("expected '{' opening a row");
18464 }
18465 i += 1;
18466 let row_start = i;
18467 let mut depth = 1;
18468 while i < bytes.len() && depth > 0 {
18469 match bytes[i] {
18470 b'{' => depth += 1,
18471 b'}' => depth -= 1,
18472 _ => {}
18473 }
18474 if depth > 0 {
18475 i += 1;
18476 }
18477 }
18478 if depth != 0 {
18479 return Err("unbalanced '{...}' in row");
18480 }
18481 let row_text = &trimmed[row_start..i];
18482 i += 1;
18483 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
18484 Vec::new()
18485 } else {
18486 row_text.split(',').map(|t| t.trim().to_string()).collect()
18487 };
18488 rows.push(cells);
18489 }
18490 if let Some(first) = rows.first() {
18491 let cols = first.len();
18492 for r in &rows {
18493 if r.len() != cols {
18494 return Err("ragged 2D array (rows have different column counts)");
18495 }
18496 }
18497 }
18498 Ok(rows)
18499}
18500
18501fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
18502 let raw = split_2d_literal(s)?;
18503 raw.into_iter()
18504 .map(|row| {
18505 row.into_iter()
18506 .map(|cell| {
18507 if cell.eq_ignore_ascii_case("NULL") {
18508 Ok(None)
18509 } else {
18510 cell.parse::<i32>()
18511 .map(Some)
18512 .map_err(|_| "invalid int element")
18513 }
18514 })
18515 .collect()
18516 })
18517 .collect()
18518}
18519
18520fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
18521 let raw = split_2d_literal(s)?;
18522 raw.into_iter()
18523 .map(|row| {
18524 row.into_iter()
18525 .map(|cell| {
18526 if cell.eq_ignore_ascii_case("NULL") {
18527 Ok(None)
18528 } else {
18529 cell.parse::<i64>()
18530 .map(Some)
18531 .map_err(|_| "invalid bigint element")
18532 }
18533 })
18534 .collect()
18535 })
18536 .collect()
18537}
18538
18539fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
18540 let raw = split_2d_literal(s)?;
18541 Ok(raw
18542 .into_iter()
18543 .map(|row| {
18544 row.into_iter()
18545 .map(|cell| {
18546 if cell.eq_ignore_ascii_case("NULL") {
18547 None
18548 } else {
18549 Some(cell.trim_matches('"').to_string())
18550 }
18551 })
18552 .collect()
18553 })
18554 .collect())
18555}
18556
18557fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
18558 let mut out = alloc::string::String::from("{");
18559 for (i, row) in rows.iter().enumerate() {
18560 if i > 0 {
18561 out.push(',');
18562 }
18563 out.push('{');
18564 for (j, cell) in row.iter().enumerate() {
18565 if j > 0 {
18566 out.push(',');
18567 }
18568 match cell {
18569 None => out.push_str("NULL"),
18570 Some(n) => out.push_str(&alloc::format!("{n}")),
18571 }
18572 }
18573 out.push('}');
18574 }
18575 out.push('}');
18576 out
18577}
18578
18579fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
18580 let mut out = alloc::string::String::from("{");
18581 for (i, row) in rows.iter().enumerate() {
18582 if i > 0 {
18583 out.push(',');
18584 }
18585 out.push('{');
18586 for (j, cell) in row.iter().enumerate() {
18587 if j > 0 {
18588 out.push(',');
18589 }
18590 match cell {
18591 None => out.push_str("NULL"),
18592 Some(n) => out.push_str(&alloc::format!("{n}")),
18593 }
18594 }
18595 out.push('}');
18596 }
18597 out.push('}');
18598 out
18599}
18600
18601fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
18602 let mut out = alloc::string::String::from("{");
18603 for (i, row) in rows.iter().enumerate() {
18604 if i > 0 {
18605 out.push(',');
18606 }
18607 out.push('{');
18608 for (j, cell) in row.iter().enumerate() {
18609 if j > 0 {
18610 out.push(',');
18611 }
18612 match cell {
18613 None => out.push_str("NULL"),
18614 Some(s) => out.push_str(s),
18615 }
18616 }
18617 out.push('}');
18618 }
18619 out.push('}');
18620 out
18621}
18622
18623pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
18626 format_int_2d_text(rows)
18627}
18628pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
18629 format_bigint_2d_text(rows)
18630}
18631pub fn format_text_2d_text_pub(
18632 rows: &[Vec<Option<alloc::string::String>>],
18633) -> alloc::string::String {
18634 format_text_2d_text(rows)
18635}
18636
18637fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
18642 let s = s.trim();
18643 if s.eq_ignore_ascii_case("empty") {
18644 return Some(Value::Range {
18645 kind,
18646 lower: None,
18647 upper: None,
18648 lower_inc: false,
18649 upper_inc: false,
18650 empty: true,
18651 });
18652 }
18653 let bytes = s.as_bytes();
18654 if bytes.len() < 3 {
18655 return None;
18656 }
18657 let lower_inc = match bytes[0] {
18658 b'[' => true,
18659 b'(' => false,
18660 _ => return None,
18661 };
18662 let upper_inc = match bytes[bytes.len() - 1] {
18663 b']' => true,
18664 b')' => false,
18665 _ => return None,
18666 };
18667 let inner = &s[1..s.len() - 1];
18668 let (lo_text, up_text) = inner.split_once(',')?;
18669 let lower = if lo_text.is_empty() {
18670 None
18671 } else {
18672 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
18673 };
18674 let upper = if up_text.is_empty() {
18675 None
18676 } else {
18677 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
18678 };
18679 Some(Value::Range {
18680 kind,
18681 lower,
18682 upper,
18683 lower_inc,
18684 upper_inc,
18685 empty: false,
18686 })
18687}
18688
18689fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
18692 let text = text.trim().trim_matches('"');
18693 use spg_storage::RangeKind as K;
18694 match kind {
18695 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
18696 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
18697 K::Num => {
18698 let dot = text.find('.');
18701 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
18702 let digits: alloc::string::String = text
18703 .chars()
18704 .filter(|c| *c == '-' || c.is_ascii_digit())
18705 .collect();
18706 let scaled: i128 = digits.parse().ok()?;
18707 Some(Value::Numeric { scaled, scale })
18708 }
18709 K::Ts | K::TsTz => {
18710 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
18715 }
18716 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
18717 }
18718}
18719
18720pub fn format_range_text(v: &Value) -> alloc::string::String {
18724 format_range_str(v)
18725}
18726
18727fn format_range_str(v: &Value) -> alloc::string::String {
18728 let Value::Range {
18729 lower,
18730 upper,
18731 lower_inc,
18732 upper_inc,
18733 empty,
18734 ..
18735 } = v
18736 else {
18737 return alloc::string::String::new();
18738 };
18739 if *empty {
18740 return "empty".into();
18741 }
18742 let mut out = alloc::string::String::new();
18743 out.push(if *lower_inc { '[' } else { '(' });
18744 if let Some(l) = lower {
18745 out.push_str(&format_range_element(l));
18746 }
18747 out.push(',');
18748 if let Some(u) = upper {
18749 out.push_str(&format_range_element(u));
18750 }
18751 out.push(if *upper_inc { ']' } else { ')' });
18752 out
18753}
18754
18755fn format_range_element(v: &Value) -> alloc::string::String {
18756 match v {
18757 Value::Int(n) => alloc::format!("{n}"),
18758 Value::BigInt(n) => alloc::format!("{n}"),
18759 Value::Date(d) => crate::eval::format_date(*d),
18760 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
18761 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
18762 other => alloc::format!("{other:?}"),
18763 }
18764}
18765
18766fn parse_money_str(s: &str) -> Option<i64> {
18777 let s = s.trim();
18778 let (neg, rest) = match s.strip_prefix('-') {
18779 Some(r) => (true, r.trim_start()),
18780 None => (false, s),
18781 };
18782 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
18783 let (int_part, frac_part) = match rest.split_once('.') {
18784 Some((i, f)) => (i, Some(f)),
18785 None => (rest, None),
18786 };
18787 if int_part.is_empty() {
18788 return None;
18789 }
18790 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
18792 for b in int_part.bytes() {
18793 match b {
18794 b',' => {}
18795 b'0'..=b'9' => int_digits.push(b as char),
18796 _ => return None,
18797 }
18798 }
18799 if int_digits.is_empty() {
18800 return None;
18801 }
18802 let dollars: i64 = int_digits.parse().ok()?;
18803 let cents: i64 = match frac_part {
18804 None => 0,
18805 Some(f) => {
18806 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
18807 return None;
18808 }
18809 let padded = if f.len() == 1 {
18810 alloc::format!("{f}0")
18811 } else {
18812 f.to_string()
18813 };
18814 padded.parse().ok()?
18815 }
18816 };
18817 let total = dollars.checked_mul(100)?.checked_add(cents)?;
18818 Some(if neg { -total } else { total })
18819}
18820
18821fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
18832 let s = s.trim();
18833 let bytes = s.as_bytes();
18837 let sign_pos = bytes
18838 .iter()
18839 .enumerate()
18840 .rev()
18841 .find(|&(_, &b)| b == b'+' || b == b'-')
18842 .map(|(i, _)| i)?;
18843 if sign_pos == 0 {
18844 return None; }
18846 let time_part = &s[..sign_pos];
18847 let offset_part = &s[sign_pos..];
18848 let us = parse_time_str(time_part)?;
18849 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
18850 let offset_body = &offset_part[1..];
18851 let (hh_str, mm_str) = match offset_body.split_once(':') {
18852 Some((h, m)) => (h, m),
18853 None => (offset_body, "0"),
18854 };
18855 let hh: i32 = hh_str.parse().ok()?;
18856 let mm: i32 = mm_str.parse().ok()?;
18857 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
18858 return None;
18859 }
18860 let total = sign * (hh * 3600 + mm * 60);
18861 if total.abs() > 50_400 {
18862 return None;
18863 }
18864 Some((us, total))
18865}
18866
18867fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
18872 if n == 0 || (1901..=2155).contains(&n) {
18873 return Ok(Value::Year(n as u16));
18876 }
18877 Err(EngineError::Eval(EvalError::TypeMismatch {
18878 detail: alloc::format!(
18879 "year value out of range: {n} (column `{col_name}`; \
18880 MySQL accepts 0 or 1901..=2155)"
18881 ),
18882 }))
18883}
18884
18885fn parse_time_str(s: &str) -> Option<i64> {
18897 let s = s.trim();
18898 let (hms, frac) = match s.split_once('.') {
18899 Some((h, f)) => (h, Some(f)),
18900 None => (s, None),
18901 };
18902 let mut parts = hms.split(':');
18903 let hh: u32 = parts.next()?.parse().ok()?;
18904 let mm: u32 = parts.next()?.parse().ok()?;
18905 let ss: u32 = parts.next()?.parse().ok()?;
18906 if parts.next().is_some() {
18907 return None;
18908 }
18909 if hh > 23 || mm > 59 || ss > 59 {
18910 return None;
18911 }
18912 let frac_us: i64 = match frac {
18913 None => 0,
18914 Some(f) => {
18915 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
18916 return None;
18917 }
18918 let mut padded = alloc::string::String::with_capacity(6);
18920 padded.push_str(f);
18921 while padded.len() < 6 {
18922 padded.push('0');
18923 }
18924 padded.parse().ok()?
18925 }
18926 };
18927 Some(
18928 i64::from(hh) * 3_600_000_000
18929 + i64::from(mm) * 60_000_000
18930 + i64::from(ss) * 1_000_000
18931 + frac_us,
18932 )
18933}
18934
18935const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
18936 match t {
18937 ColumnTypeName::SmallInt => DataType::SmallInt,
18938 ColumnTypeName::Int => DataType::Int,
18939 ColumnTypeName::BigInt => DataType::BigInt,
18940 ColumnTypeName::Float => DataType::Float,
18941 ColumnTypeName::Text => DataType::Text,
18942 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
18943 ColumnTypeName::Char(n) => DataType::Char(n),
18944 ColumnTypeName::Bool => DataType::Bool,
18945 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
18946 dim,
18947 encoding: match encoding {
18948 SqlVecEncoding::F32 => VecEncoding::F32,
18949 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
18950 SqlVecEncoding::F16 => VecEncoding::F16,
18951 },
18952 },
18953 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
18954 ColumnTypeName::Date => DataType::Date,
18955 ColumnTypeName::Timestamp => DataType::Timestamp,
18956 ColumnTypeName::Timestamptz => DataType::Timestamptz,
18957 ColumnTypeName::Json => DataType::Json,
18958 ColumnTypeName::Jsonb => DataType::Jsonb,
18959 ColumnTypeName::Bytes => DataType::Bytes,
18960 ColumnTypeName::TextArray => DataType::TextArray,
18961 ColumnTypeName::IntArray => DataType::IntArray,
18962 ColumnTypeName::BigIntArray => DataType::BigIntArray,
18963 ColumnTypeName::TsVector => DataType::TsVector,
18964 ColumnTypeName::TsQuery => DataType::TsQuery,
18965 ColumnTypeName::Uuid => DataType::Uuid,
18966 ColumnTypeName::Time => DataType::Time,
18967 ColumnTypeName::Year => DataType::Year,
18968 ColumnTypeName::TimeTz => DataType::TimeTz,
18969 ColumnTypeName::Money => DataType::Money,
18970 ColumnTypeName::Range(k) => DataType::Range(match k {
18971 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
18972 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
18973 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
18974 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
18975 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
18976 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
18977 }),
18978 ColumnTypeName::Hstore => DataType::Hstore,
18979 ColumnTypeName::IntArray2D => DataType::IntArray2D,
18980 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
18981 ColumnTypeName::TextArray2D => DataType::TextArray2D,
18982 }
18983}
18984
18985fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
18989 match expr {
18990 Expr::Literal(l) => Ok(literal_to_value(l)),
18991 Expr::Cast { expr, target } => {
18992 let inner_value = literal_expr_to_value(*expr)?;
18993 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
18994 }
18995 Expr::Unary {
18996 op: UnOp::Neg,
18997 expr,
18998 } => match *expr {
18999 Expr::Literal(Literal::Integer(n)) => {
19000 let neg = n.checked_neg().ok_or_else(|| {
19003 EngineError::Unsupported("integer literal overflow on negation".into())
19004 })?;
19005 Ok(int_value_for(neg))
19006 }
19007 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
19008 other => Err(EngineError::Unsupported(alloc::format!(
19009 "unary minus over non-literal expression: {other:?}"
19010 ))),
19011 },
19012 Expr::Array(items) => {
19020 let mut materialised: alloc::vec::Vec<Value> =
19021 alloc::vec::Vec::with_capacity(items.len());
19022 for elem in items {
19023 materialised.push(literal_expr_to_value(elem)?);
19024 }
19025 Ok(array_literal_widen(materialised))
19026 }
19027 other => {
19040 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
19041 let ctx = EvalContext::new(&empty_schema, None);
19042 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
19043 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
19044 }
19045 }
19046}
19047
19048fn literal_to_value(l: Literal) -> Value {
19049 match l {
19050 Literal::Integer(n) => int_value_for(n),
19051 Literal::Float(x) => Value::Float(x),
19052 Literal::String(s) => Value::Text(s),
19053 Literal::Bool(b) => Value::Bool(b),
19054 Literal::Null => Value::Null,
19055 Literal::Vector(v) => Value::Vector(v),
19056 Literal::TextArray(items) => Value::TextArray(items),
19057 Literal::IntArray(items) => Value::IntArray(items),
19058 Literal::BigIntArray(items) => Value::BigIntArray(items),
19059 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
19060 }
19061}
19062
19063fn int_value_for(n: i64) -> Value {
19067 if let Ok(small) = i32::try_from(n) {
19068 Value::Int(small)
19069 } else {
19070 Value::BigInt(n)
19071 }
19072}
19073
19074#[allow(clippy::too_many_lines)]
19080fn check_unsigned_range(
19085 v: &Value,
19086 schema: &ColumnSchema,
19087 position: usize,
19088) -> Result<(), EngineError> {
19089 if !schema.is_unsigned {
19090 return Ok(());
19091 }
19092 let n = match v {
19093 Value::SmallInt(x) => i64::from(*x),
19094 Value::Int(x) => i64::from(*x),
19095 Value::BigInt(x) => *x,
19096 _ => return Ok(()), };
19098 if n < 0 {
19099 return Err(EngineError::Unsupported(alloc::format!(
19100 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
19101 schema.name
19102 )));
19103 }
19104 Ok(())
19105}
19106
19107fn coerce_value(
19108 v: Value,
19109 expected: DataType,
19110 col_name: &str,
19111 position: usize,
19112) -> Result<Value, EngineError> {
19113 if v.is_null() {
19114 return Ok(Value::Null);
19115 }
19116 let actual = v.data_type().expect("non-null");
19117 if actual == expected {
19118 return Ok(v);
19119 }
19120 let coerced = match (v, expected) {
19121 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
19122 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
19123 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
19124 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
19125 i128::from(n),
19126 precision,
19127 scale,
19128 col_name,
19129 )?),
19130 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
19131 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
19132 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
19133 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
19134 i128::from(n),
19135 precision,
19136 scale,
19137 col_name,
19138 )?),
19139 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
19140 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
19141 #[allow(clippy::cast_precision_loss)]
19142 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
19143 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
19144 i128::from(n),
19145 precision,
19146 scale,
19147 col_name,
19148 )?),
19149 (Value::Float(x), DataType::Numeric { precision, scale }) => {
19150 Some(numeric_from_float(x, precision, scale, col_name)?)
19151 }
19152 (Value::Text(s), DataType::Numeric { precision, scale }) => {
19163 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
19164 return Err(EngineError::Eval(EvalError::TypeMismatch {
19165 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
19166 }));
19167 };
19168 Some(numeric_rescale(
19169 mantissa, src_scale, precision, scale, col_name,
19170 )?)
19171 }
19172 (Value::Text(s), DataType::Date) => {
19174 let d = eval::parse_date_literal(&s).ok_or_else(|| {
19175 EngineError::Eval(EvalError::TypeMismatch {
19176 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
19177 })
19178 })?;
19179 Some(Value::Date(d))
19180 }
19181 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
19188 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
19189 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
19190 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
19191 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
19192 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
19193 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
19194 _ => None,
19195 },
19196 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19205 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19206 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
19207 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
19211 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
19212 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
19220 (Value::Text(s), DataType::Bytes) => {
19227 let bytes = decode_bytea_literal(&s).map_err(|e| {
19228 EngineError::Eval(EvalError::TypeMismatch {
19229 detail: alloc::format!(
19230 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
19231 ),
19232 })
19233 })?;
19234 Some(Value::Bytes(bytes))
19235 }
19236 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
19240 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
19248 Some(b) => Some(Value::Uuid(b)),
19249 None => {
19250 return Err(EngineError::Eval(EvalError::TypeMismatch {
19251 detail: alloc::format!(
19252 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
19253 ),
19254 }));
19255 }
19256 },
19257 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
19262 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
19268 Some(us) => Some(Value::Time(us)),
19269 None => {
19270 return Err(EngineError::Eval(EvalError::TypeMismatch {
19271 detail: alloc::format!(
19272 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
19273 ),
19274 }));
19275 }
19276 },
19277 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
19279 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
19284 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
19285 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
19286 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
19290 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
19291 Err(_) => {
19292 return Err(EngineError::Eval(EvalError::TypeMismatch {
19293 detail: alloc::format!(
19294 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
19295 ),
19296 }));
19297 }
19298 },
19299 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
19301 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
19305 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
19306 None => {
19307 return Err(EngineError::Eval(EvalError::TypeMismatch {
19308 detail: alloc::format!(
19309 "invalid input syntax for type time with time zone: \
19310 {s:?} (column `{col_name}`)"
19311 ),
19312 }));
19313 }
19314 },
19315 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
19317 Some(Value::Text(eval::format_timetz(us, offset_secs)))
19318 }
19319 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
19323 Some(c) => Some(Value::Money(c)),
19324 None => {
19325 return Err(EngineError::Eval(EvalError::TypeMismatch {
19326 detail: alloc::format!(
19327 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
19328 ),
19329 }));
19330 }
19331 },
19332 (Value::SmallInt(n), DataType::Money) => {
19336 Some(Value::Money(i64::from(n).saturating_mul(100)))
19337 }
19338 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
19339 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
19340 (Value::Float(x), DataType::Money) => {
19341 let scaled = x * 100.0;
19344 let cents = if scaled >= 0.0 {
19345 (scaled + 0.5) as i64
19346 } else {
19347 (scaled - 0.5) as i64
19348 };
19349 Some(Value::Money(cents))
19350 }
19351 (Value::Numeric { scaled, scale }, DataType::Money) => {
19352 let cents = if scale == 2 {
19355 scaled
19356 } else if scale < 2 {
19357 let mult = 10_i128.pow(u32::from(2 - scale));
19358 scaled.saturating_mul(mult)
19359 } else {
19360 let div = 10_i128.pow(u32::from(scale - 2));
19361 let half = div / 2;
19362 let bias = if scaled >= 0 { half } else { -half };
19363 (scaled + bias) / div
19364 };
19365 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
19366 }
19367 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
19369 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
19373 Some(v) => Some(v),
19374 None => {
19375 return Err(EngineError::Eval(EvalError::TypeMismatch {
19376 detail: alloc::format!(
19377 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
19378 ),
19379 }));
19380 }
19381 },
19382 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
19384 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
19386 Some(pairs) => Some(Value::Hstore(pairs)),
19387 None => {
19388 return Err(EngineError::Eval(EvalError::TypeMismatch {
19389 detail: alloc::format!(
19390 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
19391 ),
19392 }));
19393 }
19394 },
19395 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
19397 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
19400 Ok(m) => Some(Value::IntArray2D(m)),
19401 Err(e) => {
19402 return Err(EngineError::Eval(EvalError::TypeMismatch {
19403 detail: alloc::format!(
19404 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
19405 ),
19406 }));
19407 }
19408 },
19409 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
19410 Ok(m) => Some(Value::BigIntArray2D(m)),
19411 Err(e) => {
19412 return Err(EngineError::Eval(EvalError::TypeMismatch {
19413 detail: alloc::format!(
19414 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
19415 ),
19416 }));
19417 }
19418 },
19419 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
19420 Ok(m) => Some(Value::TextArray2D(m)),
19421 Err(e) => {
19422 return Err(EngineError::Eval(EvalError::TypeMismatch {
19423 detail: alloc::format!(
19424 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
19425 ),
19426 }));
19427 }
19428 },
19429 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
19431 (Value::BigIntArray2D(rows), DataType::Text) => {
19432 Some(Value::Text(format_bigint_2d_text(&rows)))
19433 }
19434 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
19435 (Value::Text(s), DataType::TextArray) => {
19440 let arr = decode_text_array_literal(&s).map_err(|e| {
19441 EngineError::Eval(EvalError::TypeMismatch {
19442 detail: alloc::format!(
19443 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
19444 ),
19445 })
19446 })?;
19447 Some(Value::TextArray(arr))
19448 }
19449 (Value::Text(s), DataType::IntArray) => {
19455 let arr = decode_text_array_literal(&s).map_err(|e| {
19456 EngineError::Eval(EvalError::TypeMismatch {
19457 detail: alloc::format!(
19458 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
19459 ),
19460 })
19461 })?;
19462 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
19463 for elem in arr {
19464 match elem {
19465 None => out.push(None),
19466 Some(t) => {
19467 let n: i32 = t.parse().map_err(|_| {
19468 EngineError::Eval(EvalError::TypeMismatch {
19469 detail: alloc::format!(
19470 "cannot parse {t:?} as INT element for `{col_name}`"
19471 ),
19472 })
19473 })?;
19474 out.push(Some(n));
19475 }
19476 }
19477 }
19478 Some(Value::IntArray(out))
19479 }
19480 (Value::Text(s), DataType::BigIntArray) => {
19481 let arr = decode_text_array_literal(&s).map_err(|e| {
19482 EngineError::Eval(EvalError::TypeMismatch {
19483 detail: alloc::format!(
19484 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
19485 ),
19486 })
19487 })?;
19488 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
19489 for elem in arr {
19490 match elem {
19491 None => out.push(None),
19492 Some(t) => {
19493 let n: i64 = t.parse().map_err(|_| {
19494 EngineError::Eval(EvalError::TypeMismatch {
19495 detail: alloc::format!(
19496 "cannot parse {t:?} as BIGINT element for `{col_name}`"
19497 ),
19498 })
19499 })?;
19500 out.push(Some(n));
19501 }
19502 }
19503 }
19504 Some(Value::BigIntArray(out))
19505 }
19506 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
19510 (Value::Text(s), DataType::Vector { dim, encoding }) => {
19519 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
19520 EngineError::Eval(EvalError::TypeMismatch {
19521 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
19522 })
19523 })?;
19524 if parsed.len() != dim as usize {
19525 return Err(EngineError::Eval(EvalError::TypeMismatch {
19526 detail: alloc::format!(
19527 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
19528 parsed.len()
19529 ),
19530 }));
19531 }
19532 Some(match encoding {
19533 VecEncoding::F32 => Value::Vector(parsed),
19534 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
19535 VecEncoding::F16 => {
19536 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
19537 }
19538 })
19539 }
19540 (Value::Text(s), DataType::TsVector) => {
19550 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
19551 EngineError::Eval(EvalError::TypeMismatch {
19552 detail: alloc::format!(
19553 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
19554 ),
19555 })
19556 })?;
19557 Some(Value::TsVector(lexs))
19558 }
19559 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
19560 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
19561 EngineError::Eval(EvalError::TypeMismatch {
19562 detail: alloc::format!(
19563 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
19564 ),
19565 })
19566 })?;
19567 Some(Value::Timestamp(t))
19568 }
19569 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
19572 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
19573 }
19574 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
19578 (Value::Timestamp(t), DataType::Date) => {
19579 let days = t.div_euclid(86_400_000_000);
19580 i32::try_from(days).ok().map(Value::Date)
19581 }
19582 (
19583 Value::Numeric {
19584 scaled,
19585 scale: src_scale,
19586 },
19587 DataType::Numeric { precision, scale },
19588 ) => Some(numeric_rescale(
19589 scaled, src_scale, precision, scale, col_name,
19590 )?),
19591 #[allow(clippy::cast_precision_loss)]
19592 (Value::Numeric { scaled, scale }, DataType::Float) => {
19593 let mut div = 1.0_f64;
19594 for _ in 0..scale {
19595 div *= 10.0;
19596 }
19597 Some(Value::Float((scaled as f64) / div))
19598 }
19599 (Value::Numeric { scaled, scale }, DataType::Int) => {
19600 let truncated = numeric_truncate_to_integer(scaled, scale);
19601 i32::try_from(truncated).ok().map(Value::Int)
19602 }
19603 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
19604 let truncated = numeric_truncate_to_integer(scaled, scale);
19605 i64::try_from(truncated).ok().map(Value::BigInt)
19606 }
19607 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
19608 let truncated = numeric_truncate_to_integer(scaled, scale);
19609 i16::try_from(truncated).ok().map(Value::SmallInt)
19610 }
19611 (Value::Text(s), DataType::Varchar(max)) => {
19613 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
19614 Some(Value::Text(s))
19615 } else {
19616 return Err(EngineError::Unsupported(alloc::format!(
19617 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
19618 {} chars",
19619 s.chars().count()
19620 )));
19621 }
19622 }
19623 (
19631 Value::Vector(v),
19632 DataType::Vector {
19633 dim,
19634 encoding: VecEncoding::Sq8,
19635 },
19636 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
19637 (
19642 Value::Vector(v),
19643 DataType::Vector {
19644 dim,
19645 encoding: VecEncoding::F16,
19646 },
19647 ) if v.len() == dim as usize => Some(Value::HalfVector(
19648 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
19649 )),
19650 (Value::Text(s), DataType::Char(size)) => {
19654 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
19655 if len > size {
19656 return Err(EngineError::Unsupported(alloc::format!(
19657 "value for CHAR({size}) column `{col_name}` exceeds length: \
19658 {len} chars"
19659 )));
19660 }
19661 let need = (size - len) as usize;
19662 let mut padded = s;
19663 padded.reserve(need);
19664 for _ in 0..need {
19665 padded.push(' ');
19666 }
19667 Some(Value::Text(padded))
19668 }
19669 _ => None,
19670 };
19671 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
19672 column: col_name.into(),
19673 expected,
19674 actual,
19675 position,
19676 }))
19677}
19678
19679fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
19685 use core::fmt::Write;
19686 let mut out = alloc::string::String::from("(");
19687 for (i, a) in args.iter().enumerate() {
19688 if i > 0 {
19689 out.push_str(", ");
19690 }
19691 match a.mode {
19692 spg_sql::ast::FunctionArgMode::In => {}
19693 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
19694 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
19695 }
19696 if let Some(n) = &a.name {
19697 out.push_str(n);
19698 out.push(' ');
19699 }
19700 match &a.ty {
19701 spg_sql::ast::FunctionArgType::Typed(t) => {
19702 let _ = write!(out, "{t}");
19703 }
19704 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
19705 }
19706 }
19707 out.push(')');
19708 out
19709}
19710
19711fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
19720 match expr {
19721 spg_sql::ast::Expr::FunctionCall { name, args } => {
19722 name.eq_ignore_ascii_case("unnest") && args.len() == 1
19723 }
19724 _ => false,
19725 }
19726}
19727
19728fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
19732 match expr {
19733 spg_sql::ast::Expr::FunctionCall { name, args }
19734 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
19735 {
19736 Some(&args[0])
19737 }
19738 _ => None,
19739 }
19740}
19741
19742fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
19747 match v {
19748 Value::Null => Ok(Vec::new()),
19749 Value::TextArray(items) => Ok(items
19750 .iter()
19751 .map(|opt| {
19752 opt.as_ref()
19753 .map(|s| Value::Text(s.clone()))
19754 .unwrap_or(Value::Null)
19755 })
19756 .collect()),
19757 Value::IntArray(items) => Ok(items
19758 .iter()
19759 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
19760 .collect()),
19761 Value::BigIntArray(items) => Ok(items
19762 .iter()
19763 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
19764 .collect()),
19765 other => Err(EngineError::Eval(EvalError::TypeMismatch {
19766 detail: alloc::format!(
19767 "unnest() expects an array argument, got {:?}",
19768 other.data_type()
19769 ),
19770 })),
19771 }
19772}
19773
19774#[cfg(test)]
19775mod tests {
19776 use super::*;
19777 use alloc::vec;
19778
19779 fn unwrap_command_ok(r: &QueryResult) -> usize {
19780 match r {
19781 QueryResult::CommandOk { affected, .. } => *affected,
19782 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
19783 }
19784 }
19785
19786 #[test]
19787 fn update_seek_positions_engages_on_indexed_eq() {
19788 let mut e = Engine::new();
19789 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
19790 .unwrap();
19791 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
19792 for i in 0..100 {
19793 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
19794 .unwrap();
19795 }
19796 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
19797 .expect("parse");
19798 let Statement::Update(u) = stmt else {
19799 panic!("expected Update, got {stmt:?}");
19800 };
19801 let w = u.where_.as_ref().expect("where");
19802 let table = e.catalog().get("b").unwrap();
19803 let schema_cols = table.schema().columns.clone();
19804 let Expr::Binary { lhs, op, rhs } = w else {
19806 panic!("WHERE not Binary: {w:?}");
19807 };
19808 assert_eq!(*op, BinOp::Eq, "op not Eq");
19809 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
19810 assert!(
19811 pair.is_some(),
19812 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
19813 );
19814 let (col_pos, value) = pair.unwrap();
19815 assert!(
19816 table.index_on(col_pos).is_some(),
19817 "no index on col {col_pos}"
19818 );
19819 assert!(
19820 IndexKey::from_value(&value).is_some(),
19821 "IndexKey::from_value None for {value:?}"
19822 );
19823 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
19824 assert_eq!(positions, Some(vec![42]), "seek did not engage");
19825 }
19826
19827 #[test]
19828 fn create_table_registers_schema() {
19829 let mut e = Engine::new();
19830 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
19831 .unwrap();
19832 assert_eq!(e.catalog().table_count(), 1);
19833 let t = e.catalog().get("foo").unwrap();
19834 assert_eq!(t.schema().columns.len(), 2);
19835 assert_eq!(t.schema().columns[0].ty, DataType::Int);
19836 assert!(!t.schema().columns[0].nullable);
19837 assert_eq!(t.schema().columns[1].ty, DataType::Text);
19838 }
19839
19840 #[test]
19841 fn create_table_vector_default_is_f32_encoded() {
19842 let mut e = Engine::new();
19843 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
19844 let t = e.catalog().get("t").unwrap();
19845 assert_eq!(
19846 t.schema().columns[0].ty,
19847 DataType::Vector {
19848 dim: 8,
19849 encoding: VecEncoding::F32,
19850 },
19851 );
19852 }
19853
19854 #[test]
19855 fn create_table_vector_using_sq8_succeeds() {
19856 let mut e = Engine::new();
19860 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
19861 let t = e.catalog().get("t").unwrap();
19862 assert_eq!(
19863 t.schema().columns[0].ty,
19864 DataType::Vector {
19865 dim: 8,
19866 encoding: VecEncoding::Sq8,
19867 },
19868 );
19869 }
19870
19871 #[test]
19872 fn insert_into_sq8_column_quantises_f32_payload() {
19873 let mut e = Engine::new();
19880 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
19881 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19882 .unwrap();
19883 let t = e.catalog().get("t").unwrap();
19884 assert_eq!(t.rows().len(), 1);
19885 match &t.rows()[0].values[0] {
19886 Value::Sq8Vector(q) => {
19887 assert_eq!(q.bytes.len(), 4);
19888 assert!((q.min - 0.0).abs() < 1e-6);
19890 assert!((q.max - 1.0).abs() < 1e-6);
19891 }
19892 other => panic!("expected Sq8Vector cell, got {other:?}"),
19893 }
19894 }
19895
19896 #[test]
19897 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
19898 let mut e = Engine::new();
19905 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
19906 .unwrap();
19907 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19908 .unwrap();
19909 let t = e.catalog().get("t").unwrap();
19910 assert_eq!(t.rows().len(), 1);
19911 match &t.rows()[0].values[0] {
19912 Value::HalfVector(h) => {
19913 assert_eq!(h.dim(), 4);
19914 let back = h.to_f32_vec();
19915 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
19916 for (g, e) in back.iter().zip(expected.iter()) {
19917 assert!(
19918 (g - e).abs() < 1e-6,
19919 "{g} vs {e} should be exact on f16 grid"
19920 );
19921 }
19922 }
19923 other => panic!("expected HalfVector cell, got {other:?}"),
19924 }
19925 }
19926
19927 #[test]
19928 fn alter_index_rebuild_in_place_succeeds() {
19929 let mut e = Engine::new();
19934 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19935 .unwrap();
19936 for i in 0..8_i32 {
19937 #[allow(clippy::cast_precision_loss)]
19938 let base = (i as f32) * 0.1;
19939 e.execute(&alloc::format!(
19940 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
19941 b1 = base + 0.01,
19942 b2 = base + 0.02,
19943 ))
19944 .unwrap();
19945 }
19946 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19947 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
19948 assert_eq!(
19950 e.catalog().get("t").unwrap().schema().columns[1].ty,
19951 DataType::Vector {
19952 dim: 3,
19953 encoding: VecEncoding::F32,
19954 },
19955 );
19956 }
19957
19958 #[test]
19959 fn alter_index_rebuild_with_encoding_switches_cell_type() {
19960 let mut e = Engine::new();
19965 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
19966 .unwrap();
19967 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
19968 .unwrap();
19969 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19970 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
19971 .unwrap();
19972 let t = e.catalog().get("t").unwrap();
19973 assert_eq!(
19974 t.schema().columns[1].ty,
19975 DataType::Vector {
19976 dim: 4,
19977 encoding: VecEncoding::Sq8,
19978 },
19979 );
19980 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
19981 }
19982
19983 #[test]
19984 fn alter_index_rebuild_unknown_index_errors() {
19985 let mut e = Engine::new();
19986 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
19987 assert!(
19988 matches!(
19989 &err,
19990 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
19991 ),
19992 "got: {err}"
19993 );
19994 }
19995
19996 #[test]
19997 fn alter_index_rebuild_on_btree_index_errors() {
19998 let mut e = Engine::new();
20001 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20002 e.execute("INSERT INTO t VALUES (1)").unwrap();
20003 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
20004 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
20005 assert!(
20006 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
20007 "got: {err}"
20008 );
20009 }
20010
20011 #[test]
20012 fn prepared_insert_substitutes_placeholders() {
20013 let mut e = Engine::new();
20019 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
20020 .unwrap();
20021 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
20022 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
20023 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
20024 .unwrap();
20025 }
20026 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
20028 let QueryResult::Rows { rows, .. } = rows_result else {
20029 panic!("expected Rows")
20030 };
20031 assert_eq!(rows.len(), 3);
20032 }
20033
20034 #[test]
20035 fn prepared_select_with_placeholder_filters_rows() {
20036 let mut e = Engine::new();
20037 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
20038 .unwrap();
20039 for i in 0..10_i32 {
20040 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
20041 .unwrap();
20042 }
20043 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
20044 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
20045 else {
20046 panic!("expected Rows")
20047 };
20048 assert_eq!(rows.len(), 1);
20050 assert_eq!(rows[0].values[0], Value::Int(5));
20051 }
20052
20053 #[test]
20054 fn prepared_too_few_params_errors() {
20055 let mut e = Engine::new();
20056 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20057 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
20058 let err = e.execute_prepared(stmt, &[]).unwrap_err();
20059 assert!(
20060 matches!(
20061 &err,
20062 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
20063 ),
20064 "got: {err}"
20065 );
20066 }
20067
20068 #[test]
20069 fn bytea_cast_round_trips_text_input() {
20070 let e = Engine::new();
20073 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
20074 let QueryResult::Rows { rows, .. } = r else {
20075 panic!("expected Rows")
20076 };
20077 assert_eq!(rows.len(), 1);
20078 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
20079 }
20080
20081 #[test]
20082 fn bytea_cast_pg_escape_hex_form() {
20083 let e = Engine::new();
20087 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
20088 let QueryResult::Rows { rows, .. } = r else {
20089 panic!("expected Rows")
20090 };
20091 assert_eq!(
20092 rows[0].values[0],
20093 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
20094 );
20095 }
20096
20097 #[test]
20098 fn bytea_cast_chains_through_octet_length() {
20099 let e = Engine::new();
20103 let r = e
20104 .execute_readonly("SELECT octet_length('hello'::bytea)")
20105 .unwrap();
20106 let QueryResult::Rows { rows, .. } = r else {
20107 panic!("expected Rows")
20108 };
20109 match &rows[0].values[0] {
20110 Value::Int(n) => assert_eq!(*n, 5),
20111 Value::BigInt(n) => assert_eq!(*n, 5),
20112 other => panic!("expected integer length, got {other:?}"),
20113 }
20114 }
20115
20116 #[test]
20117 fn readonly_prepared_on_snapshot_select_with_placeholder() {
20118 let mut e = Engine::new();
20124 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
20125 .unwrap();
20126 for i in 0..10_i32 {
20127 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
20128 .unwrap();
20129 }
20130 let snapshot = e.clone_snapshot();
20131 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
20132 let QueryResult::Rows { rows, .. } =
20133 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
20134 .unwrap()
20135 else {
20136 panic!("expected Rows")
20137 };
20138 assert_eq!(rows.len(), 1);
20139 assert_eq!(rows[0].values[0], Value::Int(5));
20140 }
20141
20142 #[test]
20143 fn readonly_prepared_on_snapshot_rejects_writes() {
20144 let mut e = Engine::new();
20148 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20149 let snapshot = e.clone_snapshot();
20150 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
20151 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
20152 .unwrap_err();
20153 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
20154 }
20155
20156 #[test]
20157 fn readonly_prepared_on_snapshot_frozen_view() {
20158 let mut e = Engine::new();
20164 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20165 e.execute("INSERT INTO t VALUES (1)").unwrap();
20166 let snapshot = e.clone_snapshot();
20167 e.execute("INSERT INTO t VALUES (2)").unwrap();
20168 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
20169 let QueryResult::Rows { rows, .. } =
20170 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
20171 .unwrap()
20172 else {
20173 panic!("expected Rows")
20174 };
20175 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
20176 }
20177
20178 #[test]
20179 fn describe_prepared_on_snapshot_resolves_columns() {
20180 let mut e = Engine::new();
20185 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
20186 .unwrap();
20187 let snapshot = e.clone_snapshot();
20188 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
20189 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
20190 assert_eq!(cols.len(), 2);
20191 assert_eq!(cols[0].name, "id");
20192 assert_eq!(cols[0].ty, DataType::Int);
20193 assert_eq!(cols[1].name, "name");
20194 assert_eq!(cols[1].ty, DataType::Text);
20195 }
20196
20197 #[test]
20198 fn insert_into_half_column_dim_mismatch_errors() {
20199 let mut e = Engine::new();
20200 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
20201 .unwrap();
20202 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
20203 assert!(matches!(
20204 &err,
20205 EngineError::Storage(StorageError::TypeMismatch { .. })
20206 ));
20207 }
20208
20209 #[test]
20210 fn insert_into_sq8_column_dim_mismatch_errors() {
20211 let mut e = Engine::new();
20216 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
20217 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
20218 assert!(
20219 matches!(
20220 &err,
20221 EngineError::Storage(StorageError::TypeMismatch { .. })
20222 ),
20223 "got: {err}",
20224 );
20225 }
20226
20227 #[test]
20228 fn create_table_duplicate_errors() {
20229 let mut e = Engine::new();
20230 e.execute("CREATE TABLE foo (a INT)").unwrap();
20231 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
20232 assert!(matches!(
20233 err,
20234 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
20235 ));
20236 }
20237
20238 #[test]
20239 fn insert_into_unknown_table_errors() {
20240 let mut e = Engine::new();
20241 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
20242 assert!(matches!(
20243 err,
20244 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
20245 ));
20246 }
20247
20248 #[test]
20249 fn insert_happy_path_reports_one_affected() {
20250 let mut e = Engine::new();
20251 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20252 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
20253 assert_eq!(unwrap_command_ok(&r), 1);
20254 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
20255 }
20256
20257 #[test]
20258 fn insert_arity_mismatch_propagates() {
20259 let mut e = Engine::new();
20260 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
20261 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
20262 assert!(matches!(
20263 err,
20264 EngineError::Storage(StorageError::ArityMismatch { .. })
20265 ));
20266 }
20267
20268 #[test]
20269 fn insert_negative_integer_via_unary_minus() {
20270 let mut e = Engine::new();
20271 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20272 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
20273 let rows = e.catalog().get("foo").unwrap().rows();
20274 assert_eq!(rows[0].values[0], Value::Int(-7));
20275 }
20276
20277 #[test]
20278 fn insert_expression_evaluated_against_empty_context() {
20279 let mut e = Engine::new();
20284 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
20285 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
20286 let rows = e.catalog().get("foo").unwrap().rows();
20287 assert_eq!(rows[0].values[0], Value::Int(3));
20288 }
20289
20290 #[test]
20291 fn select_star_returns_all_rows_in_insertion_order() {
20292 let mut e = Engine::new();
20293 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
20294 .unwrap();
20295 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
20296 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
20297 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
20298
20299 let r = e.execute("SELECT * FROM foo").unwrap();
20300 let QueryResult::Rows { columns, rows } = r else {
20301 panic!("expected Rows")
20302 };
20303 assert_eq!(columns.len(), 2);
20304 assert_eq!(columns[0].name, "a");
20305 assert_eq!(rows.len(), 3);
20306 assert_eq!(
20307 rows[1].values,
20308 vec![Value::Int(2), Value::Text("two".into())]
20309 );
20310 }
20311
20312 #[test]
20313 fn select_star_on_empty_table_returns_zero_rows() {
20314 let mut e = Engine::new();
20315 e.execute("CREATE TABLE foo (a INT)").unwrap();
20316 let r = e.execute("SELECT * FROM foo").unwrap();
20317 match r {
20318 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
20319 QueryResult::CommandOk { .. } => panic!("expected Rows"),
20320 }
20321 }
20322
20323 fn make_three_row_users(e: &mut Engine) {
20326 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
20327 .unwrap();
20328 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
20329 .unwrap();
20330 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
20331 .unwrap();
20332 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
20333 .unwrap();
20334 }
20335
20336 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
20337 match r {
20338 QueryResult::Rows { columns, rows } => (columns, rows),
20339 QueryResult::CommandOk { .. } => panic!("expected Rows"),
20340 }
20341 }
20342
20343 #[test]
20344 fn where_filter_passes_only_true_rows() {
20345 let mut e = Engine::new();
20346 make_three_row_users(&mut e);
20347 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
20348 let (_, rows) = unwrap_rows(r);
20349 assert_eq!(rows.len(), 2);
20350 assert_eq!(rows[0].values[0], Value::Int(2));
20351 assert_eq!(rows[1].values[0], Value::Int(3));
20352 }
20353
20354 #[test]
20355 fn where_with_null_result_filters_out_row() {
20356 let mut e = Engine::new();
20357 make_three_row_users(&mut e);
20358 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
20360 let (_, rows) = unwrap_rows(r);
20361 assert_eq!(rows.len(), 1);
20362 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
20363 }
20364
20365 #[test]
20366 fn projection_named_columns() {
20367 let mut e = Engine::new();
20368 make_three_row_users(&mut e);
20369 let r = e.execute("SELECT name, score FROM users").unwrap();
20370 let (cols, rows) = unwrap_rows(r);
20371 assert_eq!(cols.len(), 2);
20372 assert_eq!(cols[0].name, "name");
20373 assert_eq!(cols[1].name, "score");
20374 assert_eq!(rows.len(), 3);
20375 assert_eq!(
20376 rows[0].values,
20377 vec![Value::Text("alice".into()), Value::Int(90)]
20378 );
20379 }
20380
20381 #[test]
20382 fn projection_with_column_alias() {
20383 let mut e = Engine::new();
20384 make_three_row_users(&mut e);
20385 let r = e
20386 .execute("SELECT name AS who FROM users WHERE id = 1")
20387 .unwrap();
20388 let (cols, rows) = unwrap_rows(r);
20389 assert_eq!(cols[0].name, "who");
20390 assert_eq!(rows.len(), 1);
20391 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
20392 }
20393
20394 #[test]
20395 fn qualified_column_with_table_alias_resolves() {
20396 let mut e = Engine::new();
20397 make_three_row_users(&mut e);
20398 let r = e
20399 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
20400 .unwrap();
20401 let (cols, rows) = unwrap_rows(r);
20402 assert_eq!(cols.len(), 2);
20403 assert_eq!(rows.len(), 2);
20404 }
20405
20406 #[test]
20407 fn qualified_column_with_wrong_alias_errors() {
20408 let mut e = Engine::new();
20409 make_three_row_users(&mut e);
20410 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
20411 assert!(matches!(
20412 err,
20413 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
20414 ));
20415 }
20416
20417 #[test]
20418 fn select_unknown_column_errors_in_projection() {
20419 let mut e = Engine::new();
20420 make_three_row_users(&mut e);
20421 let err = e.execute("SELECT ghost FROM users").unwrap_err();
20422 assert!(matches!(
20423 err,
20424 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
20425 ));
20426 }
20427
20428 #[test]
20429 fn where_unknown_column_errors() {
20430 let mut e = Engine::new();
20431 make_three_row_users(&mut e);
20432 let err = e
20433 .execute("SELECT * FROM users WHERE ghost = 1")
20434 .unwrap_err();
20435 assert!(matches!(
20436 err,
20437 EngineError::Eval(EvalError::ColumnNotFound { .. })
20438 ));
20439 }
20440
20441 #[test]
20442 fn expression_projection_evaluates_and_renders() {
20443 let mut e = Engine::new();
20446 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
20447 e.execute("INSERT INTO t VALUES (3)").unwrap();
20448 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
20449 assert_eq!(rows.len(), 1);
20450 assert_eq!(rows[0].values[0], Value::Int(3));
20453 }
20454
20455 #[test]
20456 fn select_unknown_table_errors() {
20457 let mut e = Engine::new();
20458 let err = e.execute("SELECT * FROM ghost").unwrap_err();
20459 assert!(matches!(
20460 err,
20461 EngineError::Storage(StorageError::TableNotFound { .. })
20462 ));
20463 }
20464
20465 #[test]
20466 fn invalid_sql_returns_parse_error() {
20467 let mut e = Engine::new();
20470 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
20471 assert!(matches!(err, EngineError::Parse(_)));
20472 }
20473
20474 #[test]
20477 fn create_index_registers_on_table() {
20478 let mut e = Engine::new();
20479 make_three_row_users(&mut e);
20480 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
20481 let t = e.catalog().get("users").unwrap();
20482 assert_eq!(t.indices().len(), 1);
20483 assert_eq!(t.indices()[0].name, "by_name");
20484 }
20485
20486 #[test]
20487 fn create_index_on_unknown_table_errors() {
20488 let mut e = Engine::new();
20489 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
20490 assert!(matches!(
20491 err,
20492 EngineError::Storage(StorageError::TableNotFound { .. })
20493 ));
20494 }
20495
20496 #[test]
20497 fn create_index_on_unknown_column_errors() {
20498 let mut e = Engine::new();
20499 make_three_row_users(&mut e);
20500 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
20501 assert!(matches!(
20502 err,
20503 EngineError::Storage(StorageError::ColumnNotFound { .. })
20504 ));
20505 }
20506
20507 #[test]
20508 fn select_eq_uses_index_returns_same_rows_as_scan() {
20509 let mut without = Engine::new();
20513 make_three_row_users(&mut without);
20514 let mut with = Engine::new();
20515 make_three_row_users(&mut with);
20516 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
20517
20518 let q = "SELECT * FROM users WHERE id = 2";
20519 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
20520 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
20521 assert_eq!(no_idx_rows, idx_rows);
20522 assert_eq!(idx_rows.len(), 1);
20523 }
20524
20525 #[test]
20526 fn select_eq_with_no_matching_index_value_returns_empty() {
20527 let mut e = Engine::new();
20528 make_three_row_users(&mut e);
20529 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
20530 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
20531 assert_eq!(rows.len(), 0);
20532 }
20533
20534 #[test]
20537 fn begin_sets_in_transaction_flag() {
20538 let mut e = Engine::new();
20539 assert!(!e.in_transaction());
20540 e.execute("BEGIN").unwrap();
20541 assert!(e.in_transaction());
20542 }
20543
20544 #[test]
20545 fn double_begin_errors() {
20546 let mut e = Engine::new();
20547 e.execute("BEGIN").unwrap();
20548 let err = e.execute("BEGIN").unwrap_err();
20549 assert_eq!(err, EngineError::TransactionAlreadyOpen);
20550 }
20551
20552 #[test]
20553 fn commit_without_begin_errors() {
20554 let mut e = Engine::new();
20555 let err = e.execute("COMMIT").unwrap_err();
20556 assert_eq!(err, EngineError::NoActiveTransaction);
20557 }
20558
20559 #[test]
20560 fn rollback_without_begin_errors() {
20561 let mut e = Engine::new();
20562 let err = e.execute("ROLLBACK").unwrap_err();
20563 assert_eq!(err, EngineError::NoActiveTransaction);
20564 }
20565
20566 #[test]
20567 fn commit_applies_shadow_to_committed_catalog() {
20568 let mut e = Engine::new();
20569 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20570 e.execute("BEGIN").unwrap();
20571 e.execute("INSERT INTO t VALUES (1)").unwrap();
20572 e.execute("INSERT INTO t VALUES (2)").unwrap();
20573 e.execute("COMMIT").unwrap();
20574 assert!(!e.in_transaction());
20575 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
20576 }
20577
20578 #[test]
20579 fn rollback_discards_shadow() {
20580 let mut e = Engine::new();
20581 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20582 e.execute("BEGIN").unwrap();
20583 e.execute("INSERT INTO t VALUES (1)").unwrap();
20584 e.execute("INSERT INTO t VALUES (2)").unwrap();
20585 e.execute("ROLLBACK").unwrap();
20586 assert!(!e.in_transaction());
20587 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
20588 }
20589
20590 #[test]
20591 fn select_during_tx_sees_uncommitted_writes_own_session() {
20592 let mut e = Engine::new();
20595 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
20596 e.execute("BEGIN").unwrap();
20597 e.execute("INSERT INTO t VALUES (42)").unwrap();
20598 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
20599 assert_eq!(rows.len(), 1);
20600 assert_eq!(rows[0].values[0], Value::Int(42));
20601 }
20602
20603 #[test]
20604 fn snapshot_with_no_users_is_bare_catalog_format() {
20605 let mut e = Engine::new();
20606 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20607 let bytes = e.snapshot();
20608 assert_eq!(
20609 &bytes[..8],
20610 b"SPGDB001",
20611 "must be the bare v3.x catalog magic"
20612 );
20613 let e2 = Engine::restore_envelope(&bytes).unwrap();
20614 assert!(e2.users().is_empty());
20615 assert_eq!(e2.catalog().table_count(), 1);
20616 }
20617
20618 #[test]
20619 fn snapshot_with_users_round_trips_both_via_envelope() {
20620 let mut e = Engine::new();
20621 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20622 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
20623 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
20624 .unwrap();
20625 let bytes = e.snapshot();
20626 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
20627 let e2 = Engine::restore_envelope(&bytes).unwrap();
20628 assert_eq!(e2.users().len(), 2);
20629 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
20630 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
20631 assert_eq!(e2.verify_user("alice", "wrong"), None);
20632 assert_eq!(e2.catalog().table_count(), 1);
20633 }
20634
20635 #[test]
20636 fn ddl_inside_tx_also_rolled_back() {
20637 let mut e = Engine::new();
20638 e.execute("BEGIN").unwrap();
20639 e.execute("CREATE TABLE t (v INT)").unwrap();
20640 e.execute("SELECT * FROM t").unwrap();
20642 e.execute("ROLLBACK").unwrap();
20643 let err = e.execute("SELECT * FROM t").unwrap_err();
20645 assert!(matches!(
20646 err,
20647 EngineError::Storage(StorageError::TableNotFound { .. })
20648 ));
20649 }
20650
20651 #[test]
20654 fn create_publication_lands_in_catalog() {
20655 let mut e = Engine::new();
20656 assert!(e.publications().is_empty());
20657 e.execute("CREATE PUBLICATION pub_a").unwrap();
20658 assert_eq!(e.publications().len(), 1);
20659 assert!(e.publications().contains("pub_a"));
20660 }
20661
20662 #[test]
20663 fn create_publication_duplicate_errors() {
20664 let mut e = Engine::new();
20665 e.execute("CREATE PUBLICATION pub_a").unwrap();
20666 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
20667 assert!(
20668 alloc::format!("{err:?}").contains("DuplicateName"),
20669 "got {err:?}"
20670 );
20671 }
20672
20673 #[test]
20674 fn drop_publication_silent_when_absent() {
20675 let mut e = Engine::new();
20676 let r = e.execute("DROP PUBLICATION nope").unwrap();
20679 match r {
20680 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20681 other => panic!("expected CommandOk, got {other:?}"),
20682 }
20683 }
20684
20685 #[test]
20686 fn drop_publication_present_reports_one_affected() {
20687 let mut e = Engine::new();
20688 e.execute("CREATE PUBLICATION pub_a").unwrap();
20689 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
20690 match r {
20691 QueryResult::CommandOk {
20692 affected,
20693 modified_catalog,
20694 } => {
20695 assert_eq!(affected, 1);
20696 assert!(modified_catalog);
20697 }
20698 other => panic!("expected CommandOk, got {other:?}"),
20699 }
20700 assert!(e.publications().is_empty());
20701 }
20702
20703 #[test]
20704 fn publications_persist_across_snapshot_restore() {
20705 let mut e = Engine::new();
20710 e.execute("CREATE PUBLICATION pub_a").unwrap();
20711 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
20712 .unwrap();
20713 let snap = e.snapshot();
20714 let e2 = Engine::restore_envelope(&snap).unwrap();
20715 assert_eq!(e2.publications().len(), 2);
20716 assert!(e2.publications().contains("pub_a"));
20717 assert!(e2.publications().contains("pub_b"));
20718 }
20719
20720 #[test]
20721 fn create_publication_allowed_inside_transaction() {
20722 let mut e = Engine::new();
20726 e.execute("BEGIN").unwrap();
20727 e.execute("CREATE PUBLICATION pub_a").unwrap();
20728 e.execute("COMMIT").unwrap();
20729 assert!(e.publications().contains("pub_a"));
20730 }
20731
20732 #[test]
20735 fn create_publication_for_table_list_lands_with_scope() {
20736 let mut e = Engine::new();
20737 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
20738 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
20739 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
20740 .unwrap();
20741 let scope = e.publications().get("pub_a").cloned();
20742 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
20743 panic!("expected ForTables scope, got {scope:?}")
20744 };
20745 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20746 }
20747
20748 #[test]
20749 fn create_publication_all_tables_except_lands_with_scope() {
20750 let mut e = Engine::new();
20751 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
20752 .unwrap();
20753 let scope = e.publications().get("pub_a").cloned();
20754 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
20755 panic!("expected AllTablesExcept scope, got {scope:?}")
20756 };
20757 assert_eq!(ts, alloc::vec!["t3".to_string()]);
20758 }
20759
20760 #[test]
20761 fn show_publications_empty_returns_zero_rows() {
20762 let e = Engine::new();
20763 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
20764 let QueryResult::Rows { rows, columns } = r else {
20765 panic!()
20766 };
20767 assert!(rows.is_empty());
20768 assert_eq!(columns.len(), 3);
20769 assert_eq!(columns[0].name, "name");
20770 assert_eq!(columns[1].name, "scope");
20771 assert_eq!(columns[2].name, "table_count");
20772 }
20773
20774 #[test]
20775 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
20776 let mut e = Engine::new();
20777 e.execute("CREATE PUBLICATION z_pub").unwrap();
20778 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
20779 .unwrap();
20780 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
20781 .unwrap();
20782 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
20783 let QueryResult::Rows { rows, .. } = r else {
20784 panic!()
20785 };
20786 assert_eq!(rows.len(), 3);
20787 let names: Vec<&str> = rows
20789 .iter()
20790 .map(|r| {
20791 if let Value::Text(s) = &r.values[0] {
20792 s.as_str()
20793 } else {
20794 panic!()
20795 }
20796 })
20797 .collect();
20798 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
20799 match &rows[0].values[1] {
20801 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
20802 other => panic!("expected Text, got {other:?}"),
20803 }
20804 assert_eq!(rows[0].values[2], Value::Int(2));
20805 match &rows[1].values[1] {
20807 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
20808 other => panic!("expected Text, got {other:?}"),
20809 }
20810 assert_eq!(rows[1].values[2], Value::Int(1));
20811 match &rows[2].values[1] {
20813 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
20814 other => panic!("expected Text, got {other:?}"),
20815 }
20816 assert_eq!(rows[2].values[2], Value::Null);
20817 }
20818
20819 #[test]
20820 fn for_list_scopes_persist_across_snapshot() {
20821 let mut e = Engine::new();
20824 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
20825 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
20826 .unwrap();
20827 let snap = e.snapshot();
20828 let e2 = Engine::restore_envelope(&snap).unwrap();
20829 assert_eq!(e2.publications().len(), 2);
20830 let p1 = e2.publications().get("p1").cloned();
20831 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
20832 panic!("p1 scope lost: {p1:?}")
20833 };
20834 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20835 let p2 = e2.publications().get("p2").cloned();
20836 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
20837 panic!("p2 scope lost: {p2:?}")
20838 };
20839 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
20840 }
20841
20842 #[test]
20845 fn create_subscription_lands_in_catalog_with_defaults() {
20846 let mut e = Engine::new();
20847 e.execute(
20848 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
20849 )
20850 .unwrap();
20851 let s = e.subscriptions().get("sub_a").cloned().expect("present");
20852 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
20853 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
20854 assert!(s.enabled);
20855 assert_eq!(s.last_received_pos, 0);
20856 }
20857
20858 #[test]
20859 fn create_subscription_duplicate_name_errors() {
20860 let mut e = Engine::new();
20861 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
20862 .unwrap();
20863 let err = e
20864 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
20865 .unwrap_err();
20866 assert!(
20867 alloc::format!("{err:?}").contains("DuplicateName"),
20868 "got {err:?}"
20869 );
20870 }
20871
20872 #[test]
20873 fn drop_subscription_silent_when_absent() {
20874 let mut e = Engine::new();
20875 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
20876 match r {
20877 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20878 other => panic!("expected CommandOk, got {other:?}"),
20879 }
20880 }
20881
20882 #[test]
20883 fn subscription_advance_updates_last_pos_monotone() {
20884 let mut e = Engine::new();
20885 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20886 .unwrap();
20887 assert!(e.subscription_advance("s", 100));
20888 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20889 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20891 assert!(e.subscription_advance("s", 200));
20892 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
20893 assert!(!e.subscription_advance("missing", 1));
20894 }
20895
20896 #[test]
20897 fn show_subscriptions_returns_rows_ordered_by_name() {
20898 let mut e = Engine::new();
20899 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
20900 .unwrap();
20901 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
20902 .unwrap();
20903 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
20904 let QueryResult::Rows { rows, columns } = r else {
20905 panic!()
20906 };
20907 assert_eq!(rows.len(), 2);
20908 assert_eq!(columns.len(), 5);
20909 assert_eq!(columns[0].name, "name");
20910 assert_eq!(columns[4].name, "last_received_pos");
20911 let names: Vec<&str> = rows
20913 .iter()
20914 .map(|r| {
20915 if let Value::Text(s) = &r.values[0] {
20916 s.as_str()
20917 } else {
20918 panic!()
20919 }
20920 })
20921 .collect();
20922 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
20923 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
20925 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
20926 assert_eq!(rows[0].values[3], Value::Bool(true));
20927 assert_eq!(rows[0].values[4], Value::BigInt(0));
20928 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
20930 }
20931
20932 #[test]
20933 fn subscriptions_persist_across_snapshot_envelope_v4() {
20934 let mut e = Engine::new();
20935 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
20936 .unwrap();
20937 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
20938 .unwrap();
20939 e.subscription_advance("s2", 42);
20940 let snap = e.snapshot();
20941 let e2 = Engine::restore_envelope(&snap).unwrap();
20942 assert_eq!(e2.subscriptions().len(), 2);
20943 let s1 = e2.subscriptions().get("s1").unwrap();
20944 assert_eq!(s1.conn_str, "h=A");
20945 assert_eq!(
20946 s1.publications,
20947 alloc::vec!["p1".to_string(), "p2".to_string()]
20948 );
20949 assert_eq!(s1.last_received_pos, 0);
20950 let s2 = e2.subscriptions().get("s2").unwrap();
20951 assert_eq!(s2.last_received_pos, 42);
20952 }
20953
20954 #[test]
20955 fn v3_envelope_loads_with_empty_subscriptions() {
20956 let mut e = Engine::new();
20960 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
20961 let catalog = e.catalog.serialize();
20962 let users = crate::users::serialize_users(&e.users);
20963 let pubs = e.publications.serialize();
20964 let mut buf = Vec::new();
20965 buf.extend_from_slice(b"SPGENV01");
20966 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
20968 buf.extend_from_slice(&catalog);
20969 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
20970 buf.extend_from_slice(&users);
20971 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
20972 buf.extend_from_slice(&pubs);
20973 let crc = spg_crypto::crc32::crc32(&buf);
20974 buf.extend_from_slice(&crc.to_le_bytes());
20975
20976 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
20977 assert!(e2.subscriptions().is_empty());
20978 assert!(e2.publications().contains("pub_legacy"));
20979 }
20980
20981 #[test]
20982 fn create_subscription_allowed_inside_transaction() {
20983 let mut e = Engine::new();
20984 e.execute("BEGIN").unwrap();
20985 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20986 .unwrap();
20987 e.execute("COMMIT").unwrap();
20988 assert!(e.subscriptions().contains("s"));
20989 }
20990
20991 #[test]
20993 fn analyze_populates_histogram_bounds() {
20994 let mut e = Engine::new();
20995 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
20996 .unwrap();
20997 for i in 0..50 {
20998 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
20999 .unwrap();
21000 }
21001 e.execute("ANALYZE t").unwrap();
21002 let stats = e.statistics();
21003 let id_stats = stats.get("t", "id").unwrap();
21004 assert!(id_stats.histogram_bounds.len() >= 2);
21005 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
21006 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
21007 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
21008 assert_eq!(id_stats.n_distinct, 50);
21009 }
21010
21011 #[test]
21012 fn reanalyze_overwrites_prior_stats() {
21013 let mut e = Engine::new();
21014 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
21015 for i in 0..10 {
21016 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21017 .unwrap();
21018 }
21019 e.execute("ANALYZE t").unwrap();
21020 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
21021 assert_eq!(n1, 10);
21022 for i in 10..30 {
21023 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21024 .unwrap();
21025 }
21026 e.execute("ANALYZE t").unwrap();
21027 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
21028 assert_eq!(n2, 30);
21029 }
21030
21031 #[test]
21032 fn analyze_unknown_table_errors() {
21033 let mut e = Engine::new();
21034 let err = e.execute("ANALYZE nonexistent").unwrap_err();
21035 assert!(matches!(
21036 err,
21037 EngineError::Storage(StorageError::TableNotFound { .. })
21038 ));
21039 }
21040
21041 #[test]
21042 fn bare_analyze_covers_all_user_tables() {
21043 let mut e = Engine::new();
21044 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
21045 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
21046 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
21047 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
21048 let r = e.execute("ANALYZE").unwrap();
21049 match r {
21050 QueryResult::CommandOk {
21051 affected,
21052 modified_catalog,
21053 } => {
21054 assert_eq!(affected, 2);
21055 assert!(modified_catalog);
21056 }
21057 other => panic!("expected CommandOk, got {other:?}"),
21058 }
21059 assert!(e.statistics().get("t1", "id").is_some());
21060 assert!(e.statistics().get("t2", "name").is_some());
21061 }
21062
21063 #[test]
21064 fn select_from_spg_statistic_returns_rows_per_column() {
21065 let mut e = Engine::new();
21066 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
21067 .unwrap();
21068 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
21069 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
21070 e.execute("ANALYZE t").unwrap();
21071 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
21072 let QueryResult::Rows { rows, columns } = r else {
21073 panic!()
21074 };
21075 assert_eq!(columns.len(), 6);
21077 assert_eq!(columns[0].name, "table_name");
21078 assert_eq!(columns[4].name, "histogram_bounds");
21079 assert_eq!(columns[5].name, "cold_row_count");
21080 assert_eq!(rows.len(), 2, "one row per column of t");
21081 match (&rows[0].values[0], &rows[0].values[1]) {
21083 (Value::Text(t), Value::Text(c)) => {
21084 assert_eq!(t, "t");
21085 assert_eq!(c, "id");
21087 }
21088 _ => panic!(),
21089 }
21090 }
21091
21092 #[test]
21093 fn analyze_skips_vector_columns() {
21094 let mut e = Engine::new();
21097 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
21098 .unwrap();
21099 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
21100 e.execute("ANALYZE t").unwrap();
21101 assert!(e.statistics().get("t", "id").is_some());
21102 assert!(e.statistics().get("t", "v").is_none());
21103 }
21104
21105 #[test]
21106 fn statistics_persist_across_envelope_v5_round_trip() {
21107 let mut e = Engine::new();
21108 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
21109 for i in 0..20 {
21110 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21111 .unwrap();
21112 }
21113 e.execute("ANALYZE").unwrap();
21114 let snap = e.snapshot();
21115 let e2 = Engine::restore_envelope(&snap).unwrap();
21116 let s = e2.statistics().get("t", "id").unwrap();
21117 assert_eq!(s.n_distinct, 20);
21118 }
21119
21120 #[test]
21123 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
21124 let mut e = Engine::new();
21128 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
21129 for i in 0..9 {
21130 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21131 .unwrap();
21132 }
21133 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
21134 e.execute("INSERT INTO t VALUES (9)").unwrap();
21135 let needs = e.tables_needing_analyze();
21136 assert_eq!(needs, alloc::vec!["t".to_string()]);
21137 }
21138
21139 #[test]
21140 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
21141 let mut e = Engine::new();
21147 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
21148 for i in 0..1000 {
21149 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21150 .unwrap();
21151 }
21152 e.execute("ANALYZE t").unwrap();
21153 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
21154 for i in 1000..1050 {
21155 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21156 .unwrap();
21157 }
21158 assert!(
21159 e.tables_needing_analyze().is_empty(),
21160 "50 inserts < threshold of ~105"
21161 );
21162 for i in 1050..1200 {
21163 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21164 .unwrap();
21165 }
21166 assert_eq!(
21167 e.tables_needing_analyze(),
21168 alloc::vec!["t".to_string()],
21169 "200 inserts > 0.1 × 1200 threshold"
21170 );
21171 }
21172
21173 #[test]
21174 fn auto_analyze_threshold_resets_after_analyze() {
21175 let mut e = Engine::new();
21176 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
21177 for i in 0..200 {
21178 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
21179 .unwrap();
21180 }
21181 assert!(!e.tables_needing_analyze().is_empty());
21182 e.execute("ANALYZE").unwrap();
21183 assert!(
21184 e.tables_needing_analyze().is_empty(),
21185 "ANALYZE must reset the counter"
21186 );
21187 }
21188
21189 #[test]
21190 fn auto_analyze_threshold_tracks_updates_and_deletes() {
21191 let mut e = Engine::new();
21192 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
21193 .unwrap();
21194 for i in 0..50 {
21195 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
21196 .unwrap();
21197 }
21198 e.execute("ANALYZE t").unwrap();
21199 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
21202 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
21203 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
21204 }
21205
21206 #[test]
21207 fn v4_envelope_loads_with_empty_statistics() {
21208 let mut e = Engine::new();
21212 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
21213 .unwrap();
21214 let catalog = e.catalog.serialize();
21215 let users = crate::users::serialize_users(&e.users);
21216 let pubs = e.publications.serialize();
21217 let subs = e.subscriptions.serialize();
21218 let mut buf = Vec::new();
21219 buf.extend_from_slice(b"SPGENV01");
21220 buf.push(4u8);
21221 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
21222 buf.extend_from_slice(&catalog);
21223 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
21224 buf.extend_from_slice(&users);
21225 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
21226 buf.extend_from_slice(&pubs);
21227 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
21228 buf.extend_from_slice(&subs);
21229 let crc = spg_crypto::crc32::crc32(&buf);
21230 buf.extend_from_slice(&crc.to_le_bytes());
21231 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
21232 assert!(e2.statistics().is_empty());
21233 }
21234
21235 #[test]
21236 fn v1_v2_envelope_loads_with_empty_publications() {
21237 let mut e = Engine::new();
21244 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
21247 .unwrap();
21248
21249 let catalog = e.catalog.serialize();
21251 let users = crate::users::serialize_users(&e.users);
21252 let mut buf = Vec::new();
21253 buf.extend_from_slice(b"SPGENV01");
21254 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
21256 buf.extend_from_slice(&catalog);
21257 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
21258 buf.extend_from_slice(&users);
21259 let crc = spg_crypto::crc32::crc32(&buf);
21260 buf.extend_from_slice(&crc.to_le_bytes());
21261
21262 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
21263 assert!(e2.publications().is_empty());
21264 }
21265}