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 let cat = self.active_catalog_mut();
3279 if !cat.sequences().contains_key(&s.name) {
3280 if s.if_exists {
3281 return Ok(QueryResult::CommandOk {
3282 affected: 0,
3283 modified_catalog: false,
3284 });
3285 }
3286 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3287 alloc::format!("sequence {:?} does not exist", s.name),
3288 )));
3289 }
3290 let min_value = match s.options.min_value {
3291 None => None,
3292 Some(SeqBound::NoBound) => None, Some(SeqBound::Value(n)) => Some(n),
3294 };
3295 let max_value = match s.options.max_value {
3296 None => None,
3297 Some(SeqBound::NoBound) => None,
3298 Some(SeqBound::Value(n)) => Some(n),
3299 };
3300 let owned_by = s.options.owned_by.map(|ob| match ob {
3301 spg_sql::ast::SequenceOwnedBy::None => None,
3302 spg_sql::ast::SequenceOwnedBy::Column { table, column } => Some((table, column)),
3303 });
3304 cat.alter_sequence(
3305 &s.name,
3306 s.options.increment,
3307 min_value,
3308 max_value,
3309 s.options.start,
3310 s.options.restart,
3311 s.options.cache,
3312 s.options.cycle,
3313 owned_by,
3314 )
3315 .map_err(EngineError::Storage)?;
3316 Ok(QueryResult::CommandOk {
3317 affected: 0,
3318 modified_catalog: !self.in_transaction(),
3319 })
3320 }
3321
3322 fn pre_resolve_sequence_calls_in_statement(
3327 &mut self,
3328 stmt: &mut Statement,
3329 ) -> Result<(), EngineError> {
3330 match stmt {
3331 Statement::Select(s) => self.pre_resolve_sequence_calls_in_select(s),
3332 Statement::Insert(s) => {
3333 for tuple in &mut s.rows {
3334 for cell in tuple.iter_mut() {
3335 self.resolve_sequence_calls_in_expr(cell)?;
3336 }
3337 }
3338 Ok(())
3339 }
3340 Statement::Update(s) => {
3341 for (_col, expr) in &mut s.assignments {
3342 self.resolve_sequence_calls_in_expr(expr)?;
3343 }
3344 if let Some(w) = &mut s.where_ {
3345 self.resolve_sequence_calls_in_expr(w)?;
3346 }
3347 Ok(())
3348 }
3349 Statement::Delete(s) => {
3350 if let Some(w) = &mut s.where_ {
3351 self.resolve_sequence_calls_in_expr(w)?;
3352 }
3353 Ok(())
3354 }
3355 _ => Ok(()),
3356 }
3357 }
3358
3359 fn pre_resolve_sequence_calls_in_select(
3360 &mut self,
3361 s: &mut spg_sql::ast::SelectStatement,
3362 ) -> Result<(), EngineError> {
3363 for item in &mut s.items {
3364 match item {
3365 spg_sql::ast::SelectItem::Expr { expr, .. } => {
3366 self.resolve_sequence_calls_in_expr(expr)?;
3367 }
3368 spg_sql::ast::SelectItem::Wildcard => {}
3369 }
3370 }
3371 if let Some(w) = &mut s.where_ {
3372 self.resolve_sequence_calls_in_expr(w)?;
3373 }
3374 Ok(())
3375 }
3376
3377 #[allow(clippy::too_many_lines)]
3385 fn resolve_sequence_calls_in_expr(&mut self, expr: &mut Expr) -> Result<(), EngineError> {
3386 match expr {
3387 Expr::Literal(_) | Expr::Column(_) | Expr::Placeholder(_) => Ok(()),
3388 Expr::FunctionCall { name, args } => {
3389 for a in args.iter_mut() {
3393 self.resolve_sequence_calls_in_expr(a)?;
3394 }
3395 let lc = name.to_ascii_lowercase();
3396 if lc == "nextval" || lc == "currval" || lc == "setval" {
3397 let v = self.eval_sequence_call(&lc, args)?;
3398 *expr = Expr::Literal(value_to_literal(v));
3399 }
3400 Ok(())
3401 }
3402 Expr::Binary { lhs, rhs, .. } => {
3403 self.resolve_sequence_calls_in_expr(lhs)?;
3404 self.resolve_sequence_calls_in_expr(rhs)
3405 }
3406 Expr::Unary { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3407 Expr::Cast { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3408 Expr::IsNull { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3409 Expr::Like { expr, pattern, .. } => {
3410 self.resolve_sequence_calls_in_expr(expr)?;
3411 self.resolve_sequence_calls_in_expr(pattern)
3412 }
3413 Expr::Extract { source, .. } => self.resolve_sequence_calls_in_expr(source),
3414 Expr::Array(items) => {
3415 for it in items.iter_mut() {
3416 self.resolve_sequence_calls_in_expr(it)?;
3417 }
3418 Ok(())
3419 }
3420 _ => Ok(()),
3425 }
3426 }
3427
3428 fn eval_sequence_call(&mut self, op: &str, args: &[Expr]) -> Result<Value, EngineError> {
3432 if args.is_empty() {
3433 return Err(EngineError::Unsupported(alloc::format!(
3434 "{op}() takes at least one argument"
3435 )));
3436 }
3437 let seq_name = match &args[0] {
3438 Expr::Literal(spg_sql::ast::Literal::String(s)) => {
3439 let trimmed = s
3445 .strip_prefix("public.")
3446 .or_else(|| s.strip_prefix("pg_catalog."))
3447 .unwrap_or(s);
3448 trimmed.to_string()
3449 }
3450 Expr::Cast { expr, .. } => {
3455 if let Expr::Literal(spg_sql::ast::Literal::String(s)) = expr.as_ref() {
3456 let trimmed = s
3457 .strip_prefix("public.")
3458 .or_else(|| s.strip_prefix("pg_catalog."))
3459 .unwrap_or(s);
3460 trimmed.to_string()
3461 } else {
3462 return Err(EngineError::Unsupported(alloc::format!(
3463 "{op}() first argument must be a literal sequence name"
3464 )));
3465 }
3466 }
3467 other => {
3468 return Err(EngineError::Unsupported(alloc::format!(
3469 "{op}() first argument must be a literal sequence name, got {other:?}"
3470 )));
3471 }
3472 };
3473 match op {
3474 "nextval" => {
3475 let v = self
3476 .active_catalog_mut()
3477 .sequence_next_value(&seq_name)
3478 .map_err(EngineError::Storage)?;
3479 Ok(Value::BigInt(v))
3480 }
3481 "currval" => {
3482 let v = self
3483 .active_catalog()
3484 .sequence_current_value(&seq_name)
3485 .map_err(EngineError::Storage)?;
3486 Ok(Value::BigInt(v))
3487 }
3488 "setval" => {
3489 if args.len() < 2 || args.len() > 3 {
3490 return Err(EngineError::Unsupported(alloc::format!(
3491 "setval() takes 2 or 3 arguments, got {}",
3492 args.len()
3493 )));
3494 }
3495 let value = match &args[1] {
3496 Expr::Literal(spg_sql::ast::Literal::Integer(n)) => *n,
3497 other => {
3498 return Err(EngineError::Unsupported(alloc::format!(
3499 "setval() value argument must be a literal integer, got {other:?}"
3500 )));
3501 }
3502 };
3503 let is_called = if args.len() == 3 {
3504 match &args[2] {
3505 Expr::Literal(spg_sql::ast::Literal::Bool(b)) => *b,
3506 other => {
3507 return Err(EngineError::Unsupported(alloc::format!(
3508 "setval() is_called argument must be a literal BOOL, got {other:?}"
3509 )));
3510 }
3511 }
3512 } else {
3513 true
3514 };
3515 let v = self
3516 .active_catalog_mut()
3517 .sequence_set_value(&seq_name, value, is_called)
3518 .map_err(EngineError::Storage)?;
3519 Ok(Value::BigInt(v))
3520 }
3521 other => Err(EngineError::Unsupported(alloc::format!(
3522 "unknown sequence op {other:?}"
3523 ))),
3524 }
3525 }
3526
3527 fn expand_views_in_select(
3536 &self,
3537 stmt: &SelectStatement,
3538 ) -> Result<Option<SelectStatement>, EngineError> {
3539 let cat = self.active_catalog();
3540 let mut referenced: Vec<String> = Vec::new();
3541 if let Some(from) = &stmt.from {
3542 collect_view_refs(&from.primary, cat, &mut referenced);
3543 for j in &from.joins {
3544 collect_view_refs(&j.table, cat, &mut referenced);
3545 }
3546 }
3547 referenced.retain(|n| !stmt.ctes.iter().any(|c| c.name == *n));
3550 if referenced.is_empty() {
3551 return Ok(None);
3552 }
3553 let mut new_ctes: Vec<spg_sql::ast::Cte> = Vec::with_capacity(referenced.len());
3554 for name in &referenced {
3555 let view = cat.views().get(name).ok_or_else(|| {
3556 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3557 "view {name:?} disappeared mid-expansion"
3558 )))
3559 })?;
3560 let parsed = spg_sql::parser::parse_statement(&view.body).map_err(|e| {
3561 EngineError::Unsupported(alloc::format!("view {name:?} body re-parse failed: {e}"))
3562 })?;
3563 let Statement::Select(body) = parsed else {
3564 return Err(EngineError::Unsupported(alloc::format!(
3565 "view {name:?} body is not a SELECT (catalog corruption)"
3566 )));
3567 };
3568 new_ctes.push(spg_sql::ast::Cte {
3569 name: name.clone(),
3570 body,
3571 recursive: false,
3572 column_overrides: view.columns.clone(),
3573 });
3574 }
3575 let mut out = stmt.clone();
3576 new_ctes.extend(out.ctes);
3578 out.ctes = new_ctes;
3579 Ok(Some(out))
3580 }
3581
3582 fn exec_create_view(
3586 &mut self,
3587 s: spg_sql::ast::CreateViewStatement,
3588 ) -> Result<QueryResult, EngineError> {
3589 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body));
3593 let def = spg_storage::ViewDef {
3594 name: s.name.clone(),
3595 columns: s.columns,
3596 body: body_repr,
3597 };
3598 self.active_catalog_mut()
3599 .create_view(def, s.or_replace, s.if_not_exists)
3600 .map_err(EngineError::Storage)?;
3601 Ok(QueryResult::CommandOk {
3602 affected: 0,
3603 modified_catalog: !self.in_transaction(),
3604 })
3605 }
3606
3607 fn exec_create_type(
3612 &mut self,
3613 s: spg_sql::ast::CreateTypeStatement,
3614 ) -> Result<QueryResult, EngineError> {
3615 let cat = self.active_catalog();
3618 if cat.get(&s.name).is_some() {
3619 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3620 alloc::format!("type {:?} would shadow an existing table", s.name),
3621 )));
3622 }
3623 if cat.sequences().contains_key(&s.name) {
3624 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3625 alloc::format!("type {:?} would shadow an existing sequence", s.name),
3626 )));
3627 }
3628 if cat.views().contains_key(&s.name) {
3629 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3630 alloc::format!("type {:?} would shadow an existing view", s.name),
3631 )));
3632 }
3633 let def = match s.kind {
3634 spg_sql::ast::TypeKind::Enum { labels } => {
3635 if labels.is_empty() {
3636 return Err(EngineError::Unsupported(
3637 "CREATE TYPE … AS ENUM requires at least one label".into(),
3638 ));
3639 }
3640 for i in 0..labels.len() {
3642 for j in (i + 1)..labels.len() {
3643 if labels[i] == labels[j] {
3644 return Err(EngineError::Unsupported(alloc::format!(
3645 "CREATE TYPE {:?}: duplicate ENUM label {:?}",
3646 s.name,
3647 labels[i]
3648 )));
3649 }
3650 }
3651 }
3652 spg_storage::EnumDef {
3653 name: s.name.clone(),
3654 labels,
3655 }
3656 }
3657 };
3658 self.active_catalog_mut()
3659 .create_enum_type(def)
3660 .map_err(EngineError::Storage)?;
3661 Ok(QueryResult::CommandOk {
3662 affected: 0,
3663 modified_catalog: !self.in_transaction(),
3664 })
3665 }
3666
3667 fn exec_create_domain(
3672 &mut self,
3673 s: spg_sql::ast::CreateDomainStatement,
3674 ) -> Result<QueryResult, EngineError> {
3675 let cat = self.active_catalog();
3676 if cat.domain_types().contains_key(&s.name) {
3677 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3678 alloc::format!("domain {:?} already exists", s.name),
3679 )));
3680 }
3681 if cat.get(&s.name).is_some()
3682 || cat.sequences().contains_key(&s.name)
3683 || cat.views().contains_key(&s.name)
3684 || cat.enum_types().contains_key(&s.name)
3685 {
3686 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3687 alloc::format!("domain {:?} would shadow an existing object", s.name),
3688 )));
3689 }
3690 let base_type = column_type_to_data_type(s.base_type);
3691 let default = s.default.as_ref().map(|e| alloc::format!("{e}"));
3692 let checks = s
3693 .checks
3694 .iter()
3695 .map(|e| alloc::format!("{e}"))
3696 .collect::<Vec<_>>();
3697 let def = spg_storage::DomainDef {
3698 name: s.name.clone(),
3699 base_type,
3700 nullable: !s.not_null,
3701 default,
3702 checks,
3703 };
3704 self.active_catalog_mut()
3705 .create_domain_type(def)
3706 .map_err(EngineError::Storage)?;
3707 Ok(QueryResult::CommandOk {
3708 affected: 0,
3709 modified_catalog: !self.in_transaction(),
3710 })
3711 }
3712
3713 fn exec_drop_domain(
3715 &mut self,
3716 names: &[String],
3717 if_exists: bool,
3718 ) -> Result<QueryResult, EngineError> {
3719 let mut removed = 0usize;
3720 for name in names {
3721 let was_present = self.active_catalog_mut().drop_domain_type(name);
3722 if was_present {
3723 removed += 1;
3724 } else if !if_exists {
3725 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3726 alloc::format!("domain {name:?} does not exist"),
3727 )));
3728 }
3729 }
3730 Ok(QueryResult::CommandOk {
3731 affected: removed,
3732 modified_catalog: removed > 0 && !self.in_transaction(),
3733 })
3734 }
3735
3736 fn exec_create_schema(
3742 &mut self,
3743 name: String,
3744 if_not_exists: bool,
3745 ) -> Result<QueryResult, EngineError> {
3746 self.active_catalog_mut()
3747 .create_schema(name, if_not_exists)
3748 .map_err(EngineError::Storage)?;
3749 Ok(QueryResult::CommandOk {
3750 affected: 0,
3751 modified_catalog: !self.in_transaction(),
3752 })
3753 }
3754
3755 fn exec_drop_schema(
3759 &mut self,
3760 names: &[String],
3761 if_exists: bool,
3762 ) -> Result<QueryResult, EngineError> {
3763 let mut removed = 0usize;
3764 for name in names {
3765 let was_present = self
3766 .active_catalog_mut()
3767 .drop_schema(name)
3768 .map_err(EngineError::Storage)?;
3769 if was_present {
3770 removed += 1;
3771 } else if !if_exists {
3772 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3773 alloc::format!("schema {name:?} does not exist"),
3774 )));
3775 }
3776 }
3777 Ok(QueryResult::CommandOk {
3778 affected: removed,
3779 modified_catalog: removed > 0 && !self.in_transaction(),
3780 })
3781 }
3782
3783 fn exec_drop_type(
3788 &mut self,
3789 names: &[String],
3790 if_exists: bool,
3791 ) -> Result<QueryResult, EngineError> {
3792 let mut removed = 0usize;
3793 for name in names {
3794 let was_present = self.active_catalog_mut().drop_enum_type(name);
3795 if was_present {
3796 removed += 1;
3797 } else if !if_exists {
3798 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3799 alloc::format!("type {name:?} does not exist"),
3800 )));
3801 }
3802 }
3803 Ok(QueryResult::CommandOk {
3804 affected: removed,
3805 modified_catalog: removed > 0 && !self.in_transaction(),
3806 })
3807 }
3808
3809 fn exec_create_materialized_view(
3814 &mut self,
3815 s: spg_sql::ast::CreateMaterializedViewStatement,
3816 ) -> Result<QueryResult, EngineError> {
3817 let cat = self.active_catalog();
3819 if cat.materialized_views().contains_key(&s.name) || cat.get(&s.name).is_some() {
3820 if s.if_not_exists {
3821 return Ok(QueryResult::CommandOk {
3822 affected: 0,
3823 modified_catalog: false,
3824 });
3825 }
3826 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3827 alloc::format!("materialized view {:?} already exists", s.name),
3828 )));
3829 }
3830 if cat.views().contains_key(&s.name) {
3831 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3832 alloc::format!(
3833 "materialized view {:?} would shadow an existing view",
3834 s.name
3835 ),
3836 )));
3837 }
3838 if cat.sequences().contains_key(&s.name) {
3839 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3840 alloc::format!(
3841 "materialized view {:?} would shadow an existing sequence",
3842 s.name
3843 ),
3844 )));
3845 }
3846 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body.clone()));
3848 let result = self.exec_select_cancel(&s.body, CancelToken::none())?;
3853 let (mut cols, rows) = match result {
3854 QueryResult::Rows { columns, rows } => (columns, rows),
3855 other => {
3856 return Err(EngineError::Unsupported(alloc::format!(
3857 "CREATE MATERIALIZED VIEW body did not return rows: {other:?}"
3858 )));
3859 }
3860 };
3861 if !s.columns.is_empty() {
3863 if s.columns.len() != cols.len() {
3864 return Err(EngineError::Unsupported(alloc::format!(
3865 "CREATE MATERIALIZED VIEW {:?}: column list has {} names but body returns {}",
3866 s.name,
3867 s.columns.len(),
3868 cols.len()
3869 )));
3870 }
3871 for (c, name) in cols.iter_mut().zip(s.columns.iter()) {
3872 c.name.clone_from(name);
3873 }
3874 }
3875 cols = infer_column_types(&cols, &rows);
3878 let schema = spg_storage::TableSchema::new(s.name.clone(), cols);
3879 let cat = self.active_catalog_mut();
3880 cat.create_table(schema).map_err(EngineError::Storage)?;
3881 if s.with_data {
3882 let table = cat
3883 .get_mut(&s.name)
3884 .expect("just-created materialized-view backing table must exist");
3885 for row in rows {
3886 table.insert(row).map_err(EngineError::Storage)?;
3887 }
3888 }
3889 cat.register_materialized_view(s.name.clone(), body_repr);
3890 Ok(QueryResult::CommandOk {
3891 affected: 0,
3892 modified_catalog: !self.in_transaction(),
3893 })
3894 }
3895
3896 fn exec_refresh_materialized_view(
3900 &mut self,
3901 name: &str,
3902 with_data: bool,
3903 ) -> Result<QueryResult, EngineError> {
3904 let source = self
3905 .active_catalog()
3906 .materialized_views()
3907 .get(name)
3908 .cloned()
3909 .ok_or_else(|| {
3910 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3911 "materialized view {name:?} does not exist"
3912 )))
3913 })?;
3914 {
3917 let cat = self.active_catalog_mut();
3918 let table = cat.get_mut(name).ok_or_else(|| {
3919 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3920 "materialized view {name:?} backing table missing"
3921 )))
3922 })?;
3923 table.truncate();
3924 }
3925 if !with_data {
3926 return Ok(QueryResult::CommandOk {
3927 affected: 0,
3928 modified_catalog: !self.in_transaction(),
3929 });
3930 }
3931 let parsed = spg_sql::parser::parse_statement(&source).map_err(|e| {
3932 EngineError::Unsupported(alloc::format!(
3933 "materialized view {name:?} body re-parse failed: {e}"
3934 ))
3935 })?;
3936 let Statement::Select(body) = parsed else {
3937 return Err(EngineError::Unsupported(alloc::format!(
3938 "materialized view {name:?} body is not a SELECT (catalog corruption)"
3939 )));
3940 };
3941 let rows = match self.exec_select_cancel(&body, CancelToken::none())? {
3942 QueryResult::Rows { rows, .. } => rows,
3943 other => {
3944 return Err(EngineError::Unsupported(alloc::format!(
3945 "REFRESH MATERIALIZED VIEW {name:?} body did not return rows: {other:?}"
3946 )));
3947 }
3948 };
3949 let cat = self.active_catalog_mut();
3950 let table = cat.get_mut(name).expect("backing table verified above");
3951 let affected = rows.len();
3952 for row in rows {
3953 table.insert(row).map_err(EngineError::Storage)?;
3954 }
3955 Ok(QueryResult::CommandOk {
3956 affected,
3957 modified_catalog: !self.in_transaction(),
3958 })
3959 }
3960
3961 fn exec_drop_materialized_view(
3964 &mut self,
3965 names: &[String],
3966 if_exists: bool,
3967 ) -> Result<QueryResult, EngineError> {
3968 let mut removed = 0usize;
3969 for name in names {
3970 let was_present = self
3971 .active_catalog_mut()
3972 .drop_materialized_view_source(name);
3973 if was_present {
3974 self.active_catalog_mut().drop_table(name);
3976 removed += 1;
3977 } else if !if_exists {
3978 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3979 alloc::format!("materialized view {name:?} does not exist"),
3980 )));
3981 }
3982 }
3983 Ok(QueryResult::CommandOk {
3984 affected: removed,
3985 modified_catalog: removed > 0 && !self.in_transaction(),
3986 })
3987 }
3988
3989 fn exec_drop_view(
3991 &mut self,
3992 names: &[String],
3993 if_exists: bool,
3994 ) -> Result<QueryResult, EngineError> {
3995 let mut removed = 0usize;
3996 for name in names {
3997 let was_present = self.active_catalog_mut().drop_view(name);
3998 if !was_present && !if_exists {
3999 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
4000 alloc::format!("view {name:?} does not exist"),
4001 )));
4002 }
4003 if was_present {
4004 removed += 1;
4005 }
4006 }
4007 Ok(QueryResult::CommandOk {
4008 affected: removed,
4009 modified_catalog: removed > 0 && !self.in_transaction(),
4010 })
4011 }
4012
4013 fn exec_drop_sequence(
4015 &mut self,
4016 names: &[String],
4017 if_exists: bool,
4018 ) -> Result<QueryResult, EngineError> {
4019 let mut removed = 0usize;
4020 for name in names {
4021 let was_present = self.active_catalog_mut().drop_sequence(name);
4022 if !was_present && !if_exists {
4023 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
4024 alloc::format!("sequence {name:?} does not exist"),
4025 )));
4026 }
4027 if was_present {
4028 removed += 1;
4029 }
4030 }
4031 Ok(QueryResult::CommandOk {
4032 affected: removed,
4033 modified_catalog: removed > 0 && !self.in_transaction(),
4034 })
4035 }
4036
4037 fn exec_update_cancel(
4044 &mut self,
4045 stmt: &spg_sql::ast::UpdateStatement,
4046 cancel: CancelToken<'_>,
4047 ) -> Result<QueryResult, EngineError> {
4048 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
4057 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
4058 let trigger_session_cfg: Option<String> = self
4059 .session_params
4060 .get("default_text_search_config")
4061 .cloned();
4062 if let Some(w) = &stmt.where_ {
4070 let schema_cols = self
4071 .active_catalog()
4072 .get(&stmt.table)
4073 .ok_or_else(|| {
4074 EngineError::Storage(StorageError::TableNotFound {
4075 name: stmt.table.clone(),
4076 })
4077 })?
4078 .schema()
4079 .columns
4080 .clone();
4081 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4082 && let Some(idx_name) = self
4083 .active_catalog()
4084 .get(&stmt.table)
4085 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4086 {
4087 let _ = self
4091 .active_catalog_mut()
4092 .promote_cold_row(&stmt.table, &idx_name, &key);
4093 }
4094 }
4095
4096 let ts_cfg: Option<String> = self
4099 .session_param("default_text_search_config")
4100 .map(String::from);
4101 let clock_for_on_update = self.clock;
4105 let table = self
4106 .active_catalog_mut()
4107 .get_mut(&stmt.table)
4108 .ok_or_else(|| {
4109 EngineError::Storage(StorageError::TableNotFound {
4110 name: stmt.table.clone(),
4111 })
4112 })?;
4113 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4114 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
4118 for (col, expr) in &stmt.assignments {
4119 let pos = schema_cols
4120 .iter()
4121 .position(|c| c.name == *col)
4122 .ok_or_else(|| {
4123 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
4124 })?;
4125 targets.push((pos, expr));
4126 }
4127 let mut on_update_overrides: Vec<(usize, String)> = Vec::new();
4135 for (i, col) in schema_cols.iter().enumerate() {
4136 if targets.iter().any(|(p, _)| *p == i) {
4137 continue;
4138 }
4139 if let Some(src) = &col.on_update_runtime {
4140 on_update_overrides.push((i, src.clone()));
4141 }
4142 }
4143 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4144 .with_default_text_search_config(ts_cfg.as_deref());
4145 let seek_positions: Option<Vec<usize>> = stmt
4160 .where_
4161 .as_ref()
4162 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4163 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
4164 let candidate_positions: Vec<usize> = match &seek_positions {
4165 Some(list) => list.clone(),
4166 None => (0..table.row_count()).collect(),
4167 };
4168 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4169 if loop_n.is_multiple_of(256) {
4173 cancel.check()?;
4174 }
4175 let Some(row) = table.rows().get(i) else {
4176 continue;
4177 };
4178 if let Some(w) = &stmt.where_ {
4179 let cond = eval::eval_expr(w, row, &ctx)?;
4180 if !matches!(cond, Value::Bool(true)) {
4181 continue;
4182 }
4183 }
4184 let mut new_vals = row.values.clone();
4185 for (pos, expr) in &targets {
4186 let v = eval::eval_expr(expr, row, &ctx)?;
4187 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
4188 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
4189 new_vals[*pos] = coerced;
4190 }
4191 for (pos, src) in &on_update_overrides {
4194 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4195 new_vals[*pos] = v;
4196 }
4197 planned.push((i, new_vals));
4198 }
4199 planned.sort_by_key(|(i, _)| *i);
4204 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4208 .iter()
4209 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4210 .collect();
4211 let self_fks = table.schema().foreign_keys.clone();
4212 let _ = table;
4217 if !self_fks.is_empty() {
4221 let new_rows: Vec<Vec<Value>> = planned
4222 .iter()
4223 .map(|(_pos, new_vals)| new_vals.clone())
4224 .collect();
4225 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4226 }
4227 {
4231 let new_rows: Vec<Vec<Value>> = planned
4232 .iter()
4233 .map(|(_pos, new_vals)| new_vals.clone())
4234 .collect();
4235 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4236 }
4237 let child_plan =
4241 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4242 for step in &child_plan {
4244 apply_fk_child_step(self.active_catalog_mut(), step)?;
4245 }
4246 let table = self
4248 .active_catalog_mut()
4249 .get_mut(&stmt.table)
4250 .ok_or_else(|| {
4251 EngineError::Storage(StorageError::TableNotFound {
4252 name: stmt.table.clone(),
4253 })
4254 })?;
4255 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4265 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4267 for (pos, new_vals) in &planned {
4268 let old_row = table.rows()[*pos].clone();
4269 let mut new_row = Row::new(new_vals.clone());
4270 let mut skip = false;
4271 for (fd, filter) in &before_update_triggers {
4272 if !filter.is_empty()
4277 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4278 {
4279 continue;
4280 }
4281 let (outcome, deferred) = triggers::fire_row_trigger(
4282 fd,
4283 Some(new_row.clone()),
4284 Some(&old_row),
4285 &stmt.table,
4286 &schema_cols,
4287 &[],
4288 trigger_session_cfg.as_deref(),
4289 false,
4290 )
4291 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4292 deferred_embedded.extend(deferred);
4293 match outcome {
4294 triggers::TriggerOutcome::Row(r) => new_row = r,
4295 triggers::TriggerOutcome::Skip => {
4296 skip = true;
4297 break;
4298 }
4299 }
4300 }
4301 if !skip {
4302 applied_after_before.push((*pos, new_row, old_row));
4303 }
4304 }
4305 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4308 applied_after_before
4309 .iter()
4310 .map(|(_pos, new_row, _old)| new_row.values.clone())
4311 .collect()
4312 } else {
4313 Vec::new()
4314 };
4315 let affected = applied_after_before.len();
4316 for (pos, new_row, old_row) in applied_after_before {
4320 table.update_row(pos, new_row.values.clone())?;
4321 for (fd, filter) in &after_update_triggers {
4322 if !filter.is_empty()
4323 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4324 {
4325 continue;
4326 }
4327 let (_outcome, deferred) = triggers::fire_row_trigger(
4328 fd,
4329 Some(new_row.clone()),
4330 Some(&old_row),
4331 &stmt.table,
4332 &schema_cols,
4333 &[],
4334 trigger_session_cfg.as_deref(),
4335 true,
4336 )
4337 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4338 deferred_embedded.extend(deferred);
4339 }
4340 }
4341 let _ = table;
4342 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4344 if !self.in_transaction() && affected > 0 {
4346 self.statistics
4347 .record_modifications(&stmt.table, affected as u64);
4348 }
4349 if let Some(items) = &stmt.returning {
4351 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4352 }
4353 Ok(QueryResult::CommandOk {
4354 affected,
4355 modified_catalog: !self.in_transaction(),
4356 })
4357 }
4358
4359 fn exec_merge_cancel(
4390 &mut self,
4391 stmt: &spg_sql::ast::MergeStatement,
4392 cancel: CancelToken<'_>,
4393 ) -> Result<QueryResult, EngineError> {
4394 let target_alias = stmt
4395 .target_alias
4396 .clone()
4397 .unwrap_or_else(|| stmt.target.clone());
4398 let source_alias = stmt
4399 .source_alias
4400 .clone()
4401 .unwrap_or_else(|| stmt.source.clone());
4402 let (target_cols, target_rows_snapshot) = {
4403 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4404 EngineError::Storage(StorageError::TableNotFound {
4405 name: stmt.target.clone(),
4406 })
4407 })?;
4408 (
4409 t.schema().columns.clone(),
4410 t.rows().iter().cloned().collect::<Vec<Row>>(),
4411 )
4412 };
4413 let (source_cols, source_rows) = {
4414 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4415 EngineError::Storage(StorageError::TableNotFound {
4416 name: stmt.source.clone(),
4417 })
4418 })?;
4419 (
4420 s.schema().columns.clone(),
4421 s.rows().iter().cloned().collect::<Vec<Row>>(),
4422 )
4423 };
4424 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4426 for col in &target_cols {
4427 combined_schema.push(ColumnSchema::new(
4428 alloc::format!("{target_alias}.{}", col.name),
4429 col.ty,
4430 col.nullable,
4431 ));
4432 }
4433 for col in &source_cols {
4434 combined_schema.push(ColumnSchema::new(
4435 alloc::format!("{source_alias}.{}", col.name),
4436 col.ty,
4437 col.nullable,
4438 ));
4439 }
4440 let combined_ctx = EvalContext::new(&combined_schema, None);
4441 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4445 for col in &target_cols {
4446 source_only_schema.push(ColumnSchema::new(
4447 alloc::format!("{target_alias}.{}", col.name),
4448 col.ty,
4449 col.nullable,
4450 ));
4451 }
4452 for col in &source_cols {
4453 source_only_schema.push(ColumnSchema::new(
4454 alloc::format!("{source_alias}.{}", col.name),
4455 col.ty,
4456 col.nullable,
4457 ));
4458 }
4459 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4460 let target_arity = target_cols.len();
4461 let source_arity = source_cols.len();
4462
4463 let mut delete_indices: Vec<usize> = Vec::new();
4466 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4467 let mut inserts: Vec<Vec<Value>> = Vec::new();
4468 let mut affected: usize = 0;
4469
4470 for (src_idx, src_row) in source_rows.iter().enumerate() {
4471 if src_idx.is_multiple_of(256) {
4472 cancel.check()?;
4473 }
4474 let mut matched_targets: Vec<usize> = Vec::new();
4476 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4477 let mut combined_vals = t_row.values.clone();
4478 combined_vals.extend(src_row.values.iter().cloned());
4479 let combined_row = Row::new(combined_vals);
4480 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4481 if matches!(cond, Value::Bool(true)) {
4482 matched_targets.push(t_idx);
4483 }
4484 }
4485 let is_matched = !matched_targets.is_empty();
4486 let fired_clause = stmt.clauses.iter().find(|c| {
4492 let kind_ok = match c.matched {
4493 spg_sql::ast::MergeMatched::Matched => is_matched,
4494 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4495 };
4496 if !kind_ok {
4497 return false;
4498 }
4499 let Some(cond_expr) = &c.condition else {
4500 return true;
4501 };
4502 let row = if is_matched {
4503 let t = &target_rows_snapshot[matched_targets[0]];
4504 let mut vals = t.values.clone();
4505 vals.extend(src_row.values.iter().cloned());
4506 Row::new(vals)
4507 } else {
4508 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4509 vals.extend(src_row.values.iter().cloned());
4510 Row::new(vals)
4511 };
4512 let ctx_ref = if is_matched {
4513 &combined_ctx
4514 } else {
4515 &source_only_ctx
4516 };
4517 matches!(
4518 eval::eval_expr(cond_expr, &row, ctx_ref),
4519 Ok(Value::Bool(true))
4520 )
4521 });
4522 let Some(clause) = fired_clause else { continue };
4523 match &clause.action {
4524 spg_sql::ast::MergeAction::DoNothing => {}
4525 spg_sql::ast::MergeAction::Delete => {
4526 for &t_idx in &matched_targets {
4527 if !delete_indices.contains(&t_idx) {
4528 delete_indices.push(t_idx);
4529 affected += 1;
4530 }
4531 }
4532 }
4533 spg_sql::ast::MergeAction::Update { assignments } => {
4534 let mut planned_sets: Vec<(usize, &Expr)> =
4536 Vec::with_capacity(assignments.len());
4537 for (col, expr) in assignments {
4538 let pos =
4539 target_cols
4540 .iter()
4541 .position(|c| c.name == *col)
4542 .ok_or_else(|| {
4543 EngineError::Eval(EvalError::ColumnNotFound {
4544 name: col.clone(),
4545 })
4546 })?;
4547 planned_sets.push((pos, expr));
4548 }
4549 for &t_idx in &matched_targets {
4550 let t_row = &target_rows_snapshot[t_idx];
4551 let mut new_values = t_row.values.clone();
4552 let mut combined_vals = t_row.values.clone();
4553 combined_vals.extend(src_row.values.iter().cloned());
4554 let combined_row = Row::new(combined_vals);
4555 for (pos, expr) in &planned_sets {
4556 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4557 let coerced = coerce_value(
4558 raw,
4559 target_cols[*pos].ty,
4560 &target_cols[*pos].name,
4561 *pos,
4562 )?;
4563 new_values[*pos] = coerced;
4564 }
4565 updates.push((t_idx, new_values));
4566 affected += 1;
4567 }
4568 }
4569 spg_sql::ast::MergeAction::Insert { columns, values } => {
4570 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4572 vals.extend(src_row.values.iter().cloned());
4573 let synth_row = Row::new(vals);
4574 let mut new_row_values: Vec<Value> =
4575 (0..target_arity).map(|_| Value::Null).collect();
4576 for (col, expr) in columns.iter().zip(values.iter()) {
4577 let pos =
4578 target_cols
4579 .iter()
4580 .position(|c| c.name == *col)
4581 .ok_or_else(|| {
4582 EngineError::Eval(EvalError::ColumnNotFound {
4583 name: col.clone(),
4584 })
4585 })?;
4586 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4587 let coerced =
4588 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4589 new_row_values[pos] = coerced;
4590 }
4591 inserts.push(new_row_values);
4592 affected += 1;
4593 }
4594 }
4595 }
4596 let _ = source_arity; let table = self
4600 .active_catalog_mut()
4601 .get_mut(&stmt.target)
4602 .ok_or_else(|| {
4603 EngineError::Storage(StorageError::TableNotFound {
4604 name: stmt.target.clone(),
4605 })
4606 })?;
4607 for (idx, new_vals) in &updates {
4611 table
4612 .update_row(*idx, new_vals.clone())
4613 .map_err(EngineError::Storage)?;
4614 }
4615 if !delete_indices.is_empty() {
4616 table.delete_rows(&delete_indices);
4617 }
4618 for vals in inserts {
4619 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4620 }
4621 Ok(QueryResult::CommandOk {
4622 affected,
4623 modified_catalog: affected > 0,
4624 })
4625 }
4626
4627 fn exec_delete_cancel(
4628 &mut self,
4629 stmt: &spg_sql::ast::DeleteStatement,
4630 cancel: CancelToken<'_>,
4631 ) -> Result<QueryResult, EngineError> {
4632 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4636 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4637 let trigger_session_cfg: Option<String> = self
4638 .session_params
4639 .get("default_text_search_config")
4640 .cloned();
4641 let mut cold_shadow_count: usize = 0;
4649 if let Some(w) = &stmt.where_ {
4650 let schema_cols = self
4651 .active_catalog()
4652 .get(&stmt.table)
4653 .ok_or_else(|| {
4654 EngineError::Storage(StorageError::TableNotFound {
4655 name: stmt.table.clone(),
4656 })
4657 })?
4658 .schema()
4659 .columns
4660 .clone();
4661 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4662 && let Some(idx_name) = self
4663 .active_catalog()
4664 .get(&stmt.table)
4665 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4666 {
4667 cold_shadow_count = self
4668 .active_catalog_mut()
4669 .shadow_cold_row(&stmt.table, &idx_name, &key)
4670 .unwrap_or(0);
4671 }
4672 }
4673
4674 let ts_cfg: Option<String> = self
4680 .session_param("default_text_search_config")
4681 .map(String::from);
4682 let table = self
4683 .active_catalog_mut()
4684 .get_mut(&stmt.table)
4685 .ok_or_else(|| {
4686 EngineError::Storage(StorageError::TableNotFound {
4687 name: stmt.table.clone(),
4688 })
4689 })?;
4690 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4691 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4692 .with_default_text_search_config(ts_cfg.as_deref());
4693 let mut positions: Vec<usize> = Vec::new();
4694 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4698 let seek_positions: Option<Vec<usize>> = stmt
4704 .where_
4705 .as_ref()
4706 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4707 let candidate_positions: Vec<usize> = match seek_positions {
4708 Some(mut list) => {
4709 list.sort_unstable();
4710 list
4711 }
4712 None => (0..table.row_count()).collect(),
4713 };
4714 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4715 if loop_n.is_multiple_of(256) {
4716 cancel.check()?;
4717 }
4718 let Some(row) = table.rows().get(i) else {
4719 continue;
4720 };
4721 let keep = if let Some(w) = &stmt.where_ {
4722 let cond = eval::eval_expr(w, row, &ctx)?;
4723 !matches!(cond, Value::Bool(true))
4724 } else {
4725 false
4726 };
4727 if !keep {
4728 positions.push(i);
4729 to_delete_rows.push(row.values.clone());
4730 }
4731 }
4732 let _ = table;
4739 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4747 if !before_delete_triggers.is_empty() {
4748 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4749 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4750 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4751 let old_row = Row::new(old_vals.clone());
4752 let mut cancel_this = false;
4753 for fd in &before_delete_triggers {
4754 let (outcome, deferred) = triggers::fire_row_trigger(
4755 fd,
4756 None,
4757 Some(&old_row),
4758 &stmt.table,
4759 &schema_cols,
4760 &[],
4761 trigger_session_cfg.as_deref(),
4762 false,
4763 )
4764 .map_err(|e| {
4765 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4766 })?;
4767 deferred_embedded.extend(deferred);
4768 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4769 cancel_this = true;
4770 break;
4771 }
4772 }
4773 if !cancel_this {
4774 filtered_positions.push(*pos);
4775 filtered_old_rows.push(old_vals.clone());
4776 }
4777 }
4778 positions = filtered_positions;
4779 to_delete_rows = filtered_old_rows;
4780 }
4781 let cascade_plan = plan_fk_parent_deletions(
4782 self.active_catalog(),
4783 &stmt.table,
4784 &positions,
4785 &to_delete_rows,
4786 )?;
4787 for step in &cascade_plan {
4794 apply_fk_child_step(self.active_catalog_mut(), step)?;
4795 }
4796 let table = self
4798 .active_catalog_mut()
4799 .get_mut(&stmt.table)
4800 .ok_or_else(|| {
4801 EngineError::Storage(StorageError::TableNotFound {
4802 name: stmt.table.clone(),
4803 })
4804 })?;
4805 let affected = table.delete_rows(&positions) + cold_shadow_count;
4806 let _ = table;
4807 if !after_delete_triggers.is_empty() {
4812 for old_vals in &to_delete_rows {
4813 let old_row = Row::new(old_vals.clone());
4814 for fd in &after_delete_triggers {
4815 let (_outcome, deferred) = triggers::fire_row_trigger(
4816 fd,
4817 None,
4818 Some(&old_row),
4819 &stmt.table,
4820 &schema_cols,
4821 &[],
4822 trigger_session_cfg.as_deref(),
4823 true,
4824 )
4825 .map_err(|e| {
4826 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4827 })?;
4828 deferred_embedded.extend(deferred);
4829 }
4830 }
4831 }
4832 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4834 if !self.in_transaction() && affected > 0 {
4836 self.statistics
4837 .record_modifications(&stmt.table, affected as u64);
4838 }
4839 if let Some(items) = &stmt.returning {
4845 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4846 }
4847 Ok(QueryResult::CommandOk {
4848 affected,
4849 modified_catalog: !self.in_transaction(),
4850 })
4851 }
4852
4853 #[allow(clippy::format_push_string)]
4863 fn exec_explain(
4864 &self,
4865 e: &spg_sql::ast::ExplainStatement,
4866 cancel: CancelToken<'_>,
4867 ) -> Result<QueryResult, EngineError> {
4868 let mut lines = Vec::<String>::new();
4869 explain_select(&e.inner, self, 0, &mut lines);
4870 if e.suggest {
4871 let suggestions = build_index_suggestions(&e.inner, self);
4880 for s in suggestions {
4881 lines.push(s);
4882 }
4883 } else if e.analyze {
4884 let started = self.clock.map(|f| f());
4901 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4902 let elapsed_micros = match (self.clock, started) {
4903 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4904 _ => None,
4905 };
4906 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4907 rows.len()
4908 } else {
4909 0
4910 };
4911 annotate_explain_lines(&mut lines, row_count, self);
4912 let mut total = alloc::format!("Total: rows={row_count}");
4913 if let Some(us) = elapsed_micros {
4914 total.push_str(&alloc::format!(" elapsed={us}us"));
4915 }
4916 lines.push(total);
4917 }
4918 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
4919 let rows: Vec<Row> = lines
4920 .into_iter()
4921 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
4922 .collect();
4923 Ok(QueryResult::Rows { columns, rows })
4924 }
4925
4926 fn exec_show_tables(&self) -> QueryResult {
4927 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
4928 let rows: Vec<Row> = self
4929 .active_catalog()
4930 .table_names()
4931 .into_iter()
4932 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
4933 .collect();
4934 QueryResult::Rows { columns, rows }
4935 }
4936
4937 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
4942 let t = self.active_catalog().get(name).ok_or_else(|| {
4943 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4944 })?;
4945 let cols: Vec<String> = t
4946 .schema()
4947 .columns
4948 .iter()
4949 .map(|c| {
4950 let ty = render_data_type(c.ty);
4951 let nullable = if c.nullable { "" } else { " NOT NULL" };
4952 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
4953 })
4954 .collect();
4955 let mut body = cols.join(",\n");
4956 for uc in &t.schema().uniqueness_constraints {
4958 let col_names: Vec<String> = uc
4959 .columns
4960 .iter()
4961 .map(|&p| {
4962 t.schema().columns.get(p).map_or_else(
4963 || alloc::format!("col{p}"),
4964 |c| alloc::format!("`{}`", c.name),
4965 )
4966 })
4967 .collect();
4968 let kw = if uc.is_primary_key {
4969 "PRIMARY KEY"
4970 } else {
4971 "UNIQUE KEY"
4972 };
4973 body.push_str(",\n ");
4974 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
4975 }
4976 for fk in &t.schema().foreign_keys {
4978 let local: Vec<String> = fk
4979 .local_columns
4980 .iter()
4981 .map(|&p| {
4982 t.schema().columns.get(p).map_or_else(
4983 || alloc::format!("col{p}"),
4984 |c| alloc::format!("`{}`", c.name),
4985 )
4986 })
4987 .collect();
4988 let parent_cols: Vec<String> =
4989 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
4990 fk.parent_columns
4991 .iter()
4992 .map(|&p| {
4993 parent.schema().columns.get(p).map_or_else(
4994 || alloc::format!("col{p}"),
4995 |c| alloc::format!("`{}`", c.name),
4996 )
4997 })
4998 .collect()
4999 } else {
5000 fk.parent_columns
5001 .iter()
5002 .map(|p| alloc::format!("col{p}"))
5003 .collect()
5004 };
5005 body.push_str(",\n ");
5006 body.push_str(&alloc::format!(
5007 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
5008 local.join(", "),
5009 fk.parent_table,
5010 parent_cols.join(", ")
5011 ));
5012 }
5013 let ddl = alloc::format!(
5014 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
5015 name,
5016 body
5017 );
5018 let columns = alloc::vec![
5019 ColumnSchema::new("Table", DataType::Text, false),
5020 ColumnSchema::new("Create Table", DataType::Text, false),
5021 ];
5022 let rows = alloc::vec![Row::new(alloc::vec![
5023 Value::Text(name.into()),
5024 Value::Text(ddl),
5025 ])];
5026 Ok(QueryResult::Rows { columns, rows })
5027 }
5028
5029 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
5035 let t = self.active_catalog().get(name).ok_or_else(|| {
5036 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
5037 })?;
5038 let columns = alloc::vec![
5039 ColumnSchema::new("Table", DataType::Text, false),
5040 ColumnSchema::new("Non_unique", DataType::Int, false),
5041 ColumnSchema::new("Key_name", DataType::Text, false),
5042 ColumnSchema::new("Seq_in_index", DataType::Int, false),
5043 ColumnSchema::new("Column_name", DataType::Text, false),
5044 ColumnSchema::new("Null", DataType::Text, false),
5045 ColumnSchema::new("Index_type", DataType::Text, false),
5046 ];
5047 let mut rows: Vec<Row> = Vec::new();
5048 for idx in t.indices() {
5049 let col = t
5050 .schema()
5051 .columns
5052 .get(idx.column_position)
5053 .map_or("?".into(), |c| c.name.clone());
5054 let nullable = t
5055 .schema()
5056 .columns
5057 .get(idx.column_position)
5058 .map_or(true, |c| c.nullable);
5059 rows.push(Row::new(alloc::vec![
5060 Value::Text(name.into()),
5061 Value::Int(i32::from(!idx.is_unique)),
5062 Value::Text(idx.name.clone()),
5063 Value::Int(1),
5064 Value::Text(col),
5065 Value::Text(if nullable {
5066 "YES".into()
5067 } else {
5068 String::new()
5069 }),
5070 Value::Text("BTREE".into()),
5071 ]));
5072 }
5073 Ok(QueryResult::Rows { columns, rows })
5074 }
5075
5076 fn exec_show_status(&self) -> QueryResult {
5080 let columns = alloc::vec![
5081 ColumnSchema::new("Variable_name", DataType::Text, false),
5082 ColumnSchema::new("Value", DataType::Text, false),
5083 ];
5084 let pairs: &[(&str, &str)] = &[
5085 ("Uptime", "0"),
5086 ("Threads_connected", "1"),
5087 ("Threads_running", "1"),
5088 ("Questions", "0"),
5089 ("Slow_queries", "0"),
5090 ("Opened_tables", "0"),
5091 ("Innodb_buffer_pool_pages_total", "0"),
5092 ];
5093 let rows: Vec<Row> = pairs
5094 .iter()
5095 .map(|(k, v)| {
5096 Row::new(alloc::vec![
5097 Value::Text((*k).into()),
5098 Value::Text((*v).into())
5099 ])
5100 })
5101 .collect();
5102 QueryResult::Rows { columns, rows }
5103 }
5104
5105 fn exec_show_variables(&self) -> QueryResult {
5108 let columns = alloc::vec![
5109 ColumnSchema::new("Variable_name", DataType::Text, false),
5110 ColumnSchema::new("Value", DataType::Text, false),
5111 ];
5112 let mut rows: Vec<Row> = Vec::new();
5113 let canonical: &[(&str, &str)] = &[
5114 ("version", "8.0.35-spg"),
5115 ("version_comment", "SPG dual-stack engine"),
5116 ("character_set_server", "utf8mb4"),
5117 ("collation_server", "utf8mb4_0900_ai_ci"),
5118 ("max_allowed_packet", "67108864"),
5119 ("autocommit", "ON"),
5120 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
5121 ("time_zone", "SYSTEM"),
5122 ("transaction_isolation", "REPEATABLE-READ"),
5123 ];
5124 for &(k, v) in canonical {
5125 rows.push(Row::new(alloc::vec![
5126 Value::Text(k.into()),
5127 Value::Text(v.into()),
5128 ]));
5129 }
5130 for (k, v) in &self.session_params {
5132 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
5133 rows.push(Row::new(alloc::vec![
5134 Value::Text(k.clone()),
5135 Value::Text(v.clone()),
5136 ]));
5137 }
5138 }
5139 QueryResult::Rows { columns, rows }
5140 }
5141
5142 fn exec_show_processlist(&self) -> QueryResult {
5147 let columns = alloc::vec![
5148 ColumnSchema::new("Id", DataType::Int, false),
5149 ColumnSchema::new("User", DataType::Text, false),
5150 ColumnSchema::new("Host", DataType::Text, false),
5151 ColumnSchema::new("db", DataType::Text, true),
5152 ColumnSchema::new("Command", DataType::Text, false),
5153 ColumnSchema::new("Time", DataType::Int, false),
5154 ColumnSchema::new("State", DataType::Text, true),
5155 ColumnSchema::new("Info", DataType::Text, true),
5156 ];
5157 let rows = alloc::vec![Row::new(alloc::vec![
5158 Value::Int(1),
5159 Value::Text("postgres".into()),
5160 Value::Text("localhost".into()),
5161 Value::Text("postgres".into()),
5162 Value::Text("Query".into()),
5163 Value::Int(0),
5164 Value::Text("executing".into()),
5165 Value::Text("SHOW PROCESSLIST".into()),
5166 ])];
5167 QueryResult::Rows { columns, rows }
5168 }
5169
5170 fn exec_show_databases(&self) -> QueryResult {
5177 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
5178 let names = [
5179 "information_schema",
5180 "mysql",
5181 "performance_schema",
5182 "sys",
5183 "postgres",
5184 ];
5185 let rows: Vec<Row> = names
5186 .iter()
5187 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
5188 .collect();
5189 QueryResult::Rows { columns, rows }
5190 }
5191
5192 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
5195 let table =
5196 self.active_catalog()
5197 .get(table_name)
5198 .ok_or_else(|| StorageError::TableNotFound {
5199 name: table_name.into(),
5200 })?;
5201 let columns = alloc::vec![
5202 ColumnSchema::new("name", DataType::Text, false),
5203 ColumnSchema::new("type", DataType::Text, false),
5204 ColumnSchema::new("nullable", DataType::Bool, false),
5205 ];
5206 let rows: Vec<Row> = table
5207 .schema()
5208 .columns
5209 .iter()
5210 .map(|c| {
5211 Row::new(alloc::vec![
5212 Value::Text(c.name.clone()),
5213 Value::Text(alloc::format!("{}", c.ty)),
5214 Value::Bool(c.nullable),
5215 ])
5216 })
5217 .collect();
5218 Ok(QueryResult::Rows { columns, rows })
5219 }
5220
5221 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5222 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5223 if self.tx_catalogs.contains_key(&tx_id) {
5224 return Err(EngineError::TransactionAlreadyOpen);
5225 }
5226 self.tx_catalogs.insert(
5227 tx_id,
5228 TxState {
5229 catalog: self.catalog.clone(),
5230 savepoints: Vec::new(),
5231 },
5232 );
5233 Ok(QueryResult::CommandOk {
5234 affected: 0,
5235 modified_catalog: false,
5236 })
5237 }
5238
5239 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5240 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5241 let state = self
5242 .tx_catalogs
5243 .remove(&tx_id)
5244 .ok_or(EngineError::NoActiveTransaction)?;
5245 self.catalog = state.catalog;
5246 Ok(QueryResult::CommandOk {
5250 affected: 0,
5251 modified_catalog: true,
5252 })
5253 }
5254
5255 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5256 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5257 if self.tx_catalogs.remove(&tx_id).is_none() {
5258 return Err(EngineError::NoActiveTransaction);
5259 }
5260 Ok(QueryResult::CommandOk {
5262 affected: 0,
5263 modified_catalog: false,
5264 })
5265 }
5266
5267 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5268 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5269 let state = self
5270 .tx_catalogs
5271 .get_mut(&tx_id)
5272 .ok_or(EngineError::NoActiveTransaction)?;
5273 state.savepoints.retain(|(n, _)| n != &name);
5277 let snapshot = state.catalog.clone();
5278 state.savepoints.push((name, snapshot));
5279 Ok(QueryResult::CommandOk {
5280 affected: 0,
5281 modified_catalog: false,
5282 })
5283 }
5284
5285 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5286 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5287 let state = self
5288 .tx_catalogs
5289 .get_mut(&tx_id)
5290 .ok_or(EngineError::NoActiveTransaction)?;
5291 let pos = state
5292 .savepoints
5293 .iter()
5294 .rposition(|(n, _)| n == name)
5295 .ok_or_else(|| {
5296 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5297 })?;
5298 let snapshot = state.savepoints[pos].1.clone();
5302 state.savepoints.truncate(pos + 1);
5303 state.catalog = snapshot;
5304 Ok(QueryResult::CommandOk {
5305 affected: 0,
5306 modified_catalog: false,
5307 })
5308 }
5309
5310 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5311 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5312 let state = self
5313 .tx_catalogs
5314 .get_mut(&tx_id)
5315 .ok_or(EngineError::NoActiveTransaction)?;
5316 let pos = state
5317 .savepoints
5318 .iter()
5319 .rposition(|(n, _)| n == name)
5320 .ok_or_else(|| {
5321 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5322 })?;
5323 state.savepoints.truncate(pos);
5326 Ok(QueryResult::CommandOk {
5327 affected: 0,
5328 modified_catalog: false,
5329 })
5330 }
5331
5332 fn exec_alter_table(
5343 &mut self,
5344 s: spg_sql::ast::AlterTableStatement,
5345 ) -> Result<QueryResult, EngineError> {
5346 let table_name = s.name.clone();
5351 for target in s.targets {
5352 self.exec_alter_table_subaction(&table_name, target)?;
5353 }
5354 Ok(QueryResult::CommandOk {
5355 affected: 0,
5356 modified_catalog: !self.in_transaction(),
5357 })
5358 }
5359
5360 fn exec_alter_table_subaction(
5361 &mut self,
5362 table_name_outer: &str,
5363 target: spg_sql::ast::AlterTableTarget,
5364 ) -> Result<(), EngineError> {
5365 struct S<'a> {
5368 name: &'a str,
5369 }
5370 let s = S {
5371 name: table_name_outer,
5372 };
5373 match target {
5374 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5375 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5376 EngineError::Storage(StorageError::TableNotFound {
5377 name: s.name.into(),
5378 })
5379 })?;
5380 table.schema_mut().hot_tier_bytes = Some(n);
5381 }
5382 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5383 let cols_snapshot = self
5388 .active_catalog()
5389 .get(s.name)
5390 .ok_or_else(|| {
5391 EngineError::Storage(StorageError::TableNotFound {
5392 name: s.name.into(),
5393 })
5394 })?
5395 .schema()
5396 .columns
5397 .clone();
5398 let storage_fk =
5399 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5400 let existing_rows: Vec<Vec<Value>> = self
5403 .active_catalog()
5404 .get(s.name)
5405 .expect("checked above")
5406 .rows()
5407 .iter()
5408 .map(|r| r.values.clone())
5409 .collect();
5410 enforce_fk_inserts(
5411 self.active_catalog(),
5412 s.name,
5413 core::slice::from_ref(&storage_fk),
5414 &existing_rows,
5415 )?;
5416 let table = self
5418 .active_catalog_mut()
5419 .get_mut(s.name)
5420 .expect("checked above");
5421 if let Some(name) = &storage_fk.name
5422 && table
5423 .schema()
5424 .foreign_keys
5425 .iter()
5426 .any(|f| f.name.as_ref() == Some(name))
5427 {
5428 return Err(EngineError::Unsupported(alloc::format!(
5429 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5430 )));
5431 }
5432 table.schema_mut().foreign_keys.push(storage_fk);
5433 }
5434 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5435 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5436 EngineError::Storage(StorageError::TableNotFound {
5437 name: s.name.into(),
5438 })
5439 })?;
5440 let fks = &mut table.schema_mut().foreign_keys;
5441 let before = fks.len();
5442 fks.retain(|f| f.name.as_ref() != Some(&name));
5443 if fks.len() == before && !if_exists {
5444 return Err(EngineError::Unsupported(alloc::format!(
5445 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5446 s.name
5447 )));
5448 }
5449 }
5451 spg_sql::ast::AlterTableTarget::AddColumn {
5452 column,
5453 if_not_exists,
5454 } => {
5455 let clock = self.clock;
5460 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5461 EngineError::Storage(StorageError::TableNotFound {
5462 name: s.name.into(),
5463 })
5464 })?;
5465 if table
5466 .schema()
5467 .columns
5468 .iter()
5469 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5470 {
5471 if if_not_exists {
5472 return Ok(());
5473 }
5474 return Err(EngineError::Unsupported(alloc::format!(
5475 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5476 column.name,
5477 s.name
5478 )));
5479 }
5480 let col_name = column.name.clone();
5481 let nullable = column.nullable;
5482 let has_default = column.default.is_some() || column.auto_increment;
5483 let col_schema = column_def_to_schema(column)?;
5484 let row_count = table.row_count();
5485 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5492 resolve_column_default_free(&col_schema, clock)?
5493 } else if nullable || row_count == 0 {
5494 Value::Null
5495 } else {
5496 return Err(EngineError::Unsupported(alloc::format!(
5497 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5498 when the table has existing rows"
5499 )));
5500 };
5501 table.add_column(col_schema, fill_value);
5502 }
5503 spg_sql::ast::AlterTableTarget::AlterColumnType {
5504 column,
5505 new_type,
5506 using,
5507 } => {
5508 let new_data_type = column_type_to_data_type(new_type);
5514 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5515 EngineError::Storage(StorageError::TableNotFound {
5516 name: s.name.into(),
5517 })
5518 })?;
5519 let col_pos = table
5520 .schema()
5521 .columns
5522 .iter()
5523 .position(|c| c.name.eq_ignore_ascii_case(&column))
5524 .ok_or_else(|| {
5525 EngineError::Unsupported(alloc::format!(
5526 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5527 s.name
5528 ))
5529 })?;
5530 let schema_cols = table.schema().columns.clone();
5531 let ctx = eval::EvalContext::new(&schema_cols, None);
5532 let mut new_values: alloc::vec::Vec<Value> =
5533 alloc::vec::Vec::with_capacity(table.row_count());
5534 for row in table.rows().iter() {
5535 let raw = match &using {
5536 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5537 EngineError::Unsupported(alloc::format!(
5538 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5539 ))
5540 })?,
5541 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5542 };
5543 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5544 new_values.push(coerced);
5545 }
5546 table.schema_mut().columns[col_pos].ty = new_data_type;
5547 for (i, v) in new_values.into_iter().enumerate() {
5548 let mut row_values = table
5549 .rows()
5550 .get(i)
5551 .expect("bounds-checked above")
5552 .values
5553 .clone();
5554 row_values[col_pos] = v;
5555 table.update_row(i, row_values)?;
5556 }
5557 }
5558 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5559 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5565 EngineError::Storage(StorageError::TableNotFound {
5566 name: s.name.into(),
5567 })
5568 })?;
5569 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5570 let nnd = matches!(
5575 tc,
5576 spg_sql::ast::TableConstraint::Unique {
5577 nulls_not_distinct: true,
5578 ..
5579 }
5580 );
5581 match tc {
5582 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5583 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5584 let positions: Vec<usize> = columns
5585 .iter()
5586 .map(|c| {
5587 table
5588 .schema()
5589 .columns
5590 .iter()
5591 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5592 .ok_or_else(|| {
5593 EngineError::Unsupported(alloc::format!(
5594 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5595 s.name
5596 ))
5597 })
5598 })
5599 .collect::<Result<Vec<_>, _>>()?;
5600 let already = table
5604 .schema()
5605 .uniqueness_constraints
5606 .iter()
5607 .any(|u| u.columns == positions);
5608 if !already {
5609 table.schema_mut().uniqueness_constraints.push(
5610 spg_storage::UniquenessConstraint {
5611 is_primary_key: is_pk,
5612 columns: positions.clone(),
5613 nulls_not_distinct: nnd,
5614 },
5615 );
5616 if is_pk {
5618 for p in &positions {
5619 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5620 c.nullable = false;
5621 }
5622 }
5623 }
5624 let leading = &columns[0];
5627 let already_idx = table.indices().iter().any(|idx| {
5628 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5629 && table.schema().columns[idx.column_position].name == *leading
5630 });
5631 if !already_idx {
5632 let suffix = if is_pk { "pkey" } else { "key" };
5633 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5634 let _ = table.add_index(idx_name, leading);
5635 }
5636 }
5637 }
5638 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5639 table.schema_mut().checks.push(alloc::format!("{expr}"));
5640 }
5641 spg_sql::ast::TableConstraint::Index { name, columns } => {
5642 let leading = &columns[0];
5648 let already_idx = table.indices().iter().any(|idx| {
5649 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5650 && table.schema().columns[idx.column_position].name == *leading
5651 });
5652 if !already_idx {
5653 let idx_name = name
5654 .clone()
5655 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5656 let _ = table.add_index(idx_name, leading);
5657 }
5658 }
5659 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5660 for (k, col) in columns.iter().enumerate() {
5668 let already_idx = table.indices().iter().any(|idx| {
5669 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5670 && table.schema().columns[idx.column_position].name == *col
5671 });
5672 if already_idx {
5673 continue;
5674 }
5675 let idx_name = match (&name, columns.len(), k) {
5676 (Some(n), 1, _) => n.clone(),
5677 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5678 (None, _, _) => {
5679 alloc::format!("{}_{col}_ftidx", s.name)
5680 }
5681 };
5682 let _ = table.add_gin_fulltext_index(idx_name, col);
5683 }
5684 }
5685 }
5686 }
5687 spg_sql::ast::AlterTableTarget::DropColumn {
5688 column,
5689 if_exists,
5690 cascade,
5691 } => {
5692 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5699 EngineError::Storage(StorageError::TableNotFound {
5700 name: s.name.into(),
5701 })
5702 })?;
5703 let col_pos = match table
5704 .schema()
5705 .columns
5706 .iter()
5707 .position(|c| c.name.eq_ignore_ascii_case(&column))
5708 {
5709 Some(p) => p,
5710 None => {
5711 if if_exists {
5712 return Ok(());
5713 }
5714 return Err(EngineError::Unsupported(alloc::format!(
5715 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5716 s.name
5717 )));
5718 }
5719 };
5720 let dependent_fks: Vec<usize> = table
5723 .schema()
5724 .foreign_keys
5725 .iter()
5726 .enumerate()
5727 .filter_map(|(i, fk)| {
5728 if fk.local_columns.contains(&col_pos) {
5729 Some(i)
5730 } else {
5731 None
5732 }
5733 })
5734 .collect();
5735 if !dependent_fks.is_empty() && !cascade {
5736 return Err(EngineError::Unsupported(alloc::format!(
5737 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5738 use DROP COLUMN ... CASCADE to remove them"
5739 )));
5740 }
5741 if cascade {
5743 let mut sorted = dependent_fks.clone();
5745 sorted.sort();
5746 sorted.reverse();
5747 let fks = &mut table.schema_mut().foreign_keys;
5748 for i in sorted {
5749 fks.remove(i);
5750 }
5751 }
5752 table.drop_column(col_pos);
5755 }
5756 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5757 let table_name = s.name.to_string();
5765 let trigs = self.active_catalog_mut().triggers_mut();
5766 let mut touched = false;
5767 for t in trigs.iter_mut() {
5768 if !t.table.eq_ignore_ascii_case(&table_name) {
5769 continue;
5770 }
5771 match &which {
5772 spg_sql::ast::TriggerSelector::All => {
5773 t.enabled = enabled;
5774 touched = true;
5775 }
5776 spg_sql::ast::TriggerSelector::Named(name) => {
5777 if t.name.eq_ignore_ascii_case(name) {
5778 t.enabled = enabled;
5779 touched = true;
5780 }
5781 }
5782 }
5783 }
5784 if !touched {
5790 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5791 return Err(EngineError::Unsupported(alloc::format!(
5792 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5793 if enabled { "ENABLE" } else { "DISABLE" },
5794 )));
5795 }
5796 }
5797 }
5798 spg_sql::ast::AlterTableTarget::SetColumnAutoIncrement { column, seq_name } => {
5799 if let Some(seq) = seq_name {
5805 let _ = self.exec_create_sequence(spg_sql::ast::CreateSequenceStatement {
5806 name: seq,
5807 if_not_exists: true,
5808 temporary: false,
5809 data_type: None,
5810 options: spg_sql::ast::SequenceOptions::default(),
5811 })?;
5812 }
5813 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5821 EngineError::Storage(StorageError::TableNotFound {
5822 name: s.name.into(),
5823 })
5824 })?;
5825 let pos = table
5826 .schema()
5827 .columns
5828 .iter()
5829 .position(|c| c.name.eq_ignore_ascii_case(&column))
5830 .ok_or_else(|| {
5831 EngineError::Unsupported(alloc::format!(
5832 "ALTER COLUMN {column:?}: no such column on {:?}",
5833 s.name
5834 ))
5835 })?;
5836 let col = &table.schema().columns[pos];
5837 if !matches!(
5838 col.ty,
5839 spg_storage::DataType::SmallInt
5840 | spg_storage::DataType::Int
5841 | spg_storage::DataType::BigInt
5842 ) {
5843 return Err(EngineError::Unsupported(alloc::format!(
5844 "auto-increment applies to integer columns only ({column:?} is {:?})",
5845 col.ty
5846 )));
5847 }
5848 table.schema_mut().columns[pos].auto_increment = true;
5849 }
5850 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5851 let old = s.name.to_string();
5858 self.active_catalog_mut()
5859 .rename_table(&old, &new)
5860 .map_err(EngineError::Storage)?;
5861 }
5862 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5863 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5877 EngineError::Storage(StorageError::TableNotFound {
5878 name: s.name.into(),
5879 })
5880 })?;
5881 let col_pos = table
5882 .schema()
5883 .columns
5884 .iter()
5885 .position(|c| c.name.eq_ignore_ascii_case(&old))
5886 .ok_or_else(|| {
5887 EngineError::Unsupported(alloc::format!(
5888 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5889 s.name
5890 ))
5891 })?;
5892 if table
5894 .schema()
5895 .columns
5896 .iter()
5897 .enumerate()
5898 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5899 {
5900 return Err(EngineError::Unsupported(alloc::format!(
5901 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5902 s.name
5903 )));
5904 }
5905 if old.eq_ignore_ascii_case(&new) {
5909 return Ok(());
5910 }
5911 table.rename_column(col_pos, &new);
5912 let n_cols = table.schema().columns.len();
5918 for i in 0..n_cols {
5919 let rt = table.schema().columns[i].runtime_default.clone();
5920 if let Some(src) = rt {
5921 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5922 table.schema_mut().columns[i].runtime_default = Some(rewritten);
5923 }
5924 }
5925 let checks = table.schema().checks.clone();
5927 let mut new_checks = Vec::with_capacity(checks.len());
5928 for chk in checks {
5929 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
5930 }
5931 table.schema_mut().checks = new_checks;
5932 let n_idx = table.indices().len();
5934 for i in 0..n_idx {
5935 let pred = table.indices()[i].partial_predicate.clone();
5936 if let Some(src) = pred {
5937 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5938 table.set_partial_predicate(i, Some(rewritten));
5942 }
5943 }
5944 let table_name = s.name.to_string();
5947 for trig in self.active_catalog_mut().triggers_mut() {
5948 if !trig.table.eq_ignore_ascii_case(&table_name) {
5949 continue;
5950 }
5951 for c in &mut trig.update_columns {
5952 if c.eq_ignore_ascii_case(&old) {
5953 *c = new.clone();
5954 }
5955 }
5956 }
5957 }
5958 }
5959 Ok(())
5960 }
5961
5962 fn exec_alter_index(
5963 &mut self,
5964 stmt: spg_sql::ast::AlterIndexStatement,
5965 ) -> Result<QueryResult, EngineError> {
5966 let spg_sql::ast::AlterIndexStatement {
5970 name: idx_name,
5971 target,
5972 } = stmt;
5973 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
5977 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
5978 return match renamed {
5979 Ok(()) => Ok(QueryResult::CommandOk {
5980 affected: 0,
5981 modified_catalog: !self.in_transaction(),
5982 }),
5983 Err(StorageError::IndexNotFound { .. }) if if_exists => {
5984 Ok(QueryResult::CommandOk {
5985 affected: 0,
5986 modified_catalog: false,
5987 })
5988 }
5989 Err(e) => Err(EngineError::Storage(e)),
5990 };
5991 }
5992 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
5993 unreachable!("Rename branch returned above");
5994 };
5995 let target = encoding.map(|e| match e {
5996 SqlVecEncoding::F32 => VecEncoding::F32,
5997 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
5998 SqlVecEncoding::F16 => VecEncoding::F16,
5999 });
6000 let table_name = {
6005 let cat = self.active_catalog();
6006 let mut found: Option<String> = None;
6007 for tname in cat.table_names() {
6008 if let Some(t) = cat.get(&tname)
6009 && t.indices().iter().any(|i| i.name == idx_name)
6010 {
6011 found = Some(tname);
6012 break;
6013 }
6014 }
6015 found.ok_or_else(|| {
6016 EngineError::Storage(StorageError::IndexNotFound {
6017 name: idx_name.clone(),
6018 })
6019 })?
6020 };
6021 let table = self
6022 .active_catalog_mut()
6023 .get_mut(&table_name)
6024 .expect("table found above");
6025 table.rebuild_nsw_index(&idx_name, target)?;
6026 self.plan_cache.evict_referencing(&table_name);
6029 Ok(QueryResult::CommandOk {
6030 affected: 0,
6031 modified_catalog: !self.in_transaction(),
6032 })
6033 }
6034
6035 fn exec_create_index(
6036 &mut self,
6037 stmt: CreateIndexStatement,
6038 ) -> Result<QueryResult, EngineError> {
6039 let table = self
6040 .active_catalog_mut()
6041 .get_mut(&stmt.table)
6042 .ok_or_else(|| {
6043 EngineError::Storage(StorageError::TableNotFound {
6044 name: stmt.table.clone(),
6045 })
6046 })?;
6047 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
6049 return Ok(QueryResult::CommandOk {
6050 affected: 0,
6051 modified_catalog: false,
6052 });
6053 }
6054 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
6061 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
6065 Vec::new()
6066 } else {
6067 let schema = table.schema();
6068 stmt.included_columns
6069 .iter()
6070 .map(|c| {
6071 schema.column_position(c).ok_or_else(|| {
6072 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
6073 })
6074 })
6075 .collect::<Result<Vec<_>, _>>()?
6076 };
6077 match stmt.method {
6078 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
6079 IndexMethod::Hnsw => {
6080 if !included_positions.is_empty() {
6081 return Err(EngineError::Unsupported(
6082 "INCLUDE columns are not supported on HNSW indexes".into(),
6083 ));
6084 }
6085 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
6086 }
6087 IndexMethod::Brin => {
6089 if !included_positions.is_empty() {
6090 return Err(EngineError::Unsupported(
6091 "INCLUDE columns are not supported on BRIN indexes".into(),
6092 ));
6093 }
6094 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
6095 }
6096 IndexMethod::Gin => {
6104 if !included_positions.is_empty() {
6105 return Err(EngineError::Unsupported(
6106 "INCLUDE columns are not supported on GIN indexes".into(),
6107 ));
6108 }
6109 let col_pos = table
6110 .schema()
6111 .column_position(&stmt.column)
6112 .ok_or_else(|| {
6113 EngineError::Storage(StorageError::ColumnNotFound {
6114 column: stmt.column.clone(),
6115 })
6116 })?;
6117 let col_ty = table.schema().columns[col_pos].ty;
6118 let is_trgm = stmt
6124 .opclass
6125 .as_deref()
6126 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
6127 if is_trgm
6128 && matches!(
6129 col_ty,
6130 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
6131 )
6132 {
6133 table
6134 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
6135 .map_err(EngineError::Storage)?;
6136 } else if col_ty == spg_storage::DataType::TsVector {
6137 table
6138 .add_gin_index(stmt.name.clone(), &stmt.column)
6139 .map_err(EngineError::Storage)?;
6140 } else {
6141 table.add_index(stmt.name.clone(), &stmt.column)?;
6147 }
6148 }
6149 }
6150 if !included_positions.is_empty()
6151 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
6152 {
6153 idx.included_columns = included_positions;
6154 }
6155 if let Some(pred_expr) = &stmt.partial_predicate {
6163 let canonical = pred_expr.to_string();
6164 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6176 idx.partial_predicate = Some(canonical);
6177 }
6178 }
6179 if let Some(key_expr) = &stmt.expression {
6187 if matches!(
6188 stmt.method,
6189 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
6190 ) {
6191 return Err(EngineError::Unsupported(
6192 "Expression keys are not supported on HNSW or BRIN indexes".into(),
6193 ));
6194 }
6195 let canonical = key_expr.to_string();
6196 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6197 idx.expression = Some(canonical);
6198 }
6199 }
6200 if stmt.is_unique {
6209 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
6210 for col_name in &stmt.extra_columns {
6211 let pos = table
6212 .schema()
6213 .columns
6214 .iter()
6215 .position(|c| c.name.eq_ignore_ascii_case(col_name))
6216 .ok_or_else(|| {
6217 EngineError::Unsupported(alloc::format!(
6218 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
6219 stmt.name,
6220 stmt.table
6221 ))
6222 })?;
6223 extra_positions.push(pos);
6224 }
6225 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6226 idx.is_unique = true;
6227 idx.extra_column_positions = extra_positions;
6228 }
6229 let snapshot_indices = table.indices().to_vec();
6234 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
6235 table.rows().iter().cloned().collect();
6236 let snapshot_schema = table.schema().clone();
6237 let idx_ref = snapshot_indices
6238 .iter()
6239 .find(|i| i.name == stmt.name)
6240 .expect("just-added index");
6241 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
6242 }
6243 self.plan_cache.evict_referencing(&table_name);
6246 Ok(QueryResult::CommandOk {
6247 affected: 0,
6248 modified_catalog: !self.in_transaction(),
6249 })
6250 }
6251
6252 fn reconcile_table_if_not_exists(
6261 &mut self,
6262 stmt: CreateTableStatement,
6263 ) -> Result<QueryResult, EngineError> {
6264 let table_name = stmt.name.clone();
6265 let clock = self.clock;
6266 let existing_col_names: alloc::collections::BTreeSet<String> = self
6267 .active_catalog()
6268 .get(&table_name)
6269 .expect("checked above")
6270 .schema()
6271 .columns
6272 .iter()
6273 .map(|c| c.name.to_ascii_lowercase())
6274 .collect();
6275 let row_count = self
6276 .active_catalog()
6277 .get(&table_name)
6278 .expect("checked above")
6279 .row_count();
6280 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6282 .columns
6283 .iter()
6284 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6285 .cloned()
6286 .collect();
6287 for col_def in new_columns {
6288 let col_name = col_def.name.clone();
6289 let nullable = col_def.nullable;
6290 let has_default = col_def.default.is_some() || col_def.auto_increment;
6291 let col_schema = column_def_to_schema(col_def)?;
6292 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6293 resolve_column_default_free(&col_schema, clock)?
6294 } else if nullable || row_count == 0 {
6295 Value::Null
6296 } else {
6297 return Err(EngineError::Unsupported(alloc::format!(
6298 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6299 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6300 )));
6301 };
6302 let table = self
6303 .active_catalog_mut()
6304 .get_mut(&table_name)
6305 .expect("checked above");
6306 table.add_column(col_schema, fill_value);
6307 }
6308 let table_cols_now = self
6312 .active_catalog()
6313 .get(&table_name)
6314 .expect("checked above")
6315 .schema()
6316 .columns
6317 .clone();
6318 for fk in stmt.foreign_keys {
6319 let all_resolved = fk.columns.iter().all(|c| {
6323 table_cols_now
6324 .iter()
6325 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6326 });
6327 if !all_resolved {
6328 continue;
6329 }
6330 let already_present = {
6331 let table = self
6332 .active_catalog()
6333 .get(&table_name)
6334 .expect("checked above");
6335 table.schema().foreign_keys.iter().any(|f| {
6336 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6337 && f.local_columns.len() == fk.columns.len()
6338 })
6339 };
6340 if already_present {
6341 continue;
6342 }
6343 let storage_fk =
6344 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6345 let table = self
6346 .active_catalog_mut()
6347 .get_mut(&table_name)
6348 .expect("checked above");
6349 table.schema_mut().foreign_keys.push(storage_fk);
6350 }
6351 Ok(QueryResult::CommandOk {
6352 affected: 0,
6353 modified_catalog: !self.in_transaction(),
6354 })
6355 }
6356
6357 fn exec_drop_table(
6359 &mut self,
6360 names: Vec<String>,
6361 if_exists: bool,
6362 ) -> Result<QueryResult, EngineError> {
6363 for name in names {
6364 let dropped = self.active_catalog_mut().drop_table(&name);
6365 if !dropped && !if_exists {
6366 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6367 }
6368 }
6369 Ok(QueryResult::CommandOk {
6370 affected: 0,
6371 modified_catalog: !self.in_transaction(),
6372 })
6373 }
6374
6375 fn exec_drop_index(
6377 &mut self,
6378 name: String,
6379 if_exists: bool,
6380 ) -> Result<QueryResult, EngineError> {
6381 let dropped = self.active_catalog_mut().drop_named_index(&name);
6382 if !dropped && !if_exists {
6383 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6384 }
6385 Ok(QueryResult::CommandOk {
6386 affected: 0,
6387 modified_catalog: !self.in_transaction(),
6388 })
6389 }
6390
6391 fn exec_create_table(
6392 &mut self,
6393 stmt: CreateTableStatement,
6394 ) -> Result<QueryResult, EngineError> {
6395 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6396 return Ok(QueryResult::CommandOk {
6415 affected: 0,
6416 modified_catalog: false,
6417 });
6418 }
6419 let table_name = stmt.name.clone();
6420 let inline_pk_columns: Vec<String> = stmt
6424 .columns
6425 .iter()
6426 .filter(|c| c.is_primary_key)
6427 .map(|c| c.name.clone())
6428 .collect();
6429 let cols = stmt
6435 .columns
6436 .into_iter()
6437 .map(column_def_to_schema)
6438 .collect::<Result<Vec<_>, _>>()?;
6439 let mut cols = cols;
6448 for col in cols.iter_mut() {
6449 let Some(name) = col.user_enum_type.take() else {
6450 continue;
6451 };
6452 let cat = self.active_catalog();
6453 if cat.enum_types().contains_key(&name) {
6454 col.user_enum_type = Some(name);
6455 continue;
6456 }
6457 if let Some(dom) = cat.domain_types().get(&name) {
6458 col.ty = dom.base_type;
6459 col.user_domain_type = Some(name);
6460 if !dom.nullable {
6461 col.nullable = false;
6462 }
6463 continue;
6464 }
6465 return Err(EngineError::Unsupported(alloc::format!(
6466 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6467 col.name,
6468 name
6469 )));
6470 }
6471 for tc in &stmt.table_constraints {
6472 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6473 for col_name in columns {
6474 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6475 col.nullable = false;
6476 }
6477 }
6478 }
6479 }
6480 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6487 Vec::with_capacity(stmt.foreign_keys.len());
6488 for fk in stmt.foreign_keys {
6489 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6496 if !self.foreign_key_checks
6497 && needs_parent
6498 && self.active_catalog().get(&fk.parent_table).is_none()
6499 {
6500 self.pending_foreign_keys.push((table_name.clone(), fk));
6501 continue;
6502 }
6503 fks.push(resolve_foreign_key(
6504 &table_name,
6505 &cols,
6506 fk,
6507 self.active_catalog(),
6508 )?);
6509 }
6510 let mut schema = TableSchema::new(table_name.clone(), cols);
6511 schema.foreign_keys = fks;
6512 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6516 let mut check_exprs: Vec<String> = Vec::new();
6517 for tc in &stmt.table_constraints {
6518 let (is_pk, names, nnd) = match tc {
6519 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6520 (true, columns.clone(), false)
6521 }
6522 spg_sql::ast::TableConstraint::Unique {
6523 columns,
6524 nulls_not_distinct,
6525 ..
6526 } => (false, columns.clone(), *nulls_not_distinct),
6527 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6528 check_exprs.push(alloc::format!("{expr}"));
6531 continue;
6532 }
6533 spg_sql::ast::TableConstraint::Index { .. } => continue,
6539 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6543 };
6544 let mut positions = Vec::with_capacity(names.len());
6545 for n in &names {
6546 let pos = schema
6547 .columns
6548 .iter()
6549 .position(|c| c.name == *n)
6550 .ok_or_else(|| {
6551 EngineError::Unsupported(alloc::format!(
6552 "table constraint references unknown column {n:?}"
6553 ))
6554 })?;
6555 positions.push(pos);
6556 }
6557 uc_storage.push(spg_storage::UniquenessConstraint {
6558 is_primary_key: is_pk,
6559 columns: positions,
6560 nulls_not_distinct: nnd,
6561 });
6562 }
6563 if !inline_pk_columns.is_empty() {
6570 let mut positions = Vec::with_capacity(inline_pk_columns.len());
6571 for n in &inline_pk_columns {
6572 if let Some(pos) = schema.columns.iter().position(|c| c.name == *n) {
6573 positions.push(pos);
6574 }
6575 }
6576 if !uc_storage
6577 .iter()
6578 .any(|uc| uc.is_primary_key || uc.columns == positions)
6579 {
6580 uc_storage.push(spg_storage::UniquenessConstraint {
6581 is_primary_key: true,
6582 columns: positions,
6583 nulls_not_distinct: false,
6584 });
6585 }
6586 }
6587 schema.uniqueness_constraints = uc_storage.clone();
6588 schema.checks = check_exprs;
6589 self.active_catalog_mut().create_table(schema)?;
6590 let table = self
6594 .active_catalog_mut()
6595 .get_mut(&table_name)
6596 .expect("just created");
6597 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6598 let idx_name = if inline_pk_columns.len() == 1 {
6599 alloc::format!("{table_name}_pkey")
6600 } else {
6601 alloc::format!("{table_name}_pkey_{i}")
6602 };
6603 if let Err(e) = table.add_index(idx_name, col_name) {
6604 return Err(EngineError::Storage(e));
6605 }
6606 }
6607 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6608 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6613 for (k, col) in columns.iter().enumerate() {
6614 let already = table.indices().iter().any(|idx| {
6615 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6616 && table.schema().columns[idx.column_position].name == *col
6617 });
6618 if already {
6619 continue;
6620 }
6621 let idx_name = match (name.as_ref(), columns.len(), k) {
6622 (Some(n), 1, _) => n.clone(),
6623 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6624 (None, _, _) => {
6625 alloc::format!("{table_name}_{col}_ftidx")
6626 }
6627 };
6628 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6629 return Err(EngineError::Storage(e));
6630 }
6631 }
6632 continue;
6633 }
6634 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6638 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6639 ("pkey", columns, None)
6640 }
6641 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6642 spg_sql::ast::TableConstraint::Index { name, columns } => {
6643 ("idx", columns, name.as_ref())
6644 }
6645 spg_sql::ast::TableConstraint::Check { .. } => continue,
6646 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6648 };
6649 let leading = &names[0];
6650 let already = table.indices().iter().any(|idx| {
6653 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6654 && table.schema().columns[idx.column_position].name == *leading
6655 });
6656 if already {
6657 continue;
6658 }
6659 let idx_name = if let Some(n) = explicit_name {
6660 n.clone()
6661 } else if names.len() == 1 {
6662 alloc::format!("{table_name}_{leading}_{suffix}")
6663 } else {
6664 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6665 };
6666 if let Err(e) = table.add_index(idx_name, leading) {
6667 return Err(EngineError::Storage(e));
6668 }
6669 }
6670 Ok(QueryResult::CommandOk {
6671 affected: 0,
6672 modified_catalog: !self.in_transaction(),
6673 })
6674 }
6675
6676 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6677 for tuple in &mut stmt.rows {
6685 for cell in tuple.iter_mut() {
6686 self.resolve_sequence_calls_in_expr(cell)?;
6687 }
6688 }
6689 if let Some(select) = stmt.select_source.clone() {
6694 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6695 let rows = match select_result {
6696 QueryResult::Rows { rows, .. } => rows,
6697 other => {
6698 return Err(EngineError::Unsupported(alloc::format!(
6699 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6700 )));
6701 }
6702 };
6703 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6704 for row in rows {
6705 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6706 for v in row.values {
6707 tuple.push(value_to_literal_expr_permissive(v)?);
6708 }
6709 materialised.push(tuple);
6710 }
6711 let recurse = InsertStatement {
6712 table: stmt.table,
6713 columns: stmt.columns,
6714 rows: materialised,
6715 select_source: None,
6716 on_conflict: stmt.on_conflict,
6717 returning: stmt.returning,
6718 };
6719 return self.exec_insert(recurse);
6720 }
6721 let clock = self.clock;
6725 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6731 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6732 let trigger_session_cfg: Option<alloc::string::String> = self
6733 .session_params
6734 .get("default_text_search_config")
6735 .cloned();
6736 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6742 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6743 EngineError::Storage(StorageError::TableNotFound {
6744 name: stmt.table.clone(),
6745 })
6746 })?;
6747 preview_table.schema().columns.clone()
6748 };
6749 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6750 pre_borrow_column_meta
6751 .iter()
6752 .enumerate()
6753 .filter_map(|(i, col)| {
6754 if let Some(inline) = &col.inline_enum_variants {
6759 return Some((i, inline.clone()));
6760 }
6761 col.user_enum_type.as_ref().and_then(|ename| {
6762 self.active_catalog()
6763 .enum_types()
6764 .get(ename)
6765 .map(|e| (i, e.labels.clone()))
6766 })
6767 })
6768 .collect();
6769 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6774 pre_borrow_column_meta
6775 .iter()
6776 .enumerate()
6777 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6778 .collect();
6779 let table = self
6780 .active_catalog_mut()
6781 .get_mut(&stmt.table)
6782 .ok_or_else(|| {
6783 EngineError::Storage(StorageError::TableNotFound {
6784 name: stmt.table.clone(),
6785 })
6786 })?;
6787 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6793 let schema_cols_len = column_meta.len();
6794 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6798 None => None, Some(cols) => {
6800 let mut map = alloc::vec![None; schema_cols_len];
6801 for (j, name) in cols.iter().enumerate() {
6802 let idx = column_meta
6803 .iter()
6804 .position(|c| c.name == *name)
6805 .ok_or_else(|| {
6806 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6807 })?;
6808 if map[idx].is_some() {
6809 return Err(EngineError::Storage(StorageError::ArityMismatch {
6810 expected: schema_cols_len,
6811 actual: cols.len(),
6812 }));
6813 }
6814 map[idx] = Some(j);
6815 }
6816 for (i, col) in column_meta.iter().enumerate() {
6820 if map[i].is_none()
6821 && !col.nullable
6822 && col.default.is_none()
6823 && col.runtime_default.is_none()
6824 && !col.auto_increment
6825 {
6826 return Err(EngineError::Storage(StorageError::NullInNotNull {
6827 column: col.name.clone(),
6828 }));
6829 }
6830 }
6831 Some(map)
6832 }
6833 };
6834 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6835 let fks = table.schema().foreign_keys.clone();
6841 let mut affected = 0usize;
6842 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6845 let mut auto_cursors: alloc::collections::BTreeMap<usize, i64> =
6853 alloc::collections::BTreeMap::new();
6854 for tuple in stmt.rows {
6855 if tuple.len() != expected_tuple_len {
6856 return Err(EngineError::Storage(StorageError::ArityMismatch {
6857 expected: expected_tuple_len,
6858 actual: tuple.len(),
6859 }));
6860 }
6861 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6865 let raw_tuple: Vec<Value> = tuple
6867 .into_iter()
6868 .map(literal_expr_to_value)
6869 .collect::<Result<_, _>>()?;
6870 let mut out = Vec::with_capacity(schema_cols_len);
6871 for (i, col) in column_meta.iter().enumerate() {
6872 let mut raw = match map[i] {
6873 Some(j) => raw_tuple[j].clone(),
6874 None => resolve_column_default_free(col, clock)?,
6875 };
6876 if col.auto_increment && raw.is_null() {
6877 let next = match auto_cursors.get(&i) {
6878 Some(n) => *n,
6879 None => table.next_auto_value(i).ok_or_else(|| {
6880 EngineError::Unsupported(alloc::format!(
6881 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6882 col.name
6883 ))
6884 })?,
6885 };
6886 auto_cursors.insert(i, next + 1);
6887 raw = Value::BigInt(next);
6888 }
6889 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6890 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6891 let coerced =
6892 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6893 check_unsigned_range(&coerced, col, i)?;
6894 out.push(coerced);
6895 }
6896 out
6897 } else {
6898 let mut out = Vec::with_capacity(schema_cols_len);
6900 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6901 let mut raw = literal_expr_to_value(expr)?;
6902 if col.auto_increment && raw.is_null() {
6903 let next = match auto_cursors.get(&i) {
6904 Some(n) => *n,
6905 None => table.next_auto_value(i).ok_or_else(|| {
6906 EngineError::Unsupported(alloc::format!(
6907 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6908 col.name
6909 ))
6910 })?,
6911 };
6912 auto_cursors.insert(i, next + 1);
6913 raw = Value::BigInt(next);
6914 }
6915 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6916 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6917 let coerced =
6918 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6919 check_unsigned_range(&coerced, col, i)?;
6920 out.push(coerced);
6921 }
6922 out
6923 };
6924 all_values.push(values);
6925 }
6926 let uniqueness = table.schema().uniqueness_constraints.clone();
6931 let _ = table;
6932 if !fks.is_empty() {
6933 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6934 }
6935 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6937 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6951 let mut skipped_count = 0usize;
6952 if let Some(clause) = &stmt.on_conflict {
6953 let conflict_cols = resolve_on_conflict_columns(
6954 self.active_catalog(),
6955 &stmt.table,
6956 clause.target_columns.as_slice(),
6957 )?;
6958 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6959 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6960 for values in all_values {
6961 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6962 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6965 let collides_with_table = !has_null_key
6966 && on_conflict_keys_exist(
6967 self.active_catalog(),
6968 &stmt.table,
6969 &conflict_cols,
6970 &key_tuple,
6971 );
6972 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6973 let collides_with_batch =
6974 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6975 let collides = collides_with_table || collides_with_batch;
6976 match (&clause.action, collides) {
6977 (_, false) => {
6978 seen_keys.push(key_tuple_owned);
6979 kept.push(values);
6980 }
6981 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6982 skipped_count += 1;
6983 }
6984 (
6985 spg_sql::ast::OnConflictAction::Update {
6986 assignments,
6987 where_,
6988 },
6989 true,
6990 ) => {
6991 if !collides_with_table {
6992 skipped_count += 1;
6993 continue;
6994 }
6995 let target_pos = lookup_row_position_by_keys(
6996 self.active_catalog(),
6997 &stmt.table,
6998 &conflict_cols,
6999 &key_tuple,
7000 )
7001 .ok_or_else(|| {
7002 EngineError::Unsupported(
7003 "ON CONFLICT DO UPDATE: conflict detected but row \
7004 position could not be resolved (cold-tier row?)"
7005 .into(),
7006 )
7007 })?;
7008 let updated = apply_on_conflict_assignments(
7009 self.active_catalog(),
7010 &stmt.table,
7011 target_pos,
7012 &values,
7013 assignments,
7014 where_.as_ref(),
7015 )?;
7016 if let Some(new_row) = updated {
7017 pending_updates.push((target_pos, new_row));
7018 } else {
7019 skipped_count += 1;
7020 }
7021 }
7022 }
7023 }
7024 all_values = kept;
7025 }
7026 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
7032 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
7033 let table = self
7035 .active_catalog_mut()
7036 .get_mut(&stmt.table)
7037 .ok_or_else(|| {
7038 EngineError::Storage(StorageError::TableNotFound {
7039 name: stmt.table.clone(),
7040 })
7041 })?;
7042 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
7046 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
7050 'rowloop: for values in all_values {
7051 let mut row = Row::new(values);
7052 for fd in &before_insert_triggers {
7057 let (outcome, deferred) = triggers::fire_row_trigger(
7058 fd,
7059 Some(row.clone()),
7060 None,
7061 &stmt.table,
7062 &column_meta,
7063 &[],
7064 trigger_session_cfg.as_deref(),
7065 false,
7066 )
7067 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
7068 deferred_embedded.extend(deferred);
7069 match outcome {
7070 triggers::TriggerOutcome::Row(r) => row = r,
7071 triggers::TriggerOutcome::Skip => continue 'rowloop,
7072 }
7073 }
7074 if stmt.returning.is_some() {
7075 returning_rows.push(row.values.clone());
7076 }
7077 let inserted = row.clone();
7080 table.insert(row)?;
7081 affected += 1;
7082 for fd in &after_insert_triggers {
7086 let (_outcome, deferred) = triggers::fire_row_trigger(
7087 fd,
7088 Some(inserted.clone()),
7089 None,
7090 &stmt.table,
7091 &column_meta,
7092 &[],
7093 trigger_session_cfg.as_deref(),
7094 true,
7095 )
7096 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
7097 deferred_embedded.extend(deferred);
7098 }
7099 }
7100 for (pos, new_row) in pending_updates {
7104 if stmt.returning.is_some() {
7105 returning_rows.push(new_row.clone());
7106 }
7107 table.update_row(pos, new_row)?;
7108 affected += 1;
7109 }
7110 let _ = skipped_count;
7111 let _ = table;
7117 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
7118 if let Some(items) = &stmt.returning {
7122 return self.build_returning_rows(&stmt.table, items, returning_rows);
7123 }
7124 if !self.in_transaction() && affected > 0 {
7129 self.statistics
7130 .record_modifications(&stmt.table, affected as u64);
7131 }
7132 Ok(QueryResult::CommandOk {
7133 affected,
7134 modified_catalog: !self.in_transaction(),
7135 })
7136 }
7137
7138 fn exec_select_as_of_segment(
7151 &self,
7152 stmt: &SelectStatement,
7153 from: &spg_sql::ast::FromClause,
7154 segment_id: u32,
7155 ) -> Result<QueryResult, EngineError> {
7156 if !from.joins.is_empty()
7159 || stmt.group_by.is_some()
7160 || stmt.having.is_some()
7161 || !stmt.unions.is_empty()
7162 || !stmt.order_by.is_empty()
7163 || stmt.offset.is_some()
7164 || stmt.distinct
7165 || aggregate::uses_aggregate(stmt)
7166 {
7167 return Err(EngineError::Unsupported(
7168 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
7169 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
7170 .into(),
7171 ));
7172 }
7173 let table = self
7174 .active_catalog()
7175 .get(&from.primary.name)
7176 .ok_or_else(|| StorageError::TableNotFound {
7177 name: from.primary.name.clone(),
7178 })?;
7179 let schema = table.schema().clone();
7180 let schema_cols = &schema.columns;
7181 let alias = from
7182 .primary
7183 .alias
7184 .as_deref()
7185 .unwrap_or(from.primary.name.as_str());
7186 let ctx = EvalContext::new(schema_cols, Some(alias));
7187 let seg = self
7188 .active_catalog()
7189 .cold_segment(segment_id)
7190 .ok_or_else(|| {
7191 EngineError::Unsupported(alloc::format!(
7192 "AS OF SEGMENT: cold segment {segment_id} not registered"
7193 ))
7194 })?;
7195 let mut out_rows: Vec<Row> = Vec::new();
7196 let mut limit_remaining: Option<usize> =
7197 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
7198 for (_key, body) in seg.scan() {
7199 let (row, _consumed) =
7200 spg_storage::decode_row_body_dense(&body, &schema, seg.codec_version())
7201 .map_err(EngineError::Storage)?;
7202 if let Some(where_expr) = &stmt.where_ {
7203 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
7204 if !matches!(cond, Value::Bool(true)) {
7205 continue;
7206 }
7207 }
7208 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7210 out_rows.push(projected);
7211 if let Some(rem) = limit_remaining.as_mut() {
7212 if *rem == 0 {
7213 out_rows.pop();
7214 break;
7215 }
7216 *rem -= 1;
7217 }
7218 }
7219 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7221 Ok(QueryResult::Rows {
7222 columns,
7223 rows: out_rows,
7224 })
7225 }
7226
7227 fn eval_expr_simple(
7232 &self,
7233 expr: &Expr,
7234 row: &Row,
7235 ctx: &EvalContext,
7236 ) -> Result<Value, EngineError> {
7237 let cancel = CancelToken::none();
7238 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7239 }
7240
7241 fn build_returning_rows(
7248 &self,
7249 table_name: &str,
7250 items: &[SelectItem],
7251 mutated_rows: Vec<Vec<Value>>,
7252 ) -> Result<QueryResult, EngineError> {
7253 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7254 EngineError::Storage(StorageError::TableNotFound {
7255 name: table_name.into(),
7256 })
7257 })?;
7258 let schema_cols = table.schema().columns.clone();
7259 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7260 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7261 for values in mutated_rows {
7262 let row = Row::new(values);
7263 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7264 out_rows.push(projected);
7265 }
7266 Ok(QueryResult::Rows {
7267 columns,
7268 rows: out_rows,
7269 })
7270 }
7271
7272 fn project_row_simple(
7276 &self,
7277 row: &Row,
7278 items: &[SelectItem],
7279 schema_cols: &[ColumnSchema],
7280 alias: &str,
7281 ) -> Result<Row, EngineError> {
7282 let ctx = EvalContext::new(schema_cols, Some(alias));
7283 let cancel = CancelToken::none();
7284 let mut out_vals = Vec::new();
7285 for item in items {
7286 match item {
7287 SelectItem::Wildcard => {
7288 out_vals.extend(row.values.iter().cloned());
7289 }
7290 SelectItem::Expr { expr, .. } => {
7291 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7292 out_vals.push(v);
7293 }
7294 }
7295 }
7296 Ok(Row::new(out_vals))
7297 }
7298
7299 fn derive_output_columns(
7304 &self,
7305 items: &[SelectItem],
7306 schema_cols: &[ColumnSchema],
7307 _alias: &str,
7308 ) -> Vec<ColumnSchema> {
7309 let mut out = Vec::new();
7310 for item in items {
7311 match item {
7312 SelectItem::Wildcard => {
7313 out.extend(schema_cols.iter().cloned());
7314 }
7315 SelectItem::Expr { expr, alias } => {
7316 if let Expr::Column(col) = expr
7322 && let Some(sc) = schema_cols.iter().find(|c| c.name == col.name)
7323 {
7324 let name = alias.clone().unwrap_or_else(|| sc.name.clone());
7325 out.push(ColumnSchema::new(name, sc.ty, sc.nullable));
7326 continue;
7327 }
7328 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7329 out.push(ColumnSchema::new(name, DataType::Text, true));
7332 }
7333 }
7334 }
7335 out
7336 }
7337
7338 fn exec_select_cancel(
7339 &self,
7340 stmt: &SelectStatement,
7341 cancel: CancelToken<'_>,
7342 ) -> Result<QueryResult, EngineError> {
7343 cancel.check()?;
7344 if !self.active_catalog().views().is_empty() {
7351 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7352 return self.exec_select_cancel(&rewritten, cancel);
7353 }
7354 }
7355 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7364 return self.exec_select_with_meta_views(stmt, cancel);
7365 }
7366 if let Some(from) = &stmt.from
7375 && let Some(seg_id) = from.primary.as_of_segment
7376 {
7377 return self.exec_select_as_of_segment(stmt, from, seg_id);
7378 }
7379 if let Some(from) = &stmt.from
7383 && from.joins.is_empty()
7384 && stmt.where_.is_none()
7385 && stmt.group_by.is_none()
7386 && stmt.having.is_none()
7387 && stmt.unions.is_empty()
7388 && stmt.order_by.is_empty()
7389 && stmt.limit.is_none()
7390 && stmt.offset.is_none()
7391 && !stmt.distinct
7392 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7393 {
7394 let lower = from.primary.name.to_ascii_lowercase();
7395 match lower.as_str() {
7396 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7397 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7399 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7400 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7401 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7402 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7403 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7404 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7405 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7406 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7407 _ => {}
7408 }
7409 }
7410 if !stmt.ctes.is_empty() {
7418 return self.exec_with_ctes(stmt, cancel);
7419 }
7420 let mut stmt_owned;
7427 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7428 stmt_owned = stmt.clone();
7429 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7430 &stmt_owned
7431 } else {
7432 stmt
7433 };
7434 if stmt_ref.unions.is_empty() {
7435 return self.exec_bare_select_cancel(stmt_ref, cancel);
7436 }
7437 let mut head = stmt_ref.clone();
7442 head.unions = Vec::new();
7443 head.order_by = Vec::new();
7444 head.limit = None;
7445 let QueryResult::Rows { columns, mut rows } =
7446 self.exec_bare_select_cancel(&head, cancel)?
7447 else {
7448 unreachable!("bare SELECT cannot return CommandOk")
7449 };
7450 for (kind, peer) in &stmt_ref.unions {
7451 let QueryResult::Rows {
7452 columns: peer_cols,
7453 rows: peer_rows,
7454 } = self.exec_bare_select_cancel(peer, cancel)?
7455 else {
7456 unreachable!("bare SELECT cannot return CommandOk")
7457 };
7458 if peer_cols.len() != columns.len() {
7459 return Err(EngineError::Unsupported(alloc::format!(
7460 "UNION arity mismatch: head has {} columns, peer has {}",
7461 columns.len(),
7462 peer_cols.len()
7463 )));
7464 }
7465 rows.extend(peer_rows);
7466 if matches!(kind, UnionKind::Distinct) {
7467 rows = dedup_rows(rows);
7468 }
7469 }
7470 if !stmt.order_by.is_empty() {
7473 let synth_ctx = EvalContext::new(&columns, None);
7474 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7475 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7476 for r in rows {
7477 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7478 tagged.push((keys, r));
7479 }
7480 sort_by_keys(&mut tagged, &descs);
7481 rows = tagged.into_iter().map(|(_, r)| r).collect();
7482 }
7483 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7484 Ok(QueryResult::Rows { columns, rows })
7485 }
7486
7487 #[allow(clippy::too_many_lines)]
7488 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7496 &self,
7497 stmt: &SelectStatement,
7498 primary: &TableRef,
7499 cancel: CancelToken<'_>,
7500 ) -> Result<QueryResult, EngineError> {
7501 let expr = primary
7502 .unnest_expr
7503 .as_deref()
7504 .expect("caller guards unnest_expr.is_some()");
7505 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7508 let ctx = EvalContext::new(&empty_schema, None);
7509 let dummy_row = Row::new(alloc::vec::Vec::new());
7510 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7513 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7514 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7515 Value::TextArray(items) => {
7516 let rows = items
7517 .into_iter()
7518 .map(|item| {
7519 Row::new(alloc::vec![match item {
7520 Some(s) => Value::Text(s),
7521 None => Value::Null,
7522 }])
7523 })
7524 .collect();
7525 (DataType::Text, rows)
7526 }
7527 Value::IntArray(items) => {
7528 let rows = items
7529 .into_iter()
7530 .map(|item| {
7531 Row::new(alloc::vec![match item {
7532 Some(n) => Value::Int(n),
7533 None => Value::Null,
7534 }])
7535 })
7536 .collect();
7537 (DataType::Int, rows)
7538 }
7539 Value::BigIntArray(items) => {
7540 let rows = items
7541 .into_iter()
7542 .map(|item| {
7543 Row::new(alloc::vec![match item {
7544 Some(n) => Value::BigInt(n),
7545 None => Value::Null,
7546 }])
7547 })
7548 .collect();
7549 (DataType::BigInt, rows)
7550 }
7551 other => {
7552 return Err(EngineError::Unsupported(alloc::format!(
7553 "unnest() expects an array argument, got {:?}",
7554 other.data_type()
7555 )));
7556 }
7557 };
7558 let alias = primary
7559 .alias
7560 .clone()
7561 .unwrap_or_else(|| "unnest".to_string());
7562 let col_name = primary
7568 .unnest_column_aliases
7569 .first()
7570 .cloned()
7571 .unwrap_or_else(|| alias.clone());
7572 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7573 let schema_cols = alloc::vec![col_schema.clone()];
7574 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7575 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7577 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7578 for row in rows {
7579 cancel.check()?;
7580 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7581 if matches!(v, Value::Bool(true)) {
7582 out.push(row);
7583 }
7584 }
7585 out
7586 } else {
7587 rows
7588 };
7589 if aggregate::uses_aggregate(stmt) {
7595 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
7596 self.eval_expr_with_correlated(e, r, c, cancel, None)
7597 .map_err(|err| match err {
7598 EngineError::Eval(ev) => ev,
7599 other => eval::EvalError::TypeMismatch {
7600 detail: alloc::format!("{other}"),
7601 },
7602 })
7603 };
7604 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7605 let mut agg = aggregate::run(
7606 stmt,
7607 &filtered_refs,
7608 &schema_cols,
7609 Some(&alias),
7610 Some(&agg_correlated),
7611 )?;
7612 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7613 return Ok(QueryResult::Rows {
7614 columns: agg.columns,
7615 rows: agg.rows,
7616 });
7617 }
7618 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7620 let mut projected_rows: alloc::vec::Vec<Row> =
7621 alloc::vec::Vec::with_capacity(filtered.len());
7622 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7631 if let Some(srf_idx) = srf_position {
7632 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7633 .expect("checked by is_top_level_unnest above");
7634 for row in &filtered {
7635 let arr_val =
7636 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7637 let elements = array_value_to_elements(&arr_val)?;
7638 for elem in elements {
7642 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7643 for (i, p) in projection.iter().enumerate() {
7644 if i == srf_idx {
7645 vals.push(elem.clone());
7646 } else {
7647 vals.push(
7648 eval::eval_expr(&p.expr, row, &scan_ctx)
7649 .map_err(EngineError::Eval)?,
7650 );
7651 }
7652 }
7653 projected_rows.push(Row::new(vals));
7654 }
7655 }
7656 } else {
7657 let mut proj_memo = memoize::MemoizeCache::default();
7661 for row in &filtered {
7662 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7663 for p in &projection {
7664 vals.push(self.eval_expr_with_correlated(
7665 &p.expr,
7666 row,
7667 &scan_ctx,
7668 cancel,
7669 Some(&mut proj_memo),
7670 )?);
7671 }
7672 projected_rows.push(Row::new(vals));
7673 }
7674 }
7675 let columns: alloc::vec::Vec<ColumnSchema> = projection
7678 .iter()
7679 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7680 .collect();
7681 if !stmt.order_by.is_empty() {
7684 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7685 .iter()
7686 .enumerate()
7687 .map(|(i, r)| -> Result<_, EngineError> {
7688 let keys: Result<Vec<Value>, EngineError> = stmt
7689 .order_by
7690 .iter()
7691 .map(|ob| {
7692 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7693 })
7694 .collect();
7695 Ok((i, keys?))
7696 })
7697 .collect::<Result<_, _>>()?;
7698 indexed.sort_by(|a, b| {
7699 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7700 let o = &stmt.order_by[idx];
7701 let cmp = order_by_value_cmp(o.desc, o.nulls_first, ka, kb);
7702 if cmp != core::cmp::Ordering::Equal {
7703 return cmp;
7704 }
7705 }
7706 core::cmp::Ordering::Equal
7707 });
7708 projected_rows = indexed
7709 .into_iter()
7710 .map(|(i, _)| projected_rows[i].clone())
7711 .collect();
7712 }
7713 if let Some(offset) = stmt.offset_literal() {
7715 let off = (offset as usize).min(projected_rows.len());
7716 projected_rows.drain(..off);
7717 }
7718 if let Some(limit) = stmt.limit_literal() {
7719 projected_rows.truncate(limit as usize);
7720 }
7721 Ok(QueryResult::Rows {
7722 columns,
7723 rows: projected_rows,
7724 })
7725 }
7726
7727 fn exec_select_generate_series(
7738 &self,
7739 stmt: &SelectStatement,
7740 primary: &TableRef,
7741 cancel: CancelToken<'_>,
7742 ) -> Result<QueryResult, EngineError> {
7743 let args = primary
7744 .generate_series_args
7745 .as_ref()
7746 .expect("caller guards generate_series_args.is_some()");
7747 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7748 let ctx = EvalContext::new(&empty_schema, None);
7749 let dummy_row = Row::new(alloc::vec::Vec::new());
7750 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7751 for a in args {
7752 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7753 }
7754 let (elem_dtype, rows) = match arg_values.as_slice() {
7758 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7759 let interval_step = match step {
7760 Value::Interval { .. } => step.clone(),
7761 other => {
7762 return Err(EngineError::Unsupported(alloc::format!(
7763 "generate_series(timestamp, timestamp, …): \
7764 step must be INTERVAL, got {:?}",
7765 other.data_type()
7766 )));
7767 }
7768 };
7769 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7770 (DataType::Timestamp, rows)
7771 }
7772 [start, stop, step]
7773 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7774 {
7775 let s = value_to_i64(start);
7776 let e = value_to_i64(stop);
7777 let st = value_to_i64(step);
7778 let rows = generate_series_integers(s, e, st, &cancel)?;
7779 (DataType::BigInt, rows)
7780 }
7781 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7782 let s = value_to_i64(start);
7783 let e = value_to_i64(stop);
7784 let rows = generate_series_integers(s, e, 1, &cancel)?;
7785 (DataType::BigInt, rows)
7786 }
7787 _ => {
7788 return Err(EngineError::Unsupported(alloc::format!(
7789 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7790 argument shapes; got {:?}",
7791 arg_values
7792 .iter()
7793 .map(|v| v.data_type())
7794 .collect::<alloc::vec::Vec<_>>()
7795 )));
7796 }
7797 };
7798 let alias = primary
7799 .alias
7800 .clone()
7801 .unwrap_or_else(|| "generate_series".to_string());
7802 let col_name = alias.clone();
7803 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7804 let schema_cols = alloc::vec![col_schema.clone()];
7805 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7806 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7808 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7809 for row in rows {
7810 cancel.check()?;
7811 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7812 if matches!(v, Value::Bool(true)) {
7813 out.push(row);
7814 }
7815 }
7816 out
7817 } else {
7818 rows
7819 };
7820 if aggregate::uses_aggregate(stmt) {
7830 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
7831 self.eval_expr_with_correlated(e, r, c, cancel, None)
7832 .map_err(|err| match err {
7833 EngineError::Eval(ev) => ev,
7834 other => eval::EvalError::TypeMismatch {
7835 detail: alloc::format!("{other}"),
7836 },
7837 })
7838 };
7839 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7840 let mut agg = aggregate::run(
7841 stmt,
7842 &filtered_refs,
7843 &schema_cols,
7844 Some(&alias),
7845 Some(&agg_correlated),
7846 )?;
7847 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7848 return Ok(QueryResult::Rows {
7849 columns: agg.columns,
7850 rows: agg.rows,
7851 });
7852 }
7853 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7855 let mut projected_rows: alloc::vec::Vec<Row> =
7856 alloc::vec::Vec::with_capacity(filtered.len());
7857 let mut proj_memo = memoize::MemoizeCache::default();
7858 for row in &filtered {
7859 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7860 for p in &projection {
7861 vals.push(self.eval_expr_with_correlated(
7863 &p.expr,
7864 row,
7865 &scan_ctx,
7866 cancel,
7867 Some(&mut proj_memo),
7868 )?);
7869 }
7870 projected_rows.push(Row::new(vals));
7871 }
7872 let columns: alloc::vec::Vec<ColumnSchema> = projection
7873 .iter()
7874 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7875 .collect();
7876 if !stmt.order_by.is_empty() {
7878 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7879 .iter()
7880 .enumerate()
7881 .map(|(i, r)| -> Result<_, EngineError> {
7882 let keys: Result<Vec<Value>, EngineError> = stmt
7883 .order_by
7884 .iter()
7885 .map(|ob| {
7886 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7887 })
7888 .collect();
7889 Ok((i, keys?))
7890 })
7891 .collect::<Result<_, _>>()?;
7892 indexed.sort_by(|a, b| {
7893 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7894 let o = &stmt.order_by[idx];
7895 let cmp = order_by_value_cmp(o.desc, o.nulls_first, ka, kb);
7896 if cmp != core::cmp::Ordering::Equal {
7897 return cmp;
7898 }
7899 }
7900 core::cmp::Ordering::Equal
7901 });
7902 projected_rows = indexed
7903 .into_iter()
7904 .map(|(i, _)| projected_rows[i].clone())
7905 .collect();
7906 }
7907 if let Some(offset) = stmt.offset_literal() {
7908 let off = (offset as usize).min(projected_rows.len());
7909 projected_rows.drain(..off);
7910 }
7911 if let Some(limit) = stmt.limit_literal() {
7912 projected_rows.truncate(limit as usize);
7913 }
7914 Ok(QueryResult::Rows {
7915 columns,
7916 rows: projected_rows,
7917 })
7918 }
7919
7920 fn exec_bare_select_cancel(
7921 &self,
7922 stmt: &SelectStatement,
7923 cancel: CancelToken<'_>,
7924 ) -> Result<QueryResult, EngineError> {
7925 check_with_ties_requires_order_by(stmt)?;
7930 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7938 return self.exec_select_with_meta_views(stmt, cancel);
7939 }
7940 if select_has_window(stmt) {
7945 return self.exec_select_with_window(stmt, cancel);
7946 }
7947 let Some(from) = &stmt.from else {
7952 let empty_schema: Vec<ColumnSchema> = Vec::new();
7953 let ctx = self.ev_ctx(&empty_schema, None);
7954 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7955 let dummy_row = Row::new(Vec::new());
7956 let mut values = Vec::with_capacity(projection.len());
7957 for p in &projection {
7958 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7959 }
7960 let columns: Vec<ColumnSchema> = projection
7961 .into_iter()
7962 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7963 .collect();
7964 return Ok(QueryResult::Rows {
7965 columns,
7966 rows: alloc::vec![Row::new(values)],
7967 });
7968 };
7969 if !from.joins.is_empty() {
7973 return self.exec_joined_select(stmt, from, cancel);
7974 }
7975 if from.primary.unnest_expr.is_some() {
7982 return self.exec_select_unnest(stmt, &from.primary, cancel);
7983 }
7984 if from.primary.generate_series_args.is_some() {
7990 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7991 }
7992 let primary = &from.primary;
7993 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7994 StorageError::TableNotFound {
7995 name: primary.name.clone(),
7996 }
7997 })?;
7998 let schema_cols = &table.schema().columns;
7999 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
8002 let ctx = self.ev_ctx(schema_cols, Some(alias));
8003
8004 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
8009 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
8010 }
8011
8012 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
8020 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
8023 .or_else(|| {
8024 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
8030 })
8031 .or_else(|| {
8032 try_trgm_seek(w, schema_cols, table, alias)
8038 })
8039 });
8040
8041 if aggregate::uses_aggregate(stmt) {
8044 let mut filtered: Vec<&Row> = Vec::new();
8045 let mut memo = memoize::MemoizeCache::new();
8049 if let Some(rows) = &indexed_rows {
8050 for cow in rows {
8051 let row = cow.as_ref();
8052 if let Some(where_expr) = &stmt.where_ {
8053 let cond = self.eval_expr_with_correlated(
8054 where_expr,
8055 row,
8056 &ctx,
8057 cancel,
8058 Some(&mut memo),
8059 )?;
8060 if !matches!(cond, Value::Bool(true)) {
8061 continue;
8062 }
8063 }
8064 filtered.push(row);
8065 }
8066 } else {
8067 for i in 0..table.row_count() {
8068 let row = &table.rows()[i];
8069 if let Some(where_expr) = &stmt.where_ {
8070 let cond = self.eval_expr_with_correlated(
8071 where_expr,
8072 row,
8073 &ctx,
8074 cancel,
8075 Some(&mut memo),
8076 )?;
8077 if !matches!(cond, Value::Bool(true)) {
8078 continue;
8079 }
8080 }
8081 filtered.push(row);
8082 }
8083 }
8084 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
8085 self.eval_expr_with_correlated(e, r, c, cancel, None)
8086 .map_err(|err| match err {
8087 EngineError::Eval(ev) => ev,
8088 other => eval::EvalError::TypeMismatch {
8089 detail: alloc::format!("{other}"),
8090 },
8091 })
8092 };
8093 let mut agg = aggregate::run(
8094 stmt,
8095 &filtered,
8096 schema_cols,
8097 Some(alias),
8098 Some(&agg_correlated),
8099 )?;
8100 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8101 return Ok(QueryResult::Rows {
8102 columns: agg.columns,
8103 rows: agg.rows,
8104 });
8105 }
8106
8107 let projection = build_projection(&stmt.items, schema_cols, alias)?;
8108 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
8116
8117 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8120 let mut memo = memoize::MemoizeCache::new();
8122 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
8125 if loop_idx.is_multiple_of(256) {
8126 cancel.check()?;
8127 }
8128 if let Some(where_expr) = &stmt.where_ {
8129 let cond =
8130 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
8131 if !matches!(cond, Value::Bool(true)) {
8132 return Ok(());
8133 }
8134 }
8135 let order_keys = if stmt.order_by.is_empty() {
8136 Vec::new()
8137 } else {
8138 build_order_keys(&stmt.order_by, row, &ctx)?
8139 };
8140 if let Some(srf_idx) = srf_position {
8141 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
8142 .expect("checked by is_top_level_unnest above");
8143 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
8144 let elements = array_value_to_elements(&arr_val)?;
8145 for elem in elements {
8146 let mut values = Vec::with_capacity(projection.len());
8147 for (i, p) in projection.iter().enumerate() {
8148 if i == srf_idx {
8149 values.push(elem.clone());
8150 } else {
8151 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8152 }
8153 }
8154 tagged.push((order_keys.clone(), Row::new(values)));
8155 }
8156 } else {
8157 let mut values = Vec::with_capacity(projection.len());
8158 for p in &projection {
8159 values.push(self.eval_expr_with_correlated(&p.expr, row, &ctx, cancel, None)?);
8161 }
8162 tagged.push((order_keys, Row::new(values)));
8163 }
8164 Ok(())
8165 };
8166 if let Some(rows) = &indexed_rows {
8167 for (loop_idx, cow) in rows.iter().enumerate() {
8168 process_row(cow.as_ref(), loop_idx)?;
8169 }
8170 } else {
8171 for i in 0..table.row_count() {
8172 process_row(&table.rows()[i], i)?;
8173 }
8174 }
8175
8176 if !stmt.order_by.is_empty() {
8177 let keep = if stmt.distinct || stmt.limit_with_ties {
8185 None
8186 } else {
8187 stmt.limit_literal()
8188 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8189 };
8190 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8191 partial_sort_tagged(&mut tagged, keep, &descs);
8192 }
8193
8194 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
8204 apply_offset_and_limit_tagged(
8205 &mut tagged,
8206 stmt.offset_literal(),
8207 stmt.limit_literal(),
8208 true,
8209 );
8210 tagged.into_iter().map(|(_, r)| r).collect()
8211 } else {
8212 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8213 if stmt.distinct {
8214 output_rows = dedup_rows(output_rows);
8215 }
8216 apply_offset_and_limit(
8217 &mut output_rows,
8218 stmt.offset_literal(),
8219 stmt.limit_literal(),
8220 );
8221 output_rows
8222 };
8223
8224 let columns: Vec<ColumnSchema> = projection
8225 .into_iter()
8226 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8227 .collect();
8228
8229 Ok(QueryResult::Rows {
8230 columns,
8231 rows: output_rows,
8232 })
8233 }
8234
8235 #[allow(clippy::too_many_lines)]
8242 fn materialise_table_ref(
8250 &self,
8251 tref: &TableRef,
8252 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8253 if let Some(expr) = tref.unnest_expr.as_deref() {
8254 let empty_schema: Vec<ColumnSchema> = Vec::new();
8255 let ctx = EvalContext::new(&empty_schema, None);
8256 let dummy_row = Row::new(Vec::new());
8257 let (elem_dtype, rows) =
8258 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8259 Value::Null => (DataType::Text, Vec::new()),
8260 Value::TextArray(items) => (
8261 DataType::Text,
8262 items
8263 .into_iter()
8264 .map(|item| {
8265 Row::new(alloc::vec![match item {
8266 Some(s) => Value::Text(s),
8267 None => Value::Null,
8268 }])
8269 })
8270 .collect(),
8271 ),
8272 Value::IntArray(items) => (
8273 DataType::Int,
8274 items
8275 .into_iter()
8276 .map(|item| {
8277 Row::new(alloc::vec![match item {
8278 Some(n) => Value::Int(n),
8279 None => Value::Null,
8280 }])
8281 })
8282 .collect(),
8283 ),
8284 Value::BigIntArray(items) => (
8285 DataType::BigInt,
8286 items
8287 .into_iter()
8288 .map(|item| {
8289 Row::new(alloc::vec![match item {
8290 Some(n) => Value::BigInt(n),
8291 None => Value::Null,
8292 }])
8293 })
8294 .collect(),
8295 ),
8296 other => {
8297 return Err(EngineError::Unsupported(alloc::format!(
8298 "unnest() expects an array argument, got {:?}",
8299 other.data_type()
8300 )));
8301 }
8302 };
8303 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8304 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8305 return Ok((
8306 rows,
8307 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8308 ));
8309 }
8310 let table =
8311 self.active_catalog()
8312 .get(&tref.name)
8313 .ok_or_else(|| StorageError::TableNotFound {
8314 name: tref.name.clone(),
8315 })?;
8316 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8317 let cols = table.schema().columns.clone();
8318 Ok((rows, cols))
8319 }
8320
8321 fn materialise_table_ref_filtered(
8330 &self,
8331 tref: &TableRef,
8332 preds: &[&Expr],
8333 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8334 if preds.is_empty()
8335 || tref.unnest_expr.is_some()
8336 || tref.lateral_subquery.is_some()
8337 || tref.as_of_segment.is_some()
8338 {
8339 return self.materialise_table_ref(tref);
8340 }
8341 let Some(table) = self.active_catalog().get(&tref.name) else {
8342 return self.materialise_table_ref(tref);
8343 };
8344 let cols = table.schema().columns.clone();
8345 let alias = tref.alias.as_deref().unwrap_or(tref.name.as_str());
8346 let mut seeded: Option<Vec<usize>> = None;
8349 for p in preds {
8350 if let Expr::Binary {
8351 lhs,
8352 op: spg_sql::ast::BinOp::Eq,
8353 rhs,
8354 } = p
8355 {
8356 let pair = match (lhs.as_ref(), rhs.as_ref()) {
8357 (Expr::Column(c), Expr::Literal(l)) | (Expr::Literal(l), Expr::Column(c)) => {
8358 Some((c, l))
8359 }
8360 _ => None,
8361 };
8362 if let Some((c, l)) = pair
8363 && c.qualifier
8364 .as_deref()
8365 .is_none_or(|q| q.eq_ignore_ascii_case(alias))
8366 && let Some(pos) = cols.iter().position(|s| s.name == c.name)
8367 && let Some(idx) = table.index_on(pos)
8368 && let Some(key) = spg_storage::IndexKey::from_value(&eval::literal_to_value(l))
8369 {
8370 let mut ids = Vec::new();
8371 let mut all_hot = true;
8372 for loc in idx.lookup_eq(&key) {
8373 match *loc {
8374 spg_storage::RowLocator::Hot(i) => ids.push(i),
8375 spg_storage::RowLocator::Cold { .. } => {
8376 all_hot = false;
8377 break;
8378 }
8379 }
8380 }
8381 if all_hot {
8382 seeded = Some(ids);
8383 break;
8384 }
8385 }
8386 }
8387 }
8388 let ctx = EvalContext::new(&cols, Some(alias));
8389 let mut out: Vec<Row> = Vec::new();
8390 let push_if = |row: &Row, out: &mut Vec<Row>| -> Result<(), EngineError> {
8391 for p in preds {
8392 let v = eval::eval_expr(p, row, &ctx).map_err(EngineError::Eval)?;
8393 if !matches!(v, Value::Bool(true)) {
8394 return Ok(());
8395 }
8396 }
8397 out.push(row.clone());
8398 Ok(())
8399 };
8400 match seeded {
8401 Some(ids) => {
8402 for i in ids {
8403 if let Some(row) = table.rows().get(i) {
8404 push_if(row, &mut out)?;
8405 }
8406 }
8407 }
8408 None => {
8409 for row in table.rows().iter() {
8410 push_if(row, &mut out)?;
8411 }
8412 }
8413 }
8414 Ok((out, cols))
8415 }
8416
8417 fn composite_col_pos(schema: &[ColumnSchema], c: &spg_sql::ast::ColumnName) -> Option<usize> {
8432 if let Some(q) = &c.qualifier {
8433 let composite = alloc::format!("{q}.{}", c.name);
8434 return schema.iter().position(|s| s.name == composite);
8435 }
8436 let suffix = alloc::format!(".{}", c.name);
8437 let mut hits = schema
8438 .iter()
8439 .enumerate()
8440 .filter(|(_, s)| s.name.ends_with(&suffix) || s.name == c.name);
8441 let first = hits.next();
8442 if hits.next().is_some() {
8443 return None; }
8445 first.map(|(i, _)| i)
8446 }
8447
8448 fn peer_col_pos(
8451 peer_alias: &str,
8452 peer_cols: &[ColumnSchema],
8453 c: &spg_sql::ast::ColumnName,
8454 ) -> Option<usize> {
8455 if let Some(q) = &c.qualifier
8456 && !q.eq_ignore_ascii_case(peer_alias)
8457 {
8458 return None;
8459 }
8460 peer_cols.iter().position(|s| s.name == c.name)
8461 }
8462
8463 fn null_out_unreferenced(
8468 rows: &mut [Row],
8469 cols: &[ColumnSchema],
8470 alias: &str,
8471 needed: &alloc::collections::BTreeSet<(String, String)>,
8472 ) {
8473 let keep: Vec<bool> = cols
8474 .iter()
8475 .map(|c| needed.contains(&(alias.to_string(), c.name.clone())))
8476 .collect();
8477 if keep.iter().all(|k| *k) {
8478 return;
8479 }
8480 for row in rows.iter_mut() {
8481 for (i, k) in keep.iter().enumerate() {
8482 if !*k && i < row.values.len() {
8483 row.values[i] = Value::Null;
8484 }
8485 }
8486 }
8487 }
8488
8489 fn build_joined_filtered_rows(
8490 &self,
8491 from: &FromClause,
8492 where_: Option<&Expr>,
8493 cancel: CancelToken<'_>,
8494 needed: Option<&alloc::collections::BTreeSet<(String, String)>>,
8495 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8496 let primary_alias = from
8497 .primary
8498 .alias
8499 .as_deref()
8500 .unwrap_or(from.primary.name.as_str())
8501 .to_string();
8502 let mut primary_preds: Vec<&Expr> = Vec::new();
8511 let mut peer_preds: Vec<Vec<&Expr>> = alloc::vec![Vec::new(); from.joins.len()];
8512 if let Some(w) = where_ {
8513 for sub in reorder::split_and_conjunctions(w) {
8514 if expr_has_subquery(sub) || aggregate::contains_aggregate(sub) {
8515 continue;
8516 }
8517 let mut quals: Vec<&str> = Vec::new();
8518 let mut all_qualified = true;
8519 collect_column_qualifiers(sub, &mut quals, &mut all_qualified);
8520 if !all_qualified || quals.is_empty() {
8521 continue;
8522 }
8523 let q0 = quals[0];
8524 if !quals.iter().all(|q| q.eq_ignore_ascii_case(q0)) {
8525 continue;
8526 }
8527 if q0.eq_ignore_ascii_case(&primary_alias) {
8528 primary_preds.push(sub);
8529 continue;
8530 }
8531 for (i, j) in from.joins.iter().enumerate() {
8532 if matches!(j.kind, JoinKind::Inner)
8533 && j.table.lateral_subquery.is_none()
8534 && q0.eq_ignore_ascii_case(
8535 j.table.alias.as_deref().unwrap_or(j.table.name.as_str()),
8536 )
8537 {
8538 peer_preds[i].push(sub);
8539 break;
8540 }
8541 }
8542 }
8543 }
8544 let mut from_owned;
8553 let mut from = from;
8554 if primary_preds.is_empty()
8561 && let Some(j0) = from.joins.first()
8562 && matches!(j0.kind, JoinKind::Inner)
8563 && j0.table.lateral_subquery.is_none()
8564 && !peer_preds[0].is_empty()
8565 {
8566 let peer_alias = j0.table.alias.as_deref().unwrap_or(j0.table.name.as_str());
8567 let on_safe = j0.on.as_ref().is_some_and(|on| {
8568 let mut quals: Vec<&str> = Vec::new();
8569 let mut all_q = true;
8570 collect_column_qualifiers(on, &mut quals, &mut all_q);
8571 all_q
8572 && quals.iter().all(|q| {
8573 q.eq_ignore_ascii_case(&primary_alias) || q.eq_ignore_ascii_case(peer_alias)
8574 })
8575 });
8576 if on_safe {
8577 from_owned = from.clone();
8578 core::mem::swap(&mut from_owned.primary, &mut from_owned.joins[0].table);
8579 primary_preds = peer_preds[0].drain(..).collect();
8580 from = &from_owned;
8581 }
8582 }
8583 let primary_alias = from
8584 .primary
8585 .alias
8586 .as_deref()
8587 .unwrap_or(from.primary.name.as_str())
8588 .to_string();
8589 let (mut primary_rows, primary_cols) =
8590 self.materialise_table_ref_filtered(&from.primary, &primary_preds)?;
8591 if let Some(needed) = needed {
8592 Self::null_out_unreferenced(&mut primary_rows, &primary_cols, &primary_alias, needed);
8593 }
8594 #[allow(clippy::type_complexity)]
8601 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8602 for j in &from.joins {
8603 let a = j
8604 .table
8605 .alias
8606 .as_deref()
8607 .unwrap_or(j.table.name.as_str())
8608 .to_string();
8609 if let Some(inner_box) = &j.table.lateral_subquery {
8610 let schema = self.lateral_probe_schema(inner_box)?;
8615 joined.push(JoinedPeer {
8616 eager_rows: None,
8617 cols: schema,
8618 alias: a,
8619 kind: j.kind,
8620 on: j.on.as_ref(),
8621 lateral: Some(inner_box.as_ref()),
8622 join_table: None,
8623 });
8624 } else {
8625 let pidx = from
8626 .joins
8627 .iter()
8628 .position(|jj| core::ptr::eq(jj, j))
8629 .unwrap_or(0);
8630 let plain = j.table.unnest_expr.is_none() && j.table.as_of_segment.is_none();
8634 if plain
8635 && peer_preds[pidx].is_empty()
8636 && let Some(t) = self.active_catalog().get(&j.table.name)
8637 {
8638 joined.push(JoinedPeer {
8639 eager_rows: None,
8640 cols: t.schema().columns.clone(),
8641 alias: a,
8642 kind: j.kind,
8643 on: j.on.as_ref(),
8644 lateral: None,
8645 join_table: Some(j.table.name.clone()),
8646 });
8647 continue;
8648 }
8649 let (mut rows, cols) =
8650 self.materialise_table_ref_filtered(&j.table, &peer_preds[pidx])?;
8651 if let Some(needed) = needed {
8652 Self::null_out_unreferenced(&mut rows, &cols, &a, needed);
8653 }
8654 joined.push(JoinedPeer {
8655 eager_rows: Some(rows),
8656 cols,
8657 alias: a,
8658 kind: j.kind,
8659 on: j.on.as_ref(),
8660 lateral: None,
8661 join_table: Some(j.table.name.clone()),
8662 });
8663 }
8664 }
8665 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8666 for col in &primary_cols {
8667 combined_schema.push(ColumnSchema::new(
8668 alloc::format!("{primary_alias}.{}", col.name),
8669 col.ty,
8670 col.nullable,
8671 ));
8672 }
8673 for peer in &joined {
8674 for col in &peer.cols {
8675 combined_schema.push(ColumnSchema::new(
8676 alloc::format!("{}.{}", peer.alias, col.name),
8677 col.ty,
8678 col.nullable,
8679 ));
8680 }
8681 }
8682 let ctx = EvalContext::new(&combined_schema, None);
8683 const MAX_JOIN_INTERMEDIATE_ROWS: usize = 4_000_000;
8688 let mut working: Vec<Row> = primary_rows;
8689 let mut consumed_cols = primary_cols.len();
8692 for peer in &joined {
8693 if working.len() > MAX_JOIN_INTERMEDIATE_ROWS {
8694 return Err(EngineError::Unsupported(alloc::format!(
8695 "join intermediate result exceeds {MAX_JOIN_INTERMEDIATE_ROWS} rows ({} so far) - add join predicates",
8696 working.len()
8697 )));
8698 }
8699 let right_arity = peer.cols.len();
8700 let mut next: Vec<Row> = Vec::new();
8701 let mut eq_pairs: Vec<(usize, usize)> = Vec::new(); let mut residual: Vec<&Expr> = Vec::new();
8712 if let (Some(on_expr), None) = (peer.on, peer.lateral) {
8713 for sub in reorder::split_and_conjunctions(on_expr) {
8714 let mut matched = None;
8715 if let Expr::Binary {
8716 lhs,
8717 op: spg_sql::ast::BinOp::Eq,
8718 rhs,
8719 } = sub
8720 && let (Expr::Column(a), Expr::Column(b)) = (lhs.as_ref(), rhs.as_ref())
8721 {
8722 let left_slice = &combined_schema[..consumed_cols];
8723 if let (Some(l), Some(r)) = (
8724 Self::composite_col_pos(left_slice, a),
8725 Self::peer_col_pos(&peer.alias, &peer.cols, b),
8726 ) {
8727 matched = Some((l, r));
8728 } else if let (Some(l), Some(r)) = (
8729 Self::composite_col_pos(left_slice, b),
8730 Self::peer_col_pos(&peer.alias, &peer.cols, a),
8731 ) {
8732 matched = Some((l, r));
8733 }
8734 }
8735 match matched {
8736 Some(pair) => eq_pairs.push(pair),
8737 None => residual.push(sub),
8738 }
8739 }
8740 }
8741 const INL_MAX_LEFT: usize = 1024;
8747 if let Some(tname) = &peer.join_table
8748 && peer.eager_rows.is_none()
8749 && !eq_pairs.is_empty()
8750 && working.len() <= INL_MAX_LEFT
8751 && let Some(table) = self.active_catalog().get(tname)
8752 && let Some(idx) = peer
8753 .cols
8754 .iter()
8755 .position(|c| c.name == peer.cols[eq_pairs[0].1].name)
8756 .and_then(|pos| table.index_on(pos))
8757 {
8758 let (lpos0, _) = eq_pairs[0];
8759 for left in &working {
8760 cancel.check()?;
8761 let mut left_matched = false;
8762 let key_v = left.values.get(lpos0).cloned().unwrap_or(Value::Null);
8763 if !matches!(key_v, Value::Null)
8764 && let Some(key) = spg_storage::IndexKey::from_value(&key_v)
8765 {
8766 for loc in idx.lookup_eq(&key) {
8767 let right = match *loc {
8768 spg_storage::RowLocator::Hot(i) => match table.rows().get(i) {
8769 Some(r) => r,
8770 None => continue,
8771 },
8772 spg_storage::RowLocator::Cold { .. } => continue,
8773 };
8774 let mut ok = true;
8777 for (lp, rp) in eq_pairs.iter().skip(1) {
8778 let lv = left.values.get(*lp);
8779 let rv = right.values.get(*rp);
8780 let eq = match (lv, rv) {
8781 (Some(a), Some(b)) => {
8782 !matches!(a, Value::Null)
8783 && !matches!(b, Value::Null)
8784 && value_cmp(a, b) == core::cmp::Ordering::Equal
8785 }
8786 _ => false,
8787 };
8788 if !eq {
8789 ok = false;
8790 break;
8791 }
8792 }
8793 if !ok {
8794 continue;
8795 }
8796 let mut combined_vals = left.values.clone();
8797 combined_vals.extend(right.values.iter().cloned());
8798 let combined = Row::new(combined_vals);
8799 let keep = if residual.is_empty() {
8800 true
8801 } else {
8802 let mut k = true;
8803 for r in &residual {
8804 let cond = self.eval_expr_with_correlated(
8805 r, &combined, &ctx, cancel, None,
8806 )?;
8807 if !matches!(cond, Value::Bool(true)) {
8808 k = false;
8809 break;
8810 }
8811 }
8812 k
8813 };
8814 if keep {
8815 next.push(combined);
8816 left_matched = true;
8817 }
8818 }
8819 }
8820 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8821 let mut combined_vals = left.values.clone();
8822 for _ in 0..right_arity {
8823 combined_vals.push(Value::Null);
8824 }
8825 next.push(Row::new(combined_vals));
8826 }
8827 }
8828 working = next;
8829 consumed_cols += right_arity;
8830 continue;
8831 }
8832 let lazy_rows: Option<Vec<Row>> = if peer.eager_rows.is_none() && peer.lateral.is_none()
8835 {
8836 let tname = peer.join_table.as_deref().unwrap_or("");
8837 let mut rows: Vec<Row> = self
8838 .active_catalog()
8839 .get(tname)
8840 .map(|t| t.rows().iter().cloned().collect())
8841 .unwrap_or_default();
8842 if let Some(needed) = needed {
8843 Self::null_out_unreferenced(&mut rows, &peer.cols, &peer.alias, needed);
8844 }
8845 Some(rows)
8846 } else {
8847 None
8848 };
8849 let eager_view: Option<&Vec<Row>> = peer.eager_rows.as_ref().or(lazy_rows.as_ref());
8850 if !eq_pairs.is_empty() && peer.lateral.is_none() {
8851 let rights = eager_view.expect("non-lateral peer eager");
8852 let mut table: alloc::collections::BTreeMap<String, Vec<usize>> =
8853 alloc::collections::BTreeMap::new();
8854 let mut keybuf: Vec<Value> = Vec::with_capacity(eq_pairs.len());
8855 'build: for (ri, right) in rights.iter().enumerate() {
8856 keybuf.clear();
8857 for (_, rpos) in &eq_pairs {
8858 let v = right.values.get(*rpos).cloned().unwrap_or(Value::Null);
8859 if matches!(v, Value::Null) {
8860 continue 'build;
8861 }
8862 keybuf.push(v);
8863 }
8864 table
8865 .entry(aggregate::encode_key(&keybuf))
8866 .or_default()
8867 .push(ri);
8868 }
8869 for left in &working {
8870 cancel.check()?;
8871 let mut left_matched = false;
8872 keybuf.clear();
8873 let mut left_has_null = false;
8874 for (lpos, _) in &eq_pairs {
8875 let v = left.values.get(*lpos).cloned().unwrap_or(Value::Null);
8876 if matches!(v, Value::Null) {
8877 left_has_null = true;
8878 break;
8879 }
8880 keybuf.push(v);
8881 }
8882 if !left_has_null
8883 && let Some(cands) = table.get(&aggregate::encode_key(&keybuf))
8884 {
8885 for &ri in cands {
8886 let right = &rights[ri];
8887 let mut combined_vals = left.values.clone();
8888 combined_vals.extend(right.values.iter().cloned());
8889 let combined = Row::new(combined_vals);
8890 let keep = if residual.is_empty() {
8891 true
8892 } else {
8893 let mut ok = true;
8894 for r in &residual {
8895 let cond = self.eval_expr_with_correlated(
8896 r, &combined, &ctx, cancel, None,
8897 )?;
8898 if !matches!(cond, Value::Bool(true)) {
8899 ok = false;
8900 break;
8901 }
8902 }
8903 ok
8904 };
8905 if keep {
8906 next.push(combined);
8907 left_matched = true;
8908 }
8909 }
8910 }
8911 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8912 let mut combined_vals = left.values.clone();
8913 for _ in 0..right_arity {
8914 combined_vals.push(Value::Null);
8915 }
8916 next.push(Row::new(combined_vals));
8917 }
8918 }
8919 working = next;
8920 consumed_cols += right_arity;
8921 debug_assert!(consumed_cols <= combined_schema.len());
8922 continue;
8923 }
8924 for left in &working {
8926 cancel.check()?;
8927 let mut left_matched = false;
8928 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8929 Some(inner) => {
8930 let outer_schema = &combined_schema[..consumed_cols];
8934 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8935 alloc::borrow::Cow::Owned(rows)
8936 }
8937 None => {
8938 let r = eager_view.expect("non-lateral peer eager");
8939 alloc::borrow::Cow::Borrowed(r.as_slice())
8940 }
8941 };
8942 for right in per_left_rrows.as_ref() {
8943 let mut combined_vals = left.values.clone();
8944 combined_vals.extend(right.values.iter().cloned());
8945 let combined = Row::new(combined_vals);
8946 let keep = if let Some(on_expr) = peer.on {
8947 let cond =
8950 self.eval_expr_with_correlated(on_expr, &combined, &ctx, cancel, None)?;
8951 matches!(cond, Value::Bool(true))
8952 } else {
8953 true
8954 };
8955 if keep {
8956 next.push(combined);
8957 left_matched = true;
8958 }
8959 }
8960 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8961 let mut combined_vals = left.values.clone();
8962 for _ in 0..right_arity {
8963 combined_vals.push(Value::Null);
8964 }
8965 next.push(Row::new(combined_vals));
8966 }
8967 }
8968 working = next;
8969 if working.len() > MAX_JOIN_INTERMEDIATE_ROWS {
8970 return Err(EngineError::Unsupported(alloc::format!(
8971 "join intermediate result exceeds {MAX_JOIN_INTERMEDIATE_ROWS} rows ({} so far) - add join predicates",
8972 working.len()
8973 )));
8974 }
8975 consumed_cols += right_arity;
8976 debug_assert!(consumed_cols <= combined_schema.len());
8977 }
8978 let mut filtered: Vec<Row> = Vec::new();
8979 let mut memo = memoize::MemoizeCache::default();
8985 for row in working {
8986 if let Some(where_expr) = where_ {
8987 let cond = self.eval_expr_with_correlated(
8988 where_expr,
8989 &row,
8990 &ctx,
8991 cancel,
8992 Some(&mut memo),
8993 )?;
8994 if !matches!(cond, Value::Bool(true)) {
8995 continue;
8996 }
8997 }
8998 filtered.push(row);
8999 }
9000 Ok((combined_schema, filtered))
9001 }
9002
9003 fn lateral_probe_schema(
9009 &self,
9010 inner: &SelectStatement,
9011 ) -> Result<Vec<ColumnSchema>, EngineError> {
9012 match self.execute_readonly_select_for_lateral_probe(inner) {
9022 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
9023 _ => {
9029 let mut out: Vec<ColumnSchema> = Vec::new();
9030 for (i, item) in inner.items.iter().enumerate() {
9031 let name = match item {
9032 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
9033 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
9034 SelectItem::Wildcard => alloc::format!("col{i}"),
9035 };
9036 out.push(ColumnSchema::new(name, DataType::Text, true));
9037 }
9038 Ok(out)
9039 }
9040 }
9041 }
9042
9043 fn execute_readonly_select_for_lateral_probe(
9049 &self,
9050 inner: &SelectStatement,
9051 ) -> Result<QueryResult, EngineError> {
9052 self.exec_bare_select_cancel(inner, CancelToken::none())
9053 }
9054
9055 fn materialise_lateral_for_outer(
9061 &self,
9062 inner: &SelectStatement,
9063 outer_schema: &[ColumnSchema],
9064 outer_row: &Row,
9065 ) -> Result<Vec<Row>, EngineError> {
9066 let mut substituted = inner.clone();
9067 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
9068 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
9069 match result {
9070 QueryResult::Rows { rows, .. } => Ok(rows),
9071 _ => Err(EngineError::Unsupported(
9072 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
9073 )),
9074 }
9075 }
9076
9077 fn exec_joined_select(
9078 &self,
9079 stmt: &SelectStatement,
9080 from: &FromClause,
9081 cancel: CancelToken<'_>,
9082 ) -> Result<QueryResult, EngineError> {
9083 let (combined_schema, filtered) = {
9091 let mut needed = alloc::collections::BTreeSet::new();
9092 let prunable = collect_qualified_refs(stmt, &mut needed).is_some();
9093 self.build_joined_filtered_rows(
9094 from,
9095 stmt.where_.as_ref(),
9096 cancel,
9097 if prunable { Some(&needed) } else { None },
9098 )?
9099 };
9100 let ctx = EvalContext::new(&combined_schema, None);
9101 if aggregate::uses_aggregate(stmt) {
9104 let refs: Vec<&Row> = filtered.iter().collect();
9105 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
9106 self.eval_expr_with_correlated(e, r, c, cancel, None)
9107 .map_err(|err| match err {
9108 EngineError::Eval(ev) => ev,
9109 other => eval::EvalError::TypeMismatch {
9110 detail: alloc::format!("{other}"),
9111 },
9112 })
9113 };
9114 let mut agg =
9115 aggregate::run(stmt, &refs, &combined_schema, None, Some(&agg_correlated))?;
9116 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
9117 return Ok(QueryResult::Rows {
9118 columns: agg.columns,
9119 rows: agg.rows,
9120 });
9121 }
9122
9123 let projection = build_projection(&stmt.items, &combined_schema, "")?;
9124 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
9125 let mut proj_memo = memoize::MemoizeCache::default();
9126 for row in &filtered {
9127 let mut values = Vec::with_capacity(projection.len());
9128 for p in &projection {
9129 values.push(self.eval_expr_with_correlated(
9132 &p.expr,
9133 row,
9134 &ctx,
9135 cancel,
9136 Some(&mut proj_memo),
9137 )?);
9138 }
9139 let order_keys = if stmt.order_by.is_empty() {
9140 Vec::new()
9141 } else {
9142 build_order_keys(&stmt.order_by, row, &ctx)?
9143 };
9144 tagged.push((order_keys, Row::new(values)));
9145 }
9146 if !stmt.order_by.is_empty() {
9147 let keep = if stmt.distinct {
9148 None
9149 } else {
9150 stmt.limit_literal()
9151 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
9152 };
9153 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9154 partial_sort_tagged(&mut tagged, keep, &descs);
9155 }
9156 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9157 if stmt.distinct {
9158 output_rows = dedup_rows(output_rows);
9159 }
9160 apply_offset_and_limit(
9161 &mut output_rows,
9162 stmt.offset_literal(),
9163 stmt.limit_literal(),
9164 );
9165 let columns: Vec<ColumnSchema> = projection
9166 .into_iter()
9167 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9168 .collect();
9169 Ok(QueryResult::Rows {
9170 columns,
9171 rows: output_rows,
9172 })
9173 }
9174}
9175
9176#[derive(Debug, Clone)]
9179struct ProjectedItem {
9180 expr: Expr,
9181 output_name: String,
9182 ty: DataType,
9183 nullable: bool,
9184}
9185
9186fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
9192 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
9193 for r in rows {
9194 if !out.iter().any(|seen| seen == &r) {
9195 out.push(r);
9196 }
9197 }
9198 out
9199}
9200
9201fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
9205 match v {
9206 Value::Null => Ok(f64::INFINITY),
9207 Value::SmallInt(n) => Ok(f64::from(*n)),
9208 Value::Int(n) => Ok(f64::from(*n)),
9209 Value::Date(d) => Ok(f64::from(*d)),
9210 #[allow(clippy::cast_precision_loss)]
9211 Value::Timestamp(t) => Ok(*t as f64),
9212 #[allow(clippy::cast_precision_loss)]
9215 Value::Time(us) => Ok(*us as f64),
9216 Value::Year(y) => Ok(f64::from(*y)),
9220 #[allow(clippy::cast_precision_loss)]
9225 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
9226 #[allow(clippy::cast_precision_loss)]
9228 Value::Money(c) => Ok(*c as f64),
9229 Value::Range { .. } => Err(EngineError::Unsupported(
9232 "ORDER BY of a range value is not supported in v7.17.0".into(),
9233 )),
9234 Value::Hstore(_) => Err(EngineError::Unsupported(
9236 "ORDER BY of a hstore value is not supported".into(),
9237 )),
9238 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
9240 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
9241 ),
9242 #[allow(clippy::cast_precision_loss)]
9243 Value::Numeric { scaled, scale } => {
9244 let mut divisor = 1.0_f64;
9250 for _ in 0..*scale {
9251 divisor *= 10.0;
9252 }
9253 Ok((*scaled as f64) / divisor)
9254 }
9255 #[allow(clippy::cast_precision_loss)]
9256 Value::BigInt(n) => Ok(*n as f64),
9257 Value::Float(x) => Ok(*x),
9258 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
9259 Value::Text(s) => {
9260 let mut key: u64 = 0;
9264 for &b in s.as_bytes().iter().take(8) {
9265 key = (key << 8) | u64::from(b);
9266 }
9267 #[allow(clippy::cast_precision_loss)]
9268 Ok(key as f64)
9269 }
9270 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
9271 Err(EngineError::Unsupported(
9272 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
9273 ))
9274 }
9275 Value::Interval { .. } => Err(EngineError::Unsupported(
9276 "ORDER BY of an INTERVAL is not supported in v2.11 \
9277 (months vs micros has no single canonical ordering)"
9278 .into(),
9279 )),
9280 Value::Json(_) => Err(EngineError::Unsupported(
9281 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
9282 )),
9283 _ => Err(EngineError::Unsupported(
9287 "ORDER BY of this value type is not supported".into(),
9288 )),
9289 }
9290}
9291
9292fn try_nsw_knn(
9306 stmt: &SelectStatement,
9307 table: &Table,
9308 schema_cols: &[ColumnSchema],
9309 table_alias: &str,
9310) -> Option<Vec<usize>> {
9311 if stmt.distinct {
9312 return None;
9313 }
9314 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
9315 if limit == 0 {
9316 return None;
9317 }
9318 if stmt.order_by.len() != 1 {
9322 return None;
9323 }
9324 let order = &stmt.order_by[0];
9325 if order.desc {
9329 return None;
9330 }
9331 let Expr::Binary { lhs, op, rhs } = &order.expr else {
9332 return None;
9333 };
9334 let metric = match op {
9335 BinOp::L2Distance => spg_storage::NswMetric::L2,
9336 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
9337 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
9338 _ => return None,
9339 };
9340 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
9342 (lhs.as_ref(), rhs.as_ref())
9343 else {
9344 return None;
9345 };
9346 if let Some(q) = &col.qualifier
9347 && q != table_alias
9348 {
9349 return None;
9350 }
9351 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
9352 let query = literal_to_vector(literal)?;
9353 let idx = spg_storage::nsw_index_on(table, col_pos)?;
9354 if let Some(where_expr) = &stmt.where_ {
9355 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
9359 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
9360 let ctx = EvalContext::new(schema_cols, Some(table_alias));
9361 let mut kept: Vec<usize> = Vec::with_capacity(limit);
9362 for i in candidates {
9363 let row = &table.rows()[i];
9364 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
9365 if matches!(cond, Value::Bool(true)) {
9366 kept.push(i);
9367 if kept.len() >= limit {
9368 break;
9369 }
9370 }
9371 }
9372 Some(kept)
9373 } else {
9374 Some(spg_storage::nsw_query(
9375 table, &idx.name, &query, limit, metric,
9376 ))
9377 }
9378}
9379
9380const NSW_OVER_FETCH_FLOOR: usize = 32;
9384
9385fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
9388 match e {
9389 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
9390 Expr::Cast { expr, .. } => literal_to_vector(expr),
9391 _ => None,
9392 }
9393}
9394
9395fn materialise_in_order(
9399 stmt: &SelectStatement,
9400 table: &Table,
9401 schema_cols: &[ColumnSchema],
9402 table_alias: &str,
9403 ordered_rows: &[usize],
9404) -> Result<QueryResult, EngineError> {
9405 let ctx = EvalContext::new(schema_cols, Some(table_alias));
9406 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
9407 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
9408 for &i in ordered_rows {
9409 let row = &table.rows()[i];
9410 let mut values = Vec::with_capacity(projection.len());
9411 for p in &projection {
9412 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
9413 }
9414 output_rows.push(Row::new(values));
9415 }
9416 apply_offset_and_limit(
9417 &mut output_rows,
9418 stmt.offset_literal(),
9419 stmt.limit_literal(),
9420 );
9421 let columns: Vec<ColumnSchema> = projection
9422 .into_iter()
9423 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9424 .collect();
9425 Ok(QueryResult::Rows {
9426 columns,
9427 rows: output_rows,
9428 })
9429}
9430
9431fn try_index_seek_positions(
9444 where_expr: &Expr,
9445 schema_cols: &[ColumnSchema],
9446 table: &Table,
9447 table_alias: &str,
9448) -> Option<Vec<usize>> {
9449 if let Expr::Binary {
9450 lhs,
9451 op: BinOp::And,
9452 rhs,
9453 } = where_expr
9454 {
9455 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
9456 return Some(p);
9457 }
9458 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
9459 }
9460 let Expr::Binary {
9461 lhs,
9462 op: BinOp::Eq,
9463 rhs,
9464 } = where_expr
9465 else {
9466 return None;
9467 };
9468 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9469 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9470 let idx = table.index_on(col_pos)?;
9471 let key = IndexKey::from_value(&value)?;
9472 let locators = idx.lookup_eq(&key);
9473 let mut out = Vec::with_capacity(locators.len());
9474 for loc in locators {
9475 match *loc {
9476 spg_storage::RowLocator::Hot(i) => out.push(i),
9477 spg_storage::RowLocator::Cold { .. } => return None,
9478 }
9479 }
9480 Some(out)
9481}
9482
9483fn try_index_seek<'a>(
9484 where_expr: &Expr,
9485 schema_cols: &[ColumnSchema],
9486 catalog: &'a Catalog,
9487 table: &'a Table,
9488 table_alias: &str,
9489) -> Option<Vec<Cow<'a, Row>>> {
9490 if let Expr::Binary {
9497 lhs,
9498 op: BinOp::And,
9499 rhs,
9500 } = where_expr
9501 {
9502 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
9505 return Some(rows);
9506 }
9507 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
9508 }
9509 let Expr::Binary {
9510 lhs,
9511 op: BinOp::Eq,
9512 rhs,
9513 } = where_expr
9514 else {
9515 return None;
9516 };
9517 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9518 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9519 let idx = table.index_on(col_pos)?;
9520 let key = IndexKey::from_value(&value)?;
9521 let locators = idx.lookup_eq(&key);
9522 let table_name = table.schema().name.as_str();
9523 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
9531 for loc in locators {
9532 match *loc {
9533 spg_storage::RowLocator::Hot(i) => {
9534 if let Some(row) = table.rows().get(i) {
9535 out.push(Cow::Borrowed(row));
9536 }
9537 }
9538 spg_storage::RowLocator::Cold { segment_id, .. } => {
9539 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
9540 out.push(Cow::Owned(row));
9541 }
9542 }
9543 }
9544 }
9545 Some(out)
9546}
9547
9548fn try_gin_seek<'a>(
9567 where_expr: &Expr,
9568 schema_cols: &[ColumnSchema],
9569 catalog: &'a Catalog,
9570 table: &'a Table,
9571 table_alias: &str,
9572 ctx: &eval::EvalContext<'_>,
9573) -> Option<Vec<Cow<'a, Row>>> {
9574 if let Expr::Binary {
9575 lhs,
9576 op: BinOp::And,
9577 rhs,
9578 } = where_expr
9579 {
9580 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
9581 return Some(rows);
9582 }
9583 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
9584 }
9585 if let Expr::Binary {
9594 lhs,
9595 op: BinOp::Or,
9596 rhs,
9597 } = where_expr
9598 {
9599 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
9600 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
9601 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
9602 out.extend(left);
9603 out.extend(right);
9604 return Some(out);
9605 }
9606 let Expr::Binary {
9607 lhs,
9608 op: BinOp::TsMatch,
9609 rhs,
9610 } = where_expr
9611 else {
9612 return None;
9613 };
9614 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
9619 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
9620 let idx = table
9627 .indices()
9628 .iter()
9629 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
9630 let candidates = gin_query_candidates(idx, &query)?;
9631 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
9633 for loc in candidates {
9634 match loc {
9635 spg_storage::RowLocator::Hot(i) => {
9636 if let Some(row) = table.rows().get(i) {
9637 out.push(Cow::Borrowed(row));
9638 }
9639 }
9640 spg_storage::RowLocator::Cold { .. } => {}
9647 }
9648 }
9649 Some(out)
9650}
9651
9652fn try_trgm_seek<'a>(
9668 where_expr: &Expr,
9669 schema_cols: &[ColumnSchema],
9670 table: &'a Table,
9671 table_alias: &str,
9672) -> Option<Vec<Cow<'a, Row>>> {
9673 if let Expr::Binary {
9674 lhs,
9675 op: BinOp::And,
9676 rhs,
9677 } = where_expr
9678 {
9679 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9680 return Some(rows);
9681 }
9682 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9683 }
9684 let Expr::Like { expr, pattern, .. } = where_expr else {
9690 return None;
9691 };
9692 let Expr::Column(c) = expr.as_ref() else {
9694 return None;
9695 };
9696 if let Some(q) = &c.qualifier
9697 && q != table_alias
9698 {
9699 return None;
9700 }
9701 let col_pos = schema_cols
9702 .iter()
9703 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9704 let idx = table
9706 .indices()
9707 .iter()
9708 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9709 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9713 return None;
9714 };
9715 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9716 let mut iter = trigrams.iter();
9719 let first = iter.next()?;
9720 let mut acc: Vec<spg_storage::RowLocator> = {
9721 let mut v = idx.gin_trgm_lookup(first).to_vec();
9722 v.sort_by_key(locator_sort_key);
9723 v.dedup_by_key(|l| locator_sort_key(l));
9724 v
9725 };
9726 for tri in iter {
9727 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9728 next.sort_by_key(locator_sort_key);
9729 next.dedup_by_key(|l| locator_sort_key(l));
9730 let mut merged: Vec<spg_storage::RowLocator> =
9732 Vec::with_capacity(acc.len().min(next.len()));
9733 let (mut i, mut j) = (0usize, 0usize);
9734 while i < acc.len() && j < next.len() {
9735 let lk = locator_sort_key(&acc[i]);
9736 let rk = locator_sort_key(&next[j]);
9737 match lk.cmp(&rk) {
9738 core::cmp::Ordering::Less => i += 1,
9739 core::cmp::Ordering::Greater => j += 1,
9740 core::cmp::Ordering::Equal => {
9741 merged.push(acc[i]);
9742 i += 1;
9743 j += 1;
9744 }
9745 }
9746 }
9747 acc = merged;
9748 if acc.is_empty() {
9749 break;
9750 }
9751 }
9752 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9753 for loc in acc {
9754 if let spg_storage::RowLocator::Hot(i) = loc
9755 && let Some(row) = table.rows().get(i)
9756 {
9757 out.push(Cow::Borrowed(row));
9758 }
9759 }
9761 Some(out)
9762}
9763
9764fn resolve_gin_col_query(
9770 col_side: &Expr,
9771 query_side: &Expr,
9772 schema_cols: &[ColumnSchema],
9773 table_alias: &str,
9774 ctx: &eval::EvalContext<'_>,
9775) -> Option<(usize, spg_storage::TsQueryAst)> {
9776 let column = match col_side {
9781 Expr::Column(c) => c,
9782 Expr::FunctionCall { name, args }
9783 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9784 {
9785 if let Expr::Column(c) = args.last().unwrap() {
9789 c
9790 } else {
9791 return None;
9792 }
9793 }
9794 _ => return None,
9795 };
9796 let c = column;
9797 if let Some(q) = &c.qualifier
9798 && q != table_alias
9799 {
9800 return None;
9801 }
9802 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9803 let empty_row = Row::new(Vec::new());
9807 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9808 let Value::TsQuery(q) = v else { return None };
9809 Some((pos, q))
9810}
9811
9812fn gin_query_candidates(
9823 idx: &spg_storage::Index,
9824 query: &spg_storage::TsQueryAst,
9825) -> Option<Vec<spg_storage::RowLocator>> {
9826 use spg_storage::TsQueryAst;
9827 match query {
9828 TsQueryAst::Term { word, .. } => {
9829 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9830 v.sort_by_key(locator_sort_key);
9831 v.dedup_by_key(|l| locator_sort_key(l));
9832 Some(v)
9833 }
9834 TsQueryAst::And(l, r) => {
9835 let mut left = gin_query_candidates(idx, l)?;
9836 let mut right = gin_query_candidates(idx, r)?;
9837 left.sort_by_key(locator_sort_key);
9838 right.sort_by_key(locator_sort_key);
9839 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9841 let (mut i, mut j) = (0usize, 0usize);
9842 while i < left.len() && j < right.len() {
9843 let lk = locator_sort_key(&left[i]);
9844 let rk = locator_sort_key(&right[j]);
9845 match lk.cmp(&rk) {
9846 core::cmp::Ordering::Less => i += 1,
9847 core::cmp::Ordering::Greater => j += 1,
9848 core::cmp::Ordering::Equal => {
9849 out.push(left[i]);
9850 i += 1;
9851 j += 1;
9852 }
9853 }
9854 }
9855 Some(out)
9856 }
9857 TsQueryAst::Or(l, r) => {
9858 let mut out = gin_query_candidates(idx, l)?;
9859 out.extend(gin_query_candidates(idx, r)?);
9860 out.sort_by_key(locator_sort_key);
9861 out.dedup_by_key(|l| locator_sort_key(l));
9862 Some(out)
9863 }
9864 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9869 }
9870}
9871
9872fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9877 match *l {
9878 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9879 spg_storage::RowLocator::Cold {
9880 segment_id,
9881 page_offset,
9882 } => (1, u64::from(segment_id), u64::from(page_offset)),
9883 }
9884}
9885
9886fn try_pk_predicate(
9898 where_expr: &Expr,
9899 schema_cols: &[ColumnSchema],
9900 table_alias: &str,
9901) -> Option<(usize, IndexKey)> {
9902 let Expr::Binary {
9903 lhs,
9904 op: BinOp::Eq,
9905 rhs,
9906 } = where_expr
9907 else {
9908 return None;
9909 };
9910 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9911 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9912 let key = IndexKey::from_value(&value)?;
9913 Some((col_pos, key))
9914}
9915
9916fn resolve_col_literal_pair(
9917 col_side: &Expr,
9918 lit_side: &Expr,
9919 schema_cols: &[ColumnSchema],
9920 table_alias: &str,
9921) -> Option<(usize, Value)> {
9922 let Expr::Column(c) = col_side else {
9923 return None;
9924 };
9925 if let Some(q) = &c.qualifier
9926 && q != table_alias
9927 {
9928 return None;
9929 }
9930 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9931 let Expr::Literal(l) = lit_side else {
9932 return None;
9933 };
9934 let v = match l {
9935 Literal::Integer(n) => {
9936 if let Ok(small) = i32::try_from(*n) {
9937 Value::Int(small)
9938 } else {
9939 Value::BigInt(*n)
9940 }
9941 }
9942 Literal::Float(x) => Value::Float(*x),
9943 Literal::String(s) => Value::Text(s.clone()),
9944 Literal::Bool(b) => Value::Bool(*b),
9945 Literal::Null => Value::Null,
9946 Literal::Vector(_)
9949 | Literal::Interval { .. }
9950 | Literal::TextArray(_)
9951 | Literal::IntArray(_)
9952 | Literal::BigIntArray(_) => return None,
9953 };
9954 Some((pos, v))
9955}
9956
9957fn resolve_projection_column<'a>(
9962 c: &ColumnName,
9963 schema_cols: &'a [ColumnSchema],
9964 table_alias: &str,
9965) -> Result<&'a ColumnSchema, EngineError> {
9966 if let Some(q) = &c.qualifier {
9967 let composite = alloc::format!("{q}.{name}", name = c.name);
9968 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9969 return Ok(s);
9970 }
9971 if q == table_alias
9974 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9975 {
9976 return Ok(s);
9977 }
9978 let prefix = alloc::format!("{q}.");
9982 let qualifier_known =
9983 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9984 if !qualifier_known {
9985 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9986 qualifier: q.clone(),
9987 }));
9988 }
9989 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9990 name: c.name.clone(),
9991 }));
9992 }
9993 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9994 return Ok(s);
9995 }
9996 let suffix = alloc::format!(".{name}", name = c.name);
9997 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9998 let first = matches.next();
9999 let extra = matches.next();
10000 match (first, extra) {
10001 (Some(s), None) => Ok(s),
10002 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
10003 detail: alloc::format!("ambiguous column reference: {}", c.name),
10004 })),
10005 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
10006 name: c.name.clone(),
10007 })),
10008 }
10009}
10010
10011fn build_projection(
10012 items: &[SelectItem],
10013 schema_cols: &[ColumnSchema],
10014 table_alias: &str,
10015) -> Result<Vec<ProjectedItem>, EngineError> {
10016 let mut out = Vec::new();
10017 for item in items {
10018 match item {
10019 SelectItem::Wildcard => {
10020 for col in schema_cols {
10021 out.push(ProjectedItem {
10022 expr: Expr::Column(ColumnName {
10023 qualifier: None,
10024 name: col.name.clone(),
10025 }),
10026 output_name: col.name.clone(),
10027 ty: col.ty,
10028 nullable: col.nullable,
10029 });
10030 }
10031 }
10032 SelectItem::Expr { expr, alias } => {
10033 if let Expr::Column(c) = expr {
10040 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
10041 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
10042 out.push(ProjectedItem {
10043 expr: expr.clone(),
10044 output_name,
10045 ty: sch.ty,
10046 nullable: sch.nullable,
10047 });
10048 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
10049 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
10050 out.push(ProjectedItem {
10051 expr: expr.clone(),
10052 output_name,
10053 ty: shape.ty,
10054 nullable: shape.nullable,
10055 });
10056 } else {
10057 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
10058 out.push(ProjectedItem {
10059 expr: expr.clone(),
10060 output_name,
10061 ty: DataType::Text,
10062 nullable: true,
10063 });
10064 }
10065 }
10066 }
10067 }
10068 Ok(out)
10069}
10070
10071fn numeric_from_integer(
10075 n: i128,
10076 precision: u8,
10077 scale: u8,
10078 col_name: &str,
10079) -> Result<Value, EngineError> {
10080 let factor = pow10_i128(scale);
10081 let scaled = n.checked_mul(factor).ok_or_else(|| {
10082 EngineError::Unsupported(alloc::format!(
10083 "integer overflow scaling value for column `{col_name}` to scale {scale}"
10084 ))
10085 })?;
10086 check_precision(scaled, precision, col_name)?;
10087 Ok(Value::Numeric { scaled, scale })
10088}
10089
10090#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
10093fn numeric_from_float(
10094 x: f64,
10095 precision: u8,
10096 scale: u8,
10097 col_name: &str,
10098) -> Result<Value, EngineError> {
10099 if !x.is_finite() {
10100 return Err(EngineError::Unsupported(alloc::format!(
10101 "cannot store non-finite float in NUMERIC column `{col_name}`"
10102 )));
10103 }
10104 let mut factor = 1.0_f64;
10105 for _ in 0..scale {
10106 factor *= 10.0;
10107 }
10108 let shifted = x * factor;
10113 let biased = if shifted >= 0.0 {
10114 shifted + 0.5
10115 } else {
10116 shifted - 0.5
10117 };
10118 if !(-1e38..=1e38).contains(&biased) {
10121 return Err(EngineError::Unsupported(alloc::format!(
10122 "value {x} overflows NUMERIC range for column `{col_name}`"
10123 )));
10124 }
10125 let scaled = biased as i128;
10126 check_precision(scaled, precision, col_name)?;
10127 Ok(Value::Numeric { scaled, scale })
10128}
10129
10130fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
10137 let s = s.trim();
10138 if s.is_empty() {
10139 return None;
10140 }
10141 let (negative, rest) = match s.as_bytes()[0] {
10142 b'-' => (true, &s[1..]),
10143 b'+' => (false, &s[1..]),
10144 _ => (false, s),
10145 };
10146 if rest.is_empty() {
10147 return None;
10148 }
10149 if rest.bytes().any(|b| b == b'e' || b == b'E') {
10153 return None;
10154 }
10155 let (int_part, frac_part) = match rest.find('.') {
10156 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
10157 None => (rest, ""),
10158 };
10159 if int_part.is_empty() && frac_part.is_empty() {
10160 return None;
10161 }
10162 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
10163 return None;
10164 }
10165 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
10166 return None;
10167 }
10168 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
10169 if scale_u32 > u32::from(u8::MAX) {
10170 return None;
10171 }
10172 let scale = scale_u32 as u8;
10173 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
10174 if negative {
10175 digits.push('-');
10176 }
10177 digits.push_str(int_part);
10178 digits.push_str(frac_part);
10179 let digits = if digits == "-" {
10181 return None;
10182 } else if digits.is_empty() {
10183 "0"
10184 } else {
10185 digits.as_str()
10186 };
10187 let mantissa: i128 = digits.parse().ok()?;
10188 Some((mantissa, scale))
10189}
10190
10191fn numeric_rescale(
10194 scaled: i128,
10195 src_scale: u8,
10196 precision: u8,
10197 dst_scale: u8,
10198 col_name: &str,
10199) -> Result<Value, EngineError> {
10200 let new_scaled = if dst_scale >= src_scale {
10201 let bump = pow10_i128(dst_scale - src_scale);
10202 scaled.checked_mul(bump).ok_or_else(|| {
10203 EngineError::Unsupported(alloc::format!(
10204 "overflow rescaling NUMERIC for column `{col_name}`"
10205 ))
10206 })?
10207 } else {
10208 let drop = pow10_i128(src_scale - dst_scale);
10209 let half = drop / 2;
10210 if scaled >= 0 {
10211 (scaled + half) / drop
10212 } else {
10213 (scaled - half) / drop
10214 }
10215 };
10216 check_precision(new_scaled, precision, col_name)?;
10217 Ok(Value::Numeric {
10218 scaled: new_scaled,
10219 scale: dst_scale,
10220 })
10221}
10222
10223const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
10226 if scale == 0 {
10227 return scaled;
10228 }
10229 let factor = pow10_i128_const(scale);
10230 scaled / factor
10231}
10232
10233fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
10237 if precision == 0 {
10238 return Ok(());
10239 }
10240 let limit = pow10_i128(precision);
10241 if scaled.unsigned_abs() >= limit.unsigned_abs() {
10242 return Err(EngineError::Unsupported(alloc::format!(
10243 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
10244 )));
10245 }
10246 Ok(())
10247}
10248
10249const fn pow10_i128_const(p: u8) -> i128 {
10250 let mut acc: i128 = 1;
10251 let mut i = 0;
10252 while i < p {
10253 acc *= 10;
10254 i += 1;
10255 }
10256 acc
10257}
10258
10259fn pow10_i128(p: u8) -> i128 {
10260 pow10_i128_const(p)
10261}
10262
10263impl Engine {
10278 #[allow(
10289 clippy::too_many_lines,
10290 clippy::type_complexity,
10291 clippy::needless_range_loop
10292 )] fn exec_select_with_window(
10294 &self,
10295 stmt: &SelectStatement,
10296 cancel: CancelToken<'_>,
10297 ) -> Result<QueryResult, EngineError> {
10298 let from = stmt.from.as_ref().ok_or_else(|| {
10299 EngineError::Unsupported("window functions require a FROM clause".into())
10300 })?;
10301 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
10310 let filtered: Vec<Row>;
10311 if from.joins.is_empty() {
10312 let primary = &from.primary;
10313 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
10314 StorageError::TableNotFound {
10315 name: primary.name.clone(),
10316 }
10317 })?;
10318 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
10319 schema_cols_owned = table.schema().columns.clone();
10320 alias_opt = Some(alias);
10321 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
10326 let mut owned: Vec<Row> = Vec::new();
10327 for (i, row) in table.rows().iter().enumerate() {
10328 if i.is_multiple_of(256) {
10329 cancel.check()?;
10330 }
10331 if let Some(w) = &stmt.where_ {
10332 let cond = eval::eval_expr(w, row, &ctx)?;
10333 if !matches!(cond, Value::Bool(true)) {
10334 continue;
10335 }
10336 }
10337 owned.push(row.clone());
10338 }
10339 filtered = owned;
10340 } else {
10341 let (combined_schema, rows) =
10342 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel, None)?;
10343 schema_cols_owned = combined_schema;
10344 alias_opt = None;
10345 filtered = rows;
10346 }
10347 let schema_cols = &schema_cols_owned;
10348 let ctx = self.ev_ctx(schema_cols, alias_opt);
10349 let alias = alias_opt.unwrap_or("");
10350 let n_rows = filtered.len();
10351 let filtered_refs: Vec<&Row> = filtered.iter().collect();
10355
10356 let mut window_nodes: Vec<Expr> = Vec::new();
10358 for item in &stmt.items {
10359 if let SelectItem::Expr { expr, .. } = item {
10360 collect_window_nodes(expr, &mut window_nodes);
10361 }
10362 }
10363
10364 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
10367 for wnode in &window_nodes {
10368 let Expr::WindowFunction {
10369 name,
10370 args,
10371 partition_by,
10372 order_by,
10373 frame,
10374 null_treatment,
10375 } = wnode
10376 else {
10377 unreachable!("collect_window_nodes pushes only WindowFunction");
10378 };
10379 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)> =
10381 Vec::with_capacity(n_rows);
10382 for (i, row) in filtered.iter().enumerate() {
10383 let pkey: Vec<Value> = partition_by
10384 .iter()
10385 .map(|p| eval::eval_expr(p, row, &ctx))
10386 .collect::<Result<_, _>>()?;
10387 let okey: Vec<(Value, bool, Option<bool>)> = order_by
10388 .iter()
10389 .map(|(e, desc, nf)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc, *nf)))
10390 .collect::<Result<_, _>>()?;
10391 indexed.push((pkey, okey, i));
10392 }
10393 indexed.sort_by(|a, b| {
10396 let p_cmp = partition_key_cmp(&a.0, &b.0);
10397 if p_cmp != core::cmp::Ordering::Equal {
10398 return p_cmp;
10399 }
10400 order_key_cmp(&a.1, &b.1)
10401 });
10402 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
10404 let mut p_start = 0;
10405 while p_start < indexed.len() {
10406 let mut p_end = p_start + 1;
10407 while p_end < indexed.len()
10408 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
10409 == core::cmp::Ordering::Equal
10410 {
10411 p_end += 1;
10412 }
10413 compute_window_partition(
10415 name,
10416 args,
10417 !order_by.is_empty(),
10418 frame.as_ref(),
10419 *null_treatment,
10420 &indexed[p_start..p_end],
10421 &filtered_refs,
10422 &ctx,
10423 &mut out_vals,
10424 )?;
10425 p_start = p_end;
10426 }
10427 win_vals.push(out_vals);
10428 }
10429
10430 let mut ext_cols = schema_cols.clone();
10432 for i in 0..window_nodes.len() {
10433 ext_cols.push(ColumnSchema::new(
10434 alloc::format!("__win_{i}"),
10435 DataType::Text, true,
10437 ));
10438 }
10439 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
10441 for i in 0..n_rows {
10442 let mut values = filtered[i].values.clone();
10443 for w in 0..window_nodes.len() {
10444 values.push(win_vals[w][i].clone());
10445 }
10446 ext_rows.push(Row::new(values));
10447 }
10448 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
10450 for item in &stmt.items {
10451 let new_item = match item {
10452 SelectItem::Wildcard => SelectItem::Wildcard,
10453 SelectItem::Expr { expr, alias } => {
10454 let mut e = expr.clone();
10455 rewrite_window_to_columns(&mut e, &window_nodes);
10456 SelectItem::Expr {
10457 expr: e,
10458 alias: alias.clone(),
10459 }
10460 }
10461 };
10462 rewritten_items.push(new_item);
10463 }
10464
10465 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
10471 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
10472 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
10473 for (i, row) in ext_rows.iter().enumerate() {
10474 if i.is_multiple_of(256) {
10475 cancel.check()?;
10476 }
10477 let mut values = Vec::with_capacity(projection.len());
10478 for p in &projection {
10479 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
10480 }
10481 let order_keys = if stmt.order_by.is_empty() {
10482 Vec::new()
10483 } else {
10484 let mut keys = Vec::with_capacity(stmt.order_by.len());
10485 for o in &stmt.order_by {
10486 let mut e = o.expr.clone();
10487 rewrite_window_to_columns(&mut e, &window_nodes);
10488 let key = eval::eval_expr(&e, row, &ext_ctx)?;
10489 keys.push(value_to_order_key(&key)?);
10490 }
10491 keys
10492 };
10493 tagged.push((order_keys, Row::new(values)));
10494 }
10495 if !stmt.order_by.is_empty() {
10497 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
10498 sort_by_keys(&mut tagged, &descs);
10499 }
10500 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
10501 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
10502 let final_cols: Vec<ColumnSchema> = projection
10503 .into_iter()
10504 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
10505 .collect();
10506 Ok(QueryResult::Rows {
10507 columns: final_cols,
10508 rows: out_rows,
10509 })
10510 }
10511
10512 fn exec_select_with_meta_views(
10529 &self,
10530 stmt: &SelectStatement,
10531 cancel: CancelToken<'_>,
10532 ) -> Result<QueryResult, EngineError> {
10533 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
10534 collect_meta_view_names(stmt, &mut needed);
10535 let mut catalog = self.active_catalog().clone();
10536 for view in &needed {
10537 if catalog.get(view).is_some() {
10538 continue;
10539 }
10540 match view.as_str() {
10541 "__spg_info_columns" => {
10542 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
10543 materialise_meta_view(&mut catalog, view, schema, rows)?;
10544 }
10545 "__spg_info_tables" => {
10546 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
10547 materialise_meta_view(&mut catalog, view, schema, rows)?;
10548 }
10549 "__spg_pg_class" => {
10550 let (schema, rows) = synth_pg_class(self.active_catalog());
10551 materialise_meta_view(&mut catalog, view, schema, rows)?;
10552 }
10553 "__spg_pg_attribute" => {
10554 let (schema, rows) = synth_pg_attribute(self.active_catalog());
10555 materialise_meta_view(&mut catalog, view, schema, rows)?;
10556 }
10557 "__spg_pg_type" => {
10560 let (schema, rows) = synth_pg_type(self.active_catalog());
10561 materialise_meta_view(&mut catalog, view, schema, rows)?;
10562 }
10563 "__spg_pg_proc" => {
10566 let (schema, rows) = synth_pg_proc(self.active_catalog());
10567 materialise_meta_view(&mut catalog, view, schema, rows)?;
10568 }
10569 "__spg_pg_trigger" => {
10576 let (schema, rows) = synth_pg_trigger(self.active_catalog());
10577 materialise_meta_view(&mut catalog, view, schema, rows)?;
10578 }
10579 "__spg_pg_namespace" => {
10582 let (schema, rows) = synth_pg_namespace(self.active_catalog());
10583 materialise_meta_view(&mut catalog, view, schema, rows)?;
10584 }
10585 "__spg_pg_indexes" => {
10588 let (schema, rows) = synth_pg_indexes(self.active_catalog());
10589 materialise_meta_view(&mut catalog, view, schema, rows)?;
10590 }
10591 "__spg_pg_index" => {
10594 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
10595 materialise_meta_view(&mut catalog, view, schema, rows)?;
10596 }
10597 "__spg_pg_constraint" => {
10600 let (schema, rows) = synth_pg_constraint(self.active_catalog());
10601 materialise_meta_view(&mut catalog, view, schema, rows)?;
10602 }
10603 "__spg_pg_database" => {
10608 let (schema, rows) = synth_pg_database(self.active_catalog());
10609 materialise_meta_view(&mut catalog, view, schema, rows)?;
10610 }
10611 "__spg_pg_roles" | "__spg_pg_user" => {
10612 let (schema, rows) = synth_pg_roles(self);
10613 materialise_meta_view(&mut catalog, view, schema, rows)?;
10614 }
10615 "__spg_pg_views" => {
10619 let (schema, rows) = synth_pg_views(self.active_catalog());
10620 materialise_meta_view(&mut catalog, view, schema, rows)?;
10621 }
10622 "__spg_pg_matviews" => {
10626 let (schema, _) = synth_pg_views(self.active_catalog());
10627 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
10628 }
10629 "__spg_pg_extension" => {
10632 let (schema, rows) = synth_pg_extension();
10633 materialise_meta_view(&mut catalog, view, schema, rows)?;
10634 }
10635 "__spg_pg_settings" => {
10637 let (schema, rows) = synth_pg_settings(self);
10638 materialise_meta_view(&mut catalog, view, schema, rows)?;
10639 }
10640 "__spg_info_key_column_usage" => {
10642 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
10643 materialise_meta_view(&mut catalog, view, schema, rows)?;
10644 }
10645 "__spg_info_referential_constraints" => {
10647 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
10648 materialise_meta_view(&mut catalog, view, schema, rows)?;
10649 }
10650 "__spg_info_statistics" => {
10652 let (schema, rows) = synth_info_statistics(self.active_catalog());
10653 materialise_meta_view(&mut catalog, view, schema, rows)?;
10654 }
10655 "__spg_info_routines" => {
10657 let (schema, rows) = synth_info_routines();
10658 materialise_meta_view(&mut catalog, view, schema, rows)?;
10659 }
10660 "__spg_mysql_user" => {
10662 let (schema, rows) = synth_mysql_user(self);
10663 materialise_meta_view(&mut catalog, view, schema, rows)?;
10664 }
10665 "__spg_mysql_db" => {
10666 let (schema, rows) = synth_mysql_db();
10667 materialise_meta_view(&mut catalog, view, schema, rows)?;
10668 }
10669 _ => {
10670 return Err(EngineError::Unsupported(alloc::format!(
10671 "meta view {view:?} is not yet materialisable; \
10672 v7.16.2 covers information_schema.columns / .tables \
10673 and pg_catalog.pg_class / pg_attribute; \
10674 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
10675 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
10676 pg_user / pg_views / pg_matviews / pg_settings"
10677 )));
10678 }
10679 }
10680 }
10681 let mut temp = Engine::restore(catalog);
10682 if let Some(c) = self.clock {
10683 temp = temp.with_clock(c);
10684 }
10685 if let Some(f) = self.salt_fn {
10686 temp = temp.with_salt_fn(f);
10687 }
10688 temp.meta_views_materialised = true;
10689 temp.exec_select_cancel(stmt, cancel)
10690 }
10691
10692 fn exec_with_ctes(
10693 &self,
10694 stmt: &SelectStatement,
10695 cancel: CancelToken<'_>,
10696 ) -> Result<QueryResult, EngineError> {
10697 cancel.check()?;
10698 let mut catalog = self.active_catalog().clone();
10699 for cte in &stmt.ctes {
10700 if catalog.get(&cte.name).is_some() {
10701 return Err(EngineError::Unsupported(alloc::format!(
10702 "CTE name {:?} shadows an existing table; rename the CTE",
10703 cte.name
10704 )));
10705 }
10706 let (columns, rows) = if cte.recursive {
10707 self.materialise_recursive_cte(cte, &catalog, cancel)?
10708 } else {
10709 let mut cte_engine = Engine::restore(catalog.clone());
10715 if let Some(c) = self.clock {
10716 cte_engine = cte_engine.with_clock(c);
10717 }
10718 if let Some(f) = self.salt_fn {
10719 cte_engine = cte_engine.with_salt_fn(f);
10720 }
10721 let body_result = cte_engine.exec_select_cancel(&cte.body, cancel)?;
10722 let QueryResult::Rows { columns, rows } = body_result else {
10723 return Err(EngineError::Unsupported(alloc::format!(
10724 "CTE {:?} body did not return rows",
10725 cte.name
10726 )));
10727 };
10728 (columns, rows)
10729 };
10730 let inferred = infer_column_types(&columns, &rows);
10735 let mut columns = inferred;
10736 if !cte.column_overrides.is_empty() {
10738 if cte.column_overrides.len() != columns.len() {
10739 return Err(EngineError::Unsupported(alloc::format!(
10740 "CTE {:?} column list has {} names but body returns {} columns",
10741 cte.name,
10742 cte.column_overrides.len(),
10743 columns.len()
10744 )));
10745 }
10746 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10747 col.name.clone_from(name);
10748 }
10749 }
10750 let schema = TableSchema::new(cte.name.clone(), columns);
10751 catalog.create_table(schema).map_err(EngineError::Storage)?;
10752 let table = catalog
10753 .get_mut(&cte.name)
10754 .expect("just-created CTE table must exist");
10755 for row in rows {
10756 table.insert(row).map_err(EngineError::Storage)?;
10757 }
10758 }
10759 let mut body = stmt.clone();
10762 body.ctes = Vec::new();
10763 let mut temp = Engine::restore(catalog);
10764 if let Some(c) = self.clock {
10765 temp = temp.with_clock(c);
10766 }
10767 if let Some(f) = self.salt_fn {
10768 temp = temp.with_salt_fn(f);
10769 }
10770 temp.exec_select_cancel(&body, cancel)
10771 }
10772
10773 #[allow(clippy::too_many_lines)]
10783 fn materialise_recursive_cte(
10784 &self,
10785 cte: &spg_sql::ast::Cte,
10786 base_catalog: &Catalog,
10787 cancel: CancelToken<'_>,
10788 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10789 const MAX_TOTAL_ROWS: usize = 1_000_000;
10790 const MAX_ITERATIONS: usize = 100_000;
10791 cancel.check()?;
10792 if cte.body.unions.is_empty() {
10793 return Err(EngineError::Unsupported(alloc::format!(
10794 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10795 cte.name
10796 )));
10797 }
10798 let mut anchor = cte.body.clone();
10800 let union_terms = core::mem::take(&mut anchor.unions);
10801 anchor.ctes = Vec::new();
10802 if select_refers_to(&anchor, &cte.name) {
10804 return Err(EngineError::Unsupported(alloc::format!(
10805 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10806 cte.name
10807 )));
10808 }
10809 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10810 let QueryResult::Rows {
10811 columns: anchor_cols,
10812 rows: anchor_rows,
10813 } = anchor_result
10814 else {
10815 return Err(EngineError::Unsupported(alloc::format!(
10816 "WITH RECURSIVE {:?}: anchor did not return rows",
10817 cte.name
10818 )));
10819 };
10820 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10824 if !cte.column_overrides.is_empty() {
10825 if cte.column_overrides.len() != columns.len() {
10826 return Err(EngineError::Unsupported(alloc::format!(
10827 "CTE {:?} column list has {} names but anchor returns {} columns",
10828 cte.name,
10829 cte.column_overrides.len(),
10830 columns.len()
10831 )));
10832 }
10833 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10834 col.name.clone_from(name);
10835 }
10836 }
10837 let mut all_rows: Vec<Row> = anchor_rows.clone();
10838 let mut working_set: Vec<Row> = anchor_rows;
10839 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10840 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10843 if !all_union_all {
10844 for r in &all_rows {
10845 seen.insert(encode_row_key(r));
10846 }
10847 }
10848 for iter in 0..MAX_ITERATIONS {
10849 cancel.check()?;
10850 if working_set.is_empty() {
10851 break;
10852 }
10853 let mut iter_catalog = base_catalog.clone();
10855 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10856 iter_catalog
10857 .create_table(schema)
10858 .map_err(EngineError::Storage)?;
10859 {
10860 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10861 for row in &working_set {
10862 table.insert(row.clone()).map_err(EngineError::Storage)?;
10863 }
10864 }
10865 let mut iter_engine = Engine::restore(iter_catalog);
10866 if let Some(c) = self.clock {
10867 iter_engine = iter_engine.with_clock(c);
10868 }
10869 if let Some(f) = self.salt_fn {
10870 iter_engine = iter_engine.with_salt_fn(f);
10871 }
10872 let mut next_set: Vec<Row> = Vec::new();
10874 for (_, term) in &union_terms {
10875 let mut term = term.clone();
10876 term.ctes = Vec::new();
10877 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10878 let QueryResult::Rows {
10879 columns: rc,
10880 rows: rs,
10881 } = r
10882 else {
10883 return Err(EngineError::Unsupported(alloc::format!(
10884 "WITH RECURSIVE {:?}: recursive term did not return rows",
10885 cte.name
10886 )));
10887 };
10888 if rc.len() != columns.len() {
10889 return Err(EngineError::Unsupported(alloc::format!(
10890 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10891 cte.name,
10892 rc.len(),
10893 columns.len()
10894 )));
10895 }
10896 for row in rs {
10897 if !all_union_all {
10898 let key = encode_row_key(&row);
10899 if !seen.insert(key) {
10900 continue;
10901 }
10902 }
10903 next_set.push(row);
10904 }
10905 }
10906 if next_set.is_empty() {
10907 break;
10908 }
10909 all_rows.extend(next_set.iter().cloned());
10910 working_set = next_set;
10911 if all_rows.len() > MAX_TOTAL_ROWS {
10912 return Err(EngineError::Unsupported(alloc::format!(
10913 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10914 cte.name
10915 )));
10916 }
10917 if iter + 1 == MAX_ITERATIONS {
10918 return Err(EngineError::Unsupported(alloc::format!(
10919 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10920 cte.name
10921 )));
10922 }
10923 }
10924 Ok((columns, all_rows))
10925 }
10926
10927 fn resolve_select_subqueries(
10928 &self,
10929 stmt: &mut SelectStatement,
10930 cancel: CancelToken<'_>,
10931 ) -> Result<(), EngineError> {
10932 for item in &mut stmt.items {
10933 if let SelectItem::Expr { expr, .. } = item {
10934 self.resolve_expr_subqueries(expr, cancel)?;
10935 }
10936 }
10937 if let Some(w) = &mut stmt.where_ {
10938 self.resolve_expr_subqueries(w, cancel)?;
10939 }
10940 if let Some(from) = &mut stmt.from {
10944 for j in &mut from.joins {
10945 if let Some(on) = &mut j.on {
10946 self.resolve_expr_subqueries(on, cancel)?;
10947 }
10948 }
10949 }
10950 if let Some(gs) = &mut stmt.group_by {
10951 for g in gs {
10952 self.resolve_expr_subqueries(g, cancel)?;
10953 }
10954 }
10955 if let Some(h) = &mut stmt.having {
10956 self.resolve_expr_subqueries(h, cancel)?;
10957 }
10958 for o in &mut stmt.order_by {
10959 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10960 }
10961 for (_, peer) in &mut stmt.unions {
10962 self.resolve_select_subqueries(peer, cancel)?;
10963 }
10964 Ok(())
10965 }
10966
10967 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10969 &self,
10970 e: &mut Expr,
10971 cancel: CancelToken<'_>,
10972 ) -> Result<(), EngineError> {
10973 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10975 *e = replacement;
10976 return Ok(());
10977 }
10978 match e {
10979 Expr::AggregateOrdered { call, order_by, .. } => {
10980 self.resolve_expr_subqueries(call, cancel)?;
10981 for o in order_by.iter_mut() {
10982 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10983 }
10984 }
10985 Expr::Binary { lhs, rhs, .. } => {
10986 self.resolve_expr_subqueries(lhs, cancel)?;
10987 self.resolve_expr_subqueries(rhs, cancel)?;
10988 }
10989 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10990 self.resolve_expr_subqueries(expr, cancel)?;
10991 }
10992 Expr::FunctionCall { args, .. } => {
10993 for a in args {
10994 self.resolve_expr_subqueries(a, cancel)?;
10995 }
10996 }
10997 Expr::Like { expr, pattern, .. } => {
10998 self.resolve_expr_subqueries(expr, cancel)?;
10999 self.resolve_expr_subqueries(pattern, cancel)?;
11000 }
11001 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
11002 Expr::WindowFunction {
11005 args,
11006 partition_by,
11007 order_by,
11008 ..
11009 } => {
11010 for a in args {
11011 self.resolve_expr_subqueries(a, cancel)?;
11012 }
11013 for p in partition_by {
11014 self.resolve_expr_subqueries(p, cancel)?;
11015 }
11016 for (e, _, _) in order_by {
11017 self.resolve_expr_subqueries(e, cancel)?;
11018 }
11019 }
11020 Expr::ScalarSubquery(_)
11024 | Expr::Exists { .. }
11025 | Expr::InSubquery { .. }
11026 | Expr::Literal(_)
11027 | Expr::Placeholder(_)
11028 | Expr::Column(_) => {}
11029 Expr::Array(items) => {
11031 for elem in items {
11032 self.resolve_expr_subqueries(elem, cancel)?;
11033 }
11034 }
11035 Expr::ArraySubscript { target, index } => {
11036 self.resolve_expr_subqueries(target, cancel)?;
11037 self.resolve_expr_subqueries(index, cancel)?;
11038 }
11039 Expr::AnyAll { expr, array, .. } => {
11040 self.resolve_expr_subqueries(expr, cancel)?;
11041 self.resolve_expr_subqueries(array, cancel)?;
11042 }
11043 Expr::Case {
11044 operand,
11045 branches,
11046 else_branch,
11047 } => {
11048 if let Some(o) = operand {
11049 self.resolve_expr_subqueries(o, cancel)?;
11050 }
11051 for (w, t) in branches {
11052 self.resolve_expr_subqueries(w, cancel)?;
11053 self.resolve_expr_subqueries(t, cancel)?;
11054 }
11055 if let Some(e) = else_branch {
11056 self.resolve_expr_subqueries(e, cancel)?;
11057 }
11058 }
11059 }
11060 Ok(())
11061 }
11062
11063 fn eval_expr_with_correlated(
11071 &self,
11072 expr: &Expr,
11073 row: &Row,
11074 ctx: &EvalContext<'_>,
11075 cancel: CancelToken<'_>,
11076 memo: Option<&mut memoize::MemoizeCache>,
11077 ) -> Result<Value, EngineError> {
11078 if !expr_has_subquery(expr) {
11079 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
11080 }
11081 let mut e = expr.clone();
11082 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
11083 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
11084 }
11085
11086 fn resolve_correlated_in_expr(
11087 &self,
11088 e: &mut Expr,
11089 row: &Row,
11090 ctx: &EvalContext<'_>,
11091 cancel: CancelToken<'_>,
11092 mut memo: Option<&mut memoize::MemoizeCache>,
11093 ) -> Result<(), EngineError> {
11094 match e {
11095 Expr::AggregateOrdered { call, order_by, .. } => {
11096 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
11097 for o in order_by.iter_mut() {
11098 self.resolve_correlated_in_expr(
11099 &mut o.expr,
11100 row,
11101 ctx,
11102 cancel,
11103 memo.as_deref_mut(),
11104 )?;
11105 }
11106 }
11107 Expr::ScalarSubquery(inner) => {
11108 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
11113 subquery_repr: alloc::format!("{}", **inner),
11114 outer_values: row.values.clone(),
11115 });
11116 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
11117 && let Some(cached) = cache.get(k)
11118 {
11119 *e = value_to_literal_expr(cached)?;
11120 return Ok(());
11121 }
11122 let mut s = (**inner).clone();
11123 substitute_outer_columns(&mut s, row, ctx);
11124 let r = self.exec_select_cancel(&s, cancel)?;
11125 let QueryResult::Rows { rows, .. } = r else {
11126 return Err(EngineError::Unsupported(
11127 "scalar subquery: inner did not return rows".into(),
11128 ));
11129 };
11130 let value = match rows.as_slice() {
11131 [] => Value::Null,
11132 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
11133 _ => {
11134 return Err(EngineError::Unsupported(alloc::format!(
11135 "scalar subquery returned {} rows; expected 0 or 1",
11136 rows.len()
11137 )));
11138 }
11139 };
11140 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
11141 cache.insert(k, value.clone());
11142 }
11143 *e = value_to_literal_expr(value)?;
11144 }
11145 Expr::Exists { subquery, negated } => {
11146 let mut s = (**subquery).clone();
11147 substitute_outer_columns(&mut s, row, ctx);
11148 let r = self.exec_select_cancel(&s, cancel)?;
11149 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
11150 let bit = if *negated { !exists } else { exists };
11151 *e = Expr::Literal(Literal::Bool(bit));
11152 }
11153 Expr::InSubquery {
11154 expr: lhs,
11155 subquery,
11156 negated,
11157 } => {
11158 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11159 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
11160 let mut s = (**subquery).clone();
11161 substitute_outer_columns(&mut s, row, ctx);
11162 let r = self.exec_select_cancel(&s, cancel)?;
11163 let QueryResult::Rows { columns, rows, .. } = r else {
11164 return Err(EngineError::Unsupported(
11165 "IN-subquery: inner did not return rows".into(),
11166 ));
11167 };
11168 if columns.len() != 1 {
11169 return Err(EngineError::Unsupported(alloc::format!(
11170 "IN-subquery must project exactly one column; got {}",
11171 columns.len()
11172 )));
11173 }
11174 let mut found = false;
11175 let mut any_null = false;
11176 for r0 in rows {
11177 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
11178 if v.is_null() {
11179 any_null = true;
11180 continue;
11181 }
11182 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
11183 found = true;
11184 break;
11185 }
11186 }
11187 let bit = if found {
11188 !*negated
11189 } else if any_null {
11190 return Err(EngineError::Unsupported(
11191 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
11192 ));
11193 } else {
11194 *negated
11195 };
11196 *e = Expr::Literal(Literal::Bool(bit));
11197 }
11198 Expr::Binary { lhs, rhs, .. } => {
11199 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
11200 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
11201 }
11202 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11203 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11204 }
11205 Expr::Like { expr, pattern, .. } => {
11206 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11207 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
11208 }
11209 Expr::FunctionCall { args, .. } => {
11210 for a in args {
11211 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
11212 }
11213 }
11214 Expr::Extract { source, .. } => {
11215 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
11216 }
11217 Expr::WindowFunction { .. }
11218 | Expr::Literal(_)
11219 | Expr::Placeholder(_)
11220 | Expr::Column(_) => {}
11221 Expr::Array(items) => {
11223 for elem in items {
11224 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
11225 }
11226 }
11227 Expr::ArraySubscript { target, index } => {
11228 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
11229 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
11230 }
11231 Expr::AnyAll { expr, array, .. } => {
11232 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
11233 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
11234 }
11235 Expr::Case {
11236 operand,
11237 branches,
11238 else_branch,
11239 } => {
11240 if let Some(o) = operand {
11241 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
11242 }
11243 for (w, t) in branches {
11244 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
11245 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
11246 }
11247 if let Some(e) = else_branch {
11248 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
11249 }
11250 }
11251 }
11252 Ok(())
11253 }
11254
11255 fn subquery_replacement(
11256 &self,
11257 e: &Expr,
11258 cancel: CancelToken<'_>,
11259 ) -> Result<Option<Expr>, EngineError> {
11260 match e {
11261 Expr::ScalarSubquery(inner) => {
11262 let mut s = (**inner).clone();
11263 self.resolve_select_subqueries(&mut s, cancel)?;
11266 let r = match self.exec_bare_select_cancel(&s, cancel) {
11267 Ok(r) => r,
11268 Err(e) if is_correlation_error(&e) => return Ok(None),
11269 Err(e) => return Err(e),
11270 };
11271 let QueryResult::Rows { rows, .. } = r else {
11272 return Err(EngineError::Unsupported(
11273 "scalar subquery: inner statement did not return rows".into(),
11274 ));
11275 };
11276 let value = match rows.as_slice() {
11277 [] => Value::Null,
11278 [row] => row.values.first().cloned().unwrap_or(Value::Null),
11279 _ => {
11280 return Err(EngineError::Unsupported(alloc::format!(
11281 "scalar subquery returned {} rows; expected 0 or 1",
11282 rows.len()
11283 )));
11284 }
11285 };
11286 Ok(Some(value_to_literal_expr(value)?))
11287 }
11288 Expr::Exists { subquery, negated } => {
11289 let mut s = (**subquery).clone();
11290 self.resolve_select_subqueries(&mut s, cancel)?;
11291 let r = match self.exec_bare_select_cancel(&s, cancel) {
11292 Ok(r) => r,
11293 Err(e) if is_correlation_error(&e) => return Ok(None),
11294 Err(e) => return Err(e),
11295 };
11296 let exists = match r {
11297 QueryResult::Rows { rows, .. } => !rows.is_empty(),
11298 QueryResult::CommandOk { .. } => false,
11299 };
11300 let bit = if *negated { !exists } else { exists };
11301 Ok(Some(Expr::Literal(Literal::Bool(bit))))
11302 }
11303 Expr::InSubquery {
11304 expr,
11305 subquery,
11306 negated,
11307 } => {
11308 let mut s = (**subquery).clone();
11309 self.resolve_select_subqueries(&mut s, cancel)?;
11310 let r = match self.exec_bare_select_cancel(&s, cancel) {
11311 Ok(r) => r,
11312 Err(e) if is_correlation_error(&e) => return Ok(None),
11313 Err(e) => return Err(e),
11314 };
11315 let QueryResult::Rows { columns, rows, .. } = r else {
11316 return Err(EngineError::Unsupported(
11317 "IN-subquery: inner statement did not return rows".into(),
11318 ));
11319 };
11320 if columns.len() != 1 {
11321 return Err(EngineError::Unsupported(alloc::format!(
11322 "IN-subquery must project exactly one column; got {}",
11323 columns.len()
11324 )));
11325 }
11326 let mut acc: Option<Expr> = None;
11329 for row in rows {
11330 let v = row.values.into_iter().next().unwrap_or(Value::Null);
11331 let lit = value_to_literal_expr(v)?;
11332 let cmp = Expr::Binary {
11333 lhs: expr.clone(),
11334 op: BinOp::Eq,
11335 rhs: Box::new(lit),
11336 };
11337 acc = Some(match acc {
11338 None => cmp,
11339 Some(prev) => Expr::Binary {
11340 lhs: Box::new(prev),
11341 op: BinOp::Or,
11342 rhs: Box::new(cmp),
11343 },
11344 });
11345 }
11346 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
11347 let final_expr = if *negated {
11348 Expr::Unary {
11349 op: UnOp::Not,
11350 expr: Box::new(combined),
11351 }
11352 } else {
11353 combined
11354 };
11355 Ok(Some(final_expr))
11356 }
11357 _ => Ok(None),
11358 }
11359 }
11360}
11361
11362fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
11374 if let Some(from) = &stmt.from
11375 && from_refers_to(from, target)
11376 {
11377 return true;
11378 }
11379 for (_, peer) in &stmt.unions {
11380 if select_refers_to(peer, target) {
11381 return true;
11382 }
11383 }
11384 for item in &stmt.items {
11385 if let SelectItem::Expr { expr, .. } = item
11386 && expr_refers_to(expr, target)
11387 {
11388 return true;
11389 }
11390 }
11391 if let Some(w) = &stmt.where_
11392 && expr_refers_to(w, target)
11393 {
11394 return true;
11395 }
11396 false
11397}
11398
11399fn from_refers_to(from: &FromClause, target: &str) -> bool {
11400 if from.primary.name.eq_ignore_ascii_case(target) {
11401 return true;
11402 }
11403 from.joins
11404 .iter()
11405 .any(|j| j.table.name.eq_ignore_ascii_case(target))
11406}
11407
11408fn collect_qualified_refs(
11413 stmt: &SelectStatement,
11414 out: &mut alloc::collections::BTreeSet<(String, String)>,
11415) -> Option<()> {
11416 for item in &stmt.items {
11417 match item {
11418 SelectItem::Wildcard => return None,
11419 SelectItem::Expr { expr, .. } => collect_qualified_refs_expr(expr, out)?,
11420 }
11421 }
11422 if let Some(w) = &stmt.where_ {
11423 collect_qualified_refs_expr(w, out)?;
11424 }
11425 if let Some(from) = &stmt.from {
11426 for j in &from.joins {
11427 if let Some(on) = &j.on {
11428 collect_qualified_refs_expr(on, out)?;
11429 }
11430 if j.table.lateral_subquery.is_some() {
11431 return None;
11432 }
11433 }
11434 }
11435 if let Some(gs) = &stmt.group_by {
11436 for g in gs {
11437 collect_qualified_refs_expr(g, out)?;
11438 }
11439 }
11440 if let Some(h) = &stmt.having {
11441 collect_qualified_refs_expr(h, out)?;
11442 }
11443 for o in &stmt.order_by {
11444 collect_qualified_refs_expr(&o.expr, out)?;
11445 }
11446 for (_, peer) in &stmt.unions {
11447 collect_qualified_refs(peer, out)?;
11448 }
11449 for cte in &stmt.ctes {
11450 collect_qualified_refs(&cte.body, out)?;
11451 }
11452 Some(())
11453}
11454
11455fn collect_qualified_refs_expr(
11456 e: &Expr,
11457 out: &mut alloc::collections::BTreeSet<(String, String)>,
11458) -> Option<()> {
11459 let mut cols: Vec<spg_sql::ast::ColumnName> = Vec::new();
11462 let mut subs: Vec<&SelectStatement> = Vec::new();
11463 visit_expr_columns_and_subqueries(
11464 e,
11465 &mut |c: &spg_sql::ast::ColumnName| cols.push(c.clone()),
11466 &mut |sub| subs.push(sub),
11467 );
11468 for c in cols {
11469 match c.qualifier {
11470 Some(q) => {
11471 out.insert((q, c.name));
11472 }
11473 None => return None,
11474 }
11475 }
11476 for sub in subs {
11477 collect_qualified_refs(sub, out)?;
11478 }
11479 Some(())
11480}
11481
11482fn visit_expr_columns_and_subqueries<'a>(
11485 e: &'a Expr,
11486 on_col: &mut impl FnMut(&'a spg_sql::ast::ColumnName),
11487 on_sub: &mut impl FnMut(&'a SelectStatement),
11488) {
11489 match e {
11490 Expr::Column(c) => on_col(c),
11491 Expr::ScalarSubquery(s) => on_sub(s),
11492 Expr::Exists { subquery, .. } => on_sub(subquery),
11493 Expr::InSubquery { expr, subquery, .. } => {
11494 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11495 on_sub(subquery);
11496 }
11497 Expr::Binary { lhs, rhs, .. } => {
11498 visit_expr_columns_and_subqueries(lhs, on_col, on_sub);
11499 visit_expr_columns_and_subqueries(rhs, on_col, on_sub);
11500 }
11501 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11502 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11503 }
11504 Expr::Like { expr, pattern, .. } => {
11505 visit_expr_columns_and_subqueries(expr, on_col, on_sub);
11506 visit_expr_columns_and_subqueries(pattern, on_col, on_sub);
11507 }
11508 Expr::FunctionCall { args, .. } => {
11509 for a in args {
11510 visit_expr_columns_and_subqueries(a, on_col, on_sub);
11511 }
11512 }
11513 Expr::AggregateOrdered { call, order_by, .. } => {
11514 visit_expr_columns_and_subqueries(call, on_col, on_sub);
11515 for o in order_by {
11516 visit_expr_columns_and_subqueries(&o.expr, on_col, on_sub);
11517 }
11518 }
11519 Expr::Case {
11520 operand,
11521 branches,
11522 else_branch,
11523 } => {
11524 if let Some(op) = operand {
11525 visit_expr_columns_and_subqueries(op, on_col, on_sub);
11526 }
11527 for (w, t) in branches {
11528 visit_expr_columns_and_subqueries(w, on_col, on_sub);
11529 visit_expr_columns_and_subqueries(t, on_col, on_sub);
11530 }
11531 if let Some(eb) = else_branch {
11532 visit_expr_columns_and_subqueries(eb, on_col, on_sub);
11533 }
11534 }
11535 Expr::ArraySubscript { target, index } => {
11536 visit_expr_columns_and_subqueries(target, on_col, on_sub);
11537 visit_expr_columns_and_subqueries(index, on_col, on_sub);
11538 }
11539 Expr::Literal(_) | Expr::Placeholder(_) => {}
11540 _ => {
11545 static BAIL: spg_sql::ast::ColumnName = spg_sql::ast::ColumnName {
11548 qualifier: None,
11549 name: String::new(),
11550 };
11551 on_col(&BAIL);
11552 }
11553 }
11554}
11555
11556fn collect_column_qualifiers<'e>(e: &'e Expr, out: &mut Vec<&'e str>, all_qualified: &mut bool) {
11560 if let Expr::Column(c) = e {
11561 match &c.qualifier {
11562 Some(q) => out.push(q.as_str()),
11563 None => *all_qualified = false,
11564 }
11565 return;
11566 }
11567 match e {
11570 Expr::Binary { lhs, rhs, .. } => {
11571 collect_column_qualifiers(lhs, out, all_qualified);
11572 collect_column_qualifiers(rhs, out, all_qualified);
11573 }
11574 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11575 collect_column_qualifiers(expr, out, all_qualified);
11576 }
11577 Expr::Like { expr, pattern, .. } => {
11578 collect_column_qualifiers(expr, out, all_qualified);
11579 collect_column_qualifiers(pattern, out, all_qualified);
11580 }
11581 Expr::FunctionCall { args, .. } => {
11582 for a in args {
11583 collect_column_qualifiers(a, out, all_qualified);
11584 }
11585 }
11586 Expr::Literal(_) | Expr::Placeholder(_) => {}
11587 _ => *all_qualified = false,
11590 }
11591}
11592
11593fn expr_refers_to(e: &Expr, target: &str) -> bool {
11594 match e {
11595 Expr::AggregateOrdered { call, order_by, .. } => {
11596 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
11597 }
11598 Expr::ScalarSubquery(s) => select_refers_to(s, target),
11599 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
11600 select_refers_to(subquery, target)
11601 }
11602 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
11603 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
11604 expr_refers_to(expr, target)
11605 }
11606 Expr::Like { expr, pattern, .. } => {
11607 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
11608 }
11609 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
11610 Expr::Extract { source, .. } => expr_refers_to(source, target),
11611 Expr::WindowFunction {
11612 args,
11613 partition_by,
11614 order_by,
11615 ..
11616 } => {
11617 args.iter().any(|a| expr_refers_to(a, target))
11618 || partition_by.iter().any(|p| expr_refers_to(p, target))
11619 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
11620 }
11621 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
11622 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
11623 Expr::ArraySubscript { target: t, index } => {
11624 expr_refers_to(t, target) || expr_refers_to(index, target)
11625 }
11626 Expr::AnyAll { expr, array, .. } => {
11627 expr_refers_to(expr, target) || expr_refers_to(array, target)
11628 }
11629 Expr::Case {
11630 operand,
11631 branches,
11632 else_branch,
11633 } => {
11634 operand
11635 .as_deref()
11636 .is_some_and(|o| expr_refers_to(o, target))
11637 || branches
11638 .iter()
11639 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
11640 || else_branch
11641 .as_deref()
11642 .is_some_and(|e| expr_refers_to(e, target))
11643 }
11644 }
11645}
11646
11647fn pg_data_type_text(ty: DataType) -> alloc::string::String {
11658 let s = match ty {
11659 DataType::Int => "integer",
11660 DataType::BigInt => "bigint",
11661 DataType::SmallInt => "smallint",
11662 DataType::Float => "double precision",
11663 DataType::Bool => "boolean",
11664 DataType::Text => "text",
11665 DataType::Varchar(_) => "character varying",
11666 DataType::Date => "date",
11667 DataType::Timestamp => "timestamp without time zone",
11668 DataType::Timestamptz => "timestamp with time zone",
11669 DataType::Json => "jsonb",
11670 DataType::Bytes => "bytea",
11671 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
11672 DataType::TsVector => "tsvector",
11673 DataType::TsQuery => "tsquery",
11674 DataType::Vector { .. } => "USER-DEFINED",
11675 _ => "USER-DEFINED",
11678 };
11679 alloc::string::String::from(s)
11680}
11681
11682fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11689 let schema = alloc::vec![
11690 ColumnSchema::new("table_catalog", DataType::Text, false),
11691 ColumnSchema::new("table_schema", DataType::Text, false),
11692 ColumnSchema::new("table_name", DataType::Text, false),
11693 ColumnSchema::new("column_name", DataType::Text, false),
11694 ColumnSchema::new("ordinal_position", DataType::Int, false),
11695 ColumnSchema::new("is_nullable", DataType::Text, false),
11696 ColumnSchema::new("data_type", DataType::Text, false),
11697 ];
11698 let mut rows: Vec<Row> = Vec::new();
11699 for tname in cat.table_names() {
11700 let Some(t) = cat.get(&tname) else { continue };
11701 for (i, col) in t.schema().columns.iter().enumerate() {
11702 #[allow(clippy::cast_possible_wrap)]
11703 let ordinal = (i + 1) as i32;
11704 rows.push(Row::new(alloc::vec![
11705 Value::Text("spg".into()),
11706 Value::Text("public".into()),
11707 Value::Text(tname.clone()),
11708 Value::Text(col.name.clone()),
11709 Value::Int(ordinal),
11710 Value::Text(if col.nullable {
11711 "YES".into()
11712 } else {
11713 "NO".into()
11714 }),
11715 Value::Text(pg_data_type_text(col.ty)),
11716 ]));
11717 }
11718 }
11719 (schema, rows)
11720}
11721
11722fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11724 let schema = alloc::vec![
11725 ColumnSchema::new("table_catalog", DataType::Text, false),
11726 ColumnSchema::new("table_schema", DataType::Text, false),
11727 ColumnSchema::new("table_name", DataType::Text, false),
11728 ColumnSchema::new("table_type", DataType::Text, false),
11729 ];
11730 let mut rows: Vec<Row> = Vec::new();
11731 for tname in cat.table_names() {
11732 rows.push(Row::new(alloc::vec![
11733 Value::Text("spg".into()),
11734 Value::Text("public".into()),
11735 Value::Text(tname.clone()),
11736 Value::Text("BASE TABLE".into()),
11737 ]));
11738 }
11739 (schema, rows)
11740}
11741
11742fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11746 let schema = alloc::vec![
11747 ColumnSchema::new("relname", DataType::Text, false),
11748 ColumnSchema::new("relkind", DataType::Text, false),
11749 ColumnSchema::new("relnamespace", DataType::BigInt, false),
11750 ];
11751 let mut rows: Vec<Row> = Vec::new();
11752 for tname in cat.table_names() {
11753 rows.push(Row::new(alloc::vec![
11754 Value::Text(tname.clone()),
11755 Value::Text("r".into()),
11756 Value::BigInt(2200), ]));
11758 }
11759 (schema, rows)
11760}
11761
11762fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11766 let schema = alloc::vec![
11767 ColumnSchema::new("attrelid", DataType::Text, false),
11768 ColumnSchema::new("attname", DataType::Text, false),
11769 ColumnSchema::new("attnum", DataType::Int, false),
11770 ColumnSchema::new("atttypid", DataType::Text, false),
11771 ColumnSchema::new("attnotnull", DataType::Bool, false),
11772 ];
11773 let mut rows: Vec<Row> = Vec::new();
11774 for tname in cat.table_names() {
11775 let Some(t) = cat.get(&tname) else { continue };
11776 for (i, col) in t.schema().columns.iter().enumerate() {
11777 #[allow(clippy::cast_possible_wrap)]
11778 let ordinal = (i + 1) as i32;
11779 rows.push(Row::new(alloc::vec![
11780 Value::Text(tname.clone()),
11781 Value::Text(col.name.clone()),
11782 Value::Int(ordinal),
11783 Value::Text(pg_data_type_text(col.ty)),
11784 Value::Bool(!col.nullable),
11785 ]));
11786 }
11787 }
11788 (schema, rows)
11789}
11790
11791fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11808 let schema = alloc::vec![
11809 ColumnSchema::new("oid", DataType::BigInt, false),
11810 ColumnSchema::new("typname", DataType::Text, false),
11811 ColumnSchema::new("typlen", DataType::SmallInt, false),
11812 ColumnSchema::new("typtype", DataType::Text, false),
11813 ColumnSchema::new("typcategory", DataType::Text, false),
11814 ColumnSchema::new("typelem", DataType::BigInt, false),
11815 ColumnSchema::new("typarray", DataType::BigInt, false),
11816 ColumnSchema::new("typnamespace", DataType::BigInt, false),
11817 ];
11818 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
11821 (16, "bool", 1, "b", "B", 0, 1000),
11823 (17, "bytea", -1, "b", "U", 0, 1001),
11824 (18, "char", 1, "b", "S", 0, 1002),
11825 (19, "name", 64, "b", "S", 0, 1003),
11826 (20, "int8", 8, "b", "N", 0, 1016),
11827 (21, "int2", 2, "b", "N", 0, 1005),
11828 (23, "int4", 4, "b", "N", 0, 1007),
11829 (24, "regproc", 4, "b", "N", 0, 1008),
11830 (25, "text", -1, "b", "S", 0, 1009),
11831 (26, "oid", 4, "b", "N", 0, 1028),
11832 (114, "json", -1, "b", "U", 0, 199),
11833 (142, "xml", -1, "b", "U", 0, 143),
11834 (700, "float4", 4, "b", "N", 0, 1021),
11835 (701, "float8", 8, "b", "N", 0, 1022),
11836 (650, "cidr", -1, "b", "I", 0, 651),
11837 (869, "inet", -1, "b", "I", 0, 1041),
11838 (829, "macaddr", 6, "b", "U", 0, 1040),
11839 (1042, "bpchar", -1, "b", "S", 0, 1014),
11840 (1043, "varchar", -1, "b", "S", 0, 1015),
11841 (1082, "date", 4, "b", "D", 0, 1182),
11842 (1083, "time", 8, "b", "D", 0, 1183),
11843 (1114, "timestamp", 8, "b", "D", 0, 1115),
11844 (1184, "timestamptz", 8, "b", "D", 0, 1185),
11845 (1186, "interval", 16, "b", "T", 0, 1187),
11846 (1266, "timetz", 12, "b", "D", 0, 1270),
11847 (1700, "numeric", -1, "b", "N", 0, 1231),
11848 (790, "money", 8, "b", "N", 0, 791),
11849 (2950, "uuid", 16, "b", "U", 0, 2951),
11850 (3802, "jsonb", -1, "b", "U", 0, 3807),
11851 (3614, "tsvector", -1, "b", "U", 0, 3643),
11852 (3615, "tsquery", -1, "b", "U", 0, 3645),
11853 (3908, "tstzrange", -1, "r", "R", 0, 3909),
11855 (3910, "tsrange", -1, "r", "R", 0, 3911),
11856 (3904, "int4range", -1, "r", "R", 0, 3905),
11857 (3926, "int8range", -1, "r", "R", 0, 3927),
11858 (3906, "numrange", -1, "r", "R", 0, 3907),
11859 (3912, "daterange", -1, "r", "R", 0, 3913),
11860 ];
11861 let arrays: &[(i64, &str, i64)] = &[
11864 (1000, "_bool", 16),
11865 (1001, "_bytea", 17),
11866 (1002, "_char", 18),
11867 (1003, "_name", 19),
11868 (1016, "_int8", 20),
11869 (1005, "_int2", 21),
11870 (1007, "_int4", 23),
11871 (1008, "_regproc", 24),
11872 (1009, "_text", 25),
11873 (1028, "_oid", 26),
11874 (199, "_json", 114),
11875 (143, "_xml", 142),
11876 (1021, "_float4", 700),
11877 (1022, "_float8", 701),
11878 (651, "_cidr", 650),
11879 (1041, "_inet", 869),
11880 (1040, "_macaddr", 829),
11881 (1014, "_bpchar", 1042),
11882 (1015, "_varchar", 1043),
11883 (1182, "_date", 1082),
11884 (1183, "_time", 1083),
11885 (1115, "_timestamp", 1114),
11886 (1185, "_timestamptz", 1184),
11887 (1187, "_interval", 1186),
11888 (1270, "_timetz", 1266),
11889 (1231, "_numeric", 1700),
11890 (791, "_money", 790),
11891 (2951, "_uuid", 2950),
11892 (3807, "_jsonb", 3802),
11893 (3643, "_tsvector", 3614),
11894 (3645, "_tsquery", 3615),
11895 ];
11896 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
11897 for &(oid, name, len, ty, cat, elem, arr) in scalars {
11898 rows.push(Row::new(alloc::vec![
11899 Value::BigInt(oid),
11900 Value::Text(name.into()),
11901 Value::SmallInt(len),
11902 Value::Text(ty.into()),
11903 Value::Text(cat.into()),
11904 Value::BigInt(elem),
11905 Value::BigInt(arr),
11906 Value::BigInt(2200),
11907 ]));
11908 }
11909 for &(oid, name, elem) in arrays {
11910 rows.push(Row::new(alloc::vec![
11911 Value::BigInt(oid),
11912 Value::Text(name.into()),
11913 Value::SmallInt(-1),
11914 Value::Text("b".into()),
11915 Value::Text("A".into()),
11916 Value::BigInt(elem),
11917 Value::BigInt(0),
11918 Value::BigInt(2200),
11919 ]));
11920 }
11921 (schema, rows)
11922}
11923
11924fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11943 let schema = alloc::vec![
11944 ColumnSchema::new("tgname", DataType::Text, false),
11945 ColumnSchema::new("relname", DataType::Text, false),
11946 ColumnSchema::new("tgenabled", DataType::Text, false),
11947 ColumnSchema::new("timing", DataType::Text, false),
11948 ColumnSchema::new("events", DataType::Text, false),
11949 ColumnSchema::new("function", DataType::Text, false),
11950 ];
11951 let rows: Vec<Row> = cat
11952 .triggers()
11953 .iter()
11954 .map(|t| {
11955 Row::new(alloc::vec![
11956 Value::Text(t.name.clone()),
11957 Value::Text(t.table.clone()),
11958 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
11959 Value::Text(t.timing.clone()),
11960 Value::Text(t.events.join(" OR ")),
11961 Value::Text(t.function.clone()),
11962 ])
11963 })
11964 .collect();
11965 (schema, rows)
11966}
11967
11968fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11969 let schema = alloc::vec![
11970 ColumnSchema::new("oid", DataType::BigInt, false),
11971 ColumnSchema::new("proname", DataType::Text, false),
11972 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11973 ColumnSchema::new("prokind", DataType::Text, false),
11974 ColumnSchema::new("pronargs", DataType::Int, false),
11975 ColumnSchema::new("prorettype", DataType::BigInt, false),
11976 ];
11977 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11980 (1318, "length", "f", 1, 23),
11982 (871, "upper", "f", 1, 25),
11983 (870, "lower", "f", 1, 25),
11984 (936, "substring", "f", 3, 25),
11985 (937, "substring", "f", 2, 25),
11986 (3055, "btrim", "f", 1, 25),
11987 (885, "btrim", "f", 2, 25),
11988 (3056, "ltrim", "f", 1, 25),
11989 (875, "ltrim", "f", 2, 25),
11990 (3057, "rtrim", "f", 1, 25),
11991 (876, "rtrim", "f", 2, 25),
11992 (1397, "abs", "f", 1, 23),
11993 (1396, "abs", "f", 1, 20),
11994 (1606, "round", "f", 1, 1700),
11995 (1707, "round", "f", 2, 1700),
11996 (2308, "ceil", "f", 1, 701),
11997 (2309, "ceiling", "f", 1, 701),
11998 (2310, "floor", "f", 1, 701),
11999 (1376, "sqrt", "f", 1, 701),
12000 (1369, "ln", "f", 1, 701),
12001 (1373, "exp", "f", 1, 701),
12002 (1368, "power", "f", 2, 701),
12003 (2228, "random", "f", 0, 701),
12004 (1299, "now", "f", 0, 1184),
12006 (1274, "current_timestamp", "f", 0, 1184),
12007 (1140, "current_date", "f", 0, 1082),
12008 (2050, "current_time", "f", 0, 1083),
12009 (1158, "date_trunc", "f", 2, 1184),
12010 (1171, "date_part", "f", 2, 701),
12011 (1172, "age", "f", 1, 1186),
12012 (936, "to_char", "f", 2, 25),
12013 (861, "current_database", "f", 0, 19),
12015 (745, "current_user", "f", 0, 19),
12016 (745, "session_user", "f", 0, 19),
12017 (1402, "current_schema", "f", 0, 19),
12018 (3058, "concat", "f", -1, 25),
12020 (3059, "concat_ws", "f", -1, 25),
12021 (3539, "format", "f", -1, 25),
12022 (2877, "pg_typeof", "f", 1, 2206),
12024 (3198, "json_build_object", "f", -1, 114),
12026 (3199, "jsonb_build_object", "f", -1, 3802),
12027 (3271, "json_build_array", "f", -1, 114),
12028 (3272, "jsonb_build_array", "f", -1, 3802),
12029 (3253, "gen_random_uuid", "f", 0, 2950),
12031 (3252, "uuid_generate_v4", "f", 0, 2950),
12032 (2147, "count", "a", 0, 20),
12034 (2803, "count", "a", -1, 20),
12035 (2116, "max", "a", 1, 23),
12036 (2132, "min", "a", 1, 23),
12037 (2108, "sum", "a", 1, 20),
12038 (2100, "avg", "a", 1, 1700),
12039 (2517, "string_agg", "a", 2, 25),
12040 (2747, "array_agg", "a", 1, 1009),
12041 (2517, "bool_and", "a", 1, 16),
12042 (2518, "bool_or", "a", 1, 16),
12043 (2519, "every", "a", 1, 16),
12044 (3100, "row_number", "w", 0, 20),
12046 (3101, "rank", "w", 0, 20),
12047 (3102, "dense_rank", "w", 0, 20),
12048 (3103, "percent_rank", "w", 0, 701),
12049 (3104, "cume_dist", "w", 0, 701),
12050 (3105, "lag", "w", -1, 2283),
12051 (3106, "lead", "w", -1, 2283),
12052 (3107, "first_value", "w", 1, 2283),
12053 (3108, "last_value", "w", 1, 2283),
12054 (3109, "nth_value", "w", 2, 2283),
12055 ];
12056 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
12057 for &(oid, name, kind, nargs, rettype) in funcs {
12058 rows.push(Row::new(alloc::vec![
12059 Value::BigInt(oid),
12060 Value::Text(name.into()),
12061 Value::BigInt(11),
12062 Value::Text(kind.into()),
12063 Value::Int(nargs),
12064 Value::BigInt(rettype),
12065 ]));
12066 }
12067 (schema, rows)
12068}
12069
12070fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12076 let schema = alloc::vec![
12077 ColumnSchema::new("user", DataType::Text, false),
12078 ColumnSchema::new("host", DataType::Text, false),
12079 ColumnSchema::new("select_priv", DataType::Text, false),
12080 ];
12081 let mut rows: Vec<Row> = Vec::new();
12082 rows.push(Row::new(alloc::vec![
12083 Value::Text("root".into()),
12084 Value::Text("localhost".into()),
12085 Value::Text("Y".into()),
12086 ]));
12087 for (name, _) in engine.users.iter() {
12088 if name != "root" {
12089 rows.push(Row::new(alloc::vec![
12090 Value::Text(name.to_string()),
12091 Value::Text("%".into()),
12092 Value::Text("Y".into()),
12093 ]));
12094 }
12095 }
12096 (schema, rows)
12097}
12098
12099fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
12104 let schema = alloc::vec![
12105 ColumnSchema::new("host", DataType::Text, false),
12106 ColumnSchema::new("db", DataType::Text, false),
12107 ColumnSchema::new("user", DataType::Text, false),
12108 ColumnSchema::new("select_priv", DataType::Text, false),
12109 ];
12110 let rows = alloc::vec![Row::new(alloc::vec![
12111 Value::Text("localhost".into()),
12112 Value::Text("postgres".into()),
12113 Value::Text("root".into()),
12114 Value::Text("Y".into()),
12115 ])];
12116 (schema, rows)
12117}
12118
12119fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12132 let schema = alloc::vec![
12133 ColumnSchema::new("constraint_name", DataType::Text, false),
12134 ColumnSchema::new("table_name", DataType::Text, false),
12135 ColumnSchema::new("column_name", DataType::Text, false),
12136 ColumnSchema::new("ordinal_position", DataType::Int, false),
12137 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12138 ColumnSchema::new("referenced_column_name", DataType::Text, false),
12139 ];
12140 let mut rows: Vec<Row> = Vec::new();
12141 for tname in cat.table_names() {
12142 let Some(t) = cat.get(&tname) else { continue };
12143 let cols = &t.schema().columns;
12144 let col_name_at = |pos: usize| -> String {
12145 cols.get(pos)
12146 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12147 };
12148 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12150 let conname = fk
12151 .name
12152 .clone()
12153 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12154 for (i, (&local, &parent)) in fk
12155 .local_columns
12156 .iter()
12157 .zip(fk.parent_columns.iter())
12158 .enumerate()
12159 {
12160 let parent_name = cat
12161 .get(&fk.parent_table)
12162 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
12163 .unwrap_or_else(|| alloc::format!("col{parent}"));
12164 #[allow(clippy::cast_possible_wrap)]
12165 let ordinal = (i + 1) as i32;
12166 rows.push(Row::new(alloc::vec![
12167 Value::Text(conname.clone()),
12168 Value::Text(tname.clone()),
12169 Value::Text(col_name_at(local)),
12170 Value::Int(ordinal),
12171 Value::Text(fk.parent_table.clone()),
12172 Value::Text(parent_name),
12173 ]));
12174 }
12175 }
12176 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12178 let conname = if uc.is_primary_key {
12179 alloc::format!("{}_pkey", tname)
12180 } else {
12181 alloc::format!("{}_uniq{ci}", tname)
12182 };
12183 for (i, &local) in uc.columns.iter().enumerate() {
12184 #[allow(clippy::cast_possible_wrap)]
12185 let ordinal = (i + 1) as i32;
12186 rows.push(Row::new(alloc::vec![
12187 Value::Text(conname.clone()),
12188 Value::Text(tname.clone()),
12189 Value::Text(col_name_at(local)),
12190 Value::Int(ordinal),
12191 Value::Text(String::new()),
12192 Value::Text(String::new()),
12193 ]));
12194 }
12195 }
12196 }
12197 (schema, rows)
12198}
12199
12200fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12203 let schema = alloc::vec![
12204 ColumnSchema::new("constraint_name", DataType::Text, false),
12205 ColumnSchema::new("table_name", DataType::Text, false),
12206 ColumnSchema::new("referenced_table_name", DataType::Text, false),
12207 ColumnSchema::new("update_rule", DataType::Text, false),
12208 ColumnSchema::new("delete_rule", DataType::Text, false),
12209 ];
12210 fn rule_name(a: spg_storage::FkAction) -> &'static str {
12211 match a {
12212 spg_storage::FkAction::Cascade => "CASCADE",
12213 spg_storage::FkAction::SetNull => "SET NULL",
12214 spg_storage::FkAction::SetDefault => "SET DEFAULT",
12215 spg_storage::FkAction::Restrict => "RESTRICT",
12216 spg_storage::FkAction::NoAction => "NO ACTION",
12217 }
12218 }
12219 let mut rows: Vec<Row> = Vec::new();
12220 for tname in cat.table_names() {
12221 let Some(t) = cat.get(&tname) else { continue };
12222 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12223 let conname = fk
12224 .name
12225 .clone()
12226 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12227 rows.push(Row::new(alloc::vec![
12228 Value::Text(conname),
12229 Value::Text(tname.clone()),
12230 Value::Text(fk.parent_table.clone()),
12231 Value::Text(rule_name(fk.on_update).into()),
12232 Value::Text(rule_name(fk.on_delete).into()),
12233 ]));
12234 }
12235 }
12236 (schema, rows)
12237}
12238
12239fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12243 let schema = alloc::vec![
12244 ColumnSchema::new("table_name", DataType::Text, false),
12245 ColumnSchema::new("index_name", DataType::Text, false),
12246 ColumnSchema::new("column_name", DataType::Text, false),
12247 ColumnSchema::new("seq_in_index", DataType::Int, false),
12248 ColumnSchema::new("non_unique", DataType::Int, false),
12249 ColumnSchema::new("index_type", DataType::Text, false),
12250 ];
12251 let mut rows: Vec<Row> = Vec::new();
12252 for tname in cat.table_names() {
12253 let Some(t) = cat.get(&tname) else { continue };
12254 for idx in t.indices() {
12255 let col = t
12256 .schema()
12257 .columns
12258 .get(idx.column_position)
12259 .map_or("?".into(), |c| c.name.clone());
12260 rows.push(Row::new(alloc::vec![
12261 Value::Text(tname.clone()),
12262 Value::Text(idx.name.clone()),
12263 Value::Text(col),
12264 Value::Int(1),
12265 Value::Int(i32::from(!idx.is_unique)),
12266 Value::Text("BTREE".into()),
12267 ]));
12268 }
12269 }
12270 (schema, rows)
12271}
12272
12273fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
12277 let schema = alloc::vec![
12278 ColumnSchema::new("routine_name", DataType::Text, false),
12279 ColumnSchema::new("routine_type", DataType::Text, false),
12280 ColumnSchema::new("data_type", DataType::Text, false),
12281 ];
12282 (schema, Vec::new())
12283}
12284
12285fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12300 let schema = alloc::vec![
12301 ColumnSchema::new("conname", DataType::Text, false),
12302 ColumnSchema::new("contype", DataType::Text, false),
12303 ColumnSchema::new("conrelid", DataType::Text, false),
12304 ColumnSchema::new("confrelid", DataType::Text, false),
12305 ColumnSchema::new("conkey", DataType::Text, false),
12306 ColumnSchema::new("confkey", DataType::Text, false),
12307 ];
12308 let mut rows: Vec<Row> = Vec::new();
12309 for tname in cat.table_names() {
12310 let Some(t) = cat.get(&tname) else { continue };
12311 let cols = &t.schema().columns;
12312 let col_name_at = |pos: usize| -> String {
12313 cols.get(pos)
12314 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
12315 };
12316 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
12318 let kind = if uc.is_primary_key { "p" } else { "u" };
12319 let conname = if uc.is_primary_key {
12320 alloc::format!("{}_pkey", tname)
12321 } else {
12322 alloc::format!("{}_uniq{ci}", tname)
12323 };
12324 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
12325 rows.push(Row::new(alloc::vec![
12326 Value::Text(conname),
12327 Value::Text(kind.into()),
12328 Value::Text(tname.clone()),
12329 Value::Text(String::new()),
12330 Value::Text(conkey.join(",")),
12331 Value::Text(String::new()),
12332 ]));
12333 }
12334 for idx in t.indices() {
12339 if !idx.is_unique {
12340 continue;
12341 }
12342 let is_primary = idx.name.ends_with("_pkey");
12343 let conname = idx.name.clone();
12344 let kind = if is_primary { "p" } else { "u" };
12345 let col_name = col_name_at(idx.column_position);
12346 let already = t
12349 .schema()
12350 .uniqueness_constraints
12351 .iter()
12352 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
12353 if already {
12354 continue;
12355 }
12356 rows.push(Row::new(alloc::vec![
12357 Value::Text(conname),
12358 Value::Text(kind.into()),
12359 Value::Text(tname.clone()),
12360 Value::Text(String::new()),
12361 Value::Text(col_name),
12362 Value::Text(String::new()),
12363 ]));
12364 }
12365 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
12367 let conname = fk
12368 .name
12369 .clone()
12370 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
12371 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
12372 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
12375 fk.parent_columns
12376 .iter()
12377 .map(|&p| {
12378 parent
12379 .schema()
12380 .columns
12381 .get(p)
12382 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
12383 })
12384 .collect()
12385 } else {
12386 fk.parent_columns
12387 .iter()
12388 .map(|p| alloc::format!("col{p}"))
12389 .collect()
12390 };
12391 rows.push(Row::new(alloc::vec![
12392 Value::Text(conname),
12393 Value::Text("f".into()),
12394 Value::Text(tname.clone()),
12395 Value::Text(fk.parent_table.clone()),
12396 Value::Text(conkey.join(",")),
12397 Value::Text(confkey.join(",")),
12398 ]));
12399 }
12400 }
12401 (schema, rows)
12402}
12403
12404fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12409 let schema = alloc::vec![
12410 ColumnSchema::new("oid", DataType::BigInt, false),
12411 ColumnSchema::new("datname", DataType::Text, false),
12412 ColumnSchema::new("datdba", DataType::BigInt, false),
12413 ColumnSchema::new("encoding", DataType::Int, false),
12414 ColumnSchema::new("datcollate", DataType::Text, false),
12415 ];
12416 let rows = alloc::vec![Row::new(alloc::vec![
12417 Value::BigInt(16384),
12418 Value::Text("postgres".into()),
12419 Value::BigInt(10),
12420 Value::Int(6), Value::Text("en_US.UTF-8".into()),
12422 ])];
12423 (schema, rows)
12424}
12425
12426fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12431 let schema = alloc::vec![
12432 ColumnSchema::new("oid", DataType::BigInt, false),
12433 ColumnSchema::new("rolname", DataType::Text, false),
12434 ColumnSchema::new("rolsuper", DataType::Bool, false),
12435 ColumnSchema::new("rolinherit", DataType::Bool, false),
12436 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
12437 ];
12438 let mut rows: Vec<Row> = Vec::new();
12439 let oid: i64 = 10;
12440 for (i, (name, _)) in engine.users.iter().enumerate() {
12441 rows.push(Row::new(alloc::vec![
12442 Value::BigInt(oid + (i as i64) + 1),
12443 Value::Text(name.to_string()),
12444 Value::Bool(false),
12445 Value::Bool(true),
12446 Value::Bool(true),
12447 ]));
12448 }
12449 if !rows
12452 .iter()
12453 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
12454 {
12455 rows.insert(
12456 0,
12457 Row::new(alloc::vec![
12458 Value::BigInt(10),
12459 Value::Text("postgres".into()),
12460 Value::Bool(true),
12461 Value::Bool(true),
12462 Value::Bool(true),
12463 ]),
12464 );
12465 }
12466 (schema, rows)
12467}
12468
12469fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
12478 let schema = alloc::vec![
12479 ColumnSchema::new("oid", DataType::BigInt, false),
12480 ColumnSchema::new("extname", DataType::Text, false),
12481 ColumnSchema::new("extversion", DataType::Text, false),
12482 ColumnSchema::new("extnamespace", DataType::Text, false),
12483 ];
12484 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
12485 let rows = exts
12486 .iter()
12487 .enumerate()
12488 .map(|(i, (name, ver))| {
12489 Row::new(alloc::vec![
12490 Value::BigInt(16384 + i as i64),
12491 Value::Text((*name).into()),
12492 Value::Text((*ver).into()),
12493 Value::Text("pg_catalog".into()),
12494 ])
12495 })
12496 .collect();
12497 (schema, rows)
12498}
12499
12500fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12501 let schema = alloc::vec![
12502 ColumnSchema::new("schemaname", DataType::Text, false),
12503 ColumnSchema::new("viewname", DataType::Text, false),
12504 ColumnSchema::new("definition", DataType::Text, false),
12505 ];
12506 let mut rows: Vec<Row> = Vec::new();
12507 for (name, def) in cat.views() {
12508 rows.push(Row::new(alloc::vec![
12509 Value::Text("public".into()),
12510 Value::Text(name.clone()),
12511 Value::Text(def.body.clone()),
12512 ]));
12513 }
12514 (schema, rows)
12515}
12516
12517fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
12523 let schema = alloc::vec![
12524 ColumnSchema::new("name", DataType::Text, false),
12525 ColumnSchema::new("setting", DataType::Text, false),
12526 ColumnSchema::new("category", DataType::Text, false),
12527 ];
12528 let mut rows: Vec<Row> = Vec::new();
12529 let defaults: &[(&str, &str, &str)] = &[
12531 ("server_version", "16.0 (spg)", "Preset Options"),
12532 ("server_encoding", "UTF8", "Client Connection Defaults"),
12533 ("client_encoding", "UTF8", "Client Connection Defaults"),
12534 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
12535 ("TimeZone", "UTC", "Client Connection Defaults"),
12536 ("standard_conforming_strings", "on", "Compatibility"),
12537 ("integer_datetimes", "on", "Compatibility"),
12538 ("max_connections", "100", "Connections and Authentication"),
12539 ];
12540 for &(name, val, cat) in defaults {
12541 rows.push(Row::new(alloc::vec![
12542 Value::Text(name.into()),
12543 Value::Text(val.into()),
12544 Value::Text(cat.into()),
12545 ]));
12546 }
12547 for (k, v) in &engine.session_params {
12549 if !defaults
12550 .iter()
12551 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
12552 {
12553 rows.push(Row::new(alloc::vec![
12554 Value::Text(k.clone()),
12555 Value::Text(v.clone()),
12556 Value::Text("Session".into()),
12557 ]));
12558 }
12559 }
12560 (schema, rows)
12561}
12562
12563fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12574 let schema = alloc::vec![
12575 ColumnSchema::new("schemaname", DataType::Text, false),
12576 ColumnSchema::new("tablename", DataType::Text, false),
12577 ColumnSchema::new("indexname", DataType::Text, false),
12578 ColumnSchema::new("indexdef", DataType::Text, false),
12579 ];
12580 let mut rows: Vec<Row> = Vec::new();
12581 for tname in cat.table_names() {
12582 let Some(t) = cat.get(&tname) else { continue };
12583 for idx in t.indices() {
12584 let col_name = t
12585 .schema()
12586 .columns
12587 .get(idx.column_position)
12588 .map_or("?".into(), |c| c.name.clone());
12589 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
12590 let indexdef = alloc::format!(
12591 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
12592 idx.name,
12593 tname,
12594 col_name
12595 );
12596 rows.push(Row::new(alloc::vec![
12597 Value::Text("public".into()),
12598 Value::Text(tname.clone()),
12599 Value::Text(idx.name.clone()),
12600 Value::Text(indexdef),
12601 ]));
12602 }
12603 }
12604 (schema, rows)
12605}
12606
12607fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12619 let schema = alloc::vec![
12620 ColumnSchema::new("indexrelid", DataType::BigInt, false),
12621 ColumnSchema::new("indrelid", DataType::BigInt, false),
12622 ColumnSchema::new("indnatts", DataType::Int, false),
12623 ColumnSchema::new("indisunique", DataType::Bool, false),
12624 ColumnSchema::new("indisprimary", DataType::Bool, false),
12625 ];
12626 let mut rows: Vec<Row> = Vec::new();
12627 let mut idx_oid: i64 = 100_000;
12628 for (table_idx, tname) in cat.table_names().iter().enumerate() {
12629 let Some(t) = cat.get(tname) else { continue };
12630 for idx in t.indices() {
12631 idx_oid += 1;
12632 #[allow(clippy::cast_possible_wrap)]
12633 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
12634 let is_primary = idx.name.ends_with("_pkey");
12637 rows.push(Row::new(alloc::vec![
12638 Value::BigInt(idx_oid),
12639 Value::BigInt((table_idx + 1) as i64),
12640 Value::Int(nattrs),
12641 Value::Bool(idx.is_unique),
12642 Value::Bool(is_primary),
12643 ]));
12644 }
12645 }
12646 (schema, rows)
12647}
12648
12649fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
12654 let schema = alloc::vec![
12655 ColumnSchema::new("oid", DataType::BigInt, false),
12656 ColumnSchema::new("nspname", DataType::Text, false),
12657 ColumnSchema::new("nspowner", DataType::BigInt, false),
12658 ];
12659 let rows = alloc::vec![
12660 Row::new(alloc::vec![
12661 Value::BigInt(11),
12662 Value::Text("pg_catalog".into()),
12663 Value::BigInt(10),
12664 ]),
12665 Row::new(alloc::vec![
12666 Value::BigInt(2200),
12667 Value::Text("public".into()),
12668 Value::BigInt(10),
12669 ]),
12670 Row::new(alloc::vec![
12671 Value::BigInt(13000),
12672 Value::Text("information_schema".into()),
12673 Value::BigInt(10),
12674 ]),
12675 ];
12676 (schema, rows)
12677}
12678
12679fn materialise_meta_view(
12682 catalog: &mut Catalog,
12683 name: &str,
12684 columns: Vec<ColumnSchema>,
12685 rows: Vec<Row>,
12686) -> Result<(), EngineError> {
12687 let schema = TableSchema::new(name.to_string(), columns);
12688 catalog.create_table(schema).map_err(EngineError::Storage)?;
12689 let table = catalog
12690 .get_mut(name)
12691 .expect("just-created meta view must exist");
12692 for row in rows {
12693 table.insert(row).map_err(EngineError::Storage)?;
12694 }
12695 Ok(())
12696}
12697
12698fn collect_view_refs(
12711 tref: &spg_sql::ast::TableRef,
12712 cat: &spg_storage::Catalog,
12713 into: &mut Vec<String>,
12714) {
12715 if cat.views().contains_key(&tref.name)
12716 && cat.get(&tref.name).is_none()
12717 && !into.iter().any(|n| n == &tref.name)
12718 {
12719 into.push(tref.name.clone());
12720 }
12721}
12722
12723fn select_references_meta_view(stmt: &SelectStatement) -> bool {
12724 fn is_meta(name: &str) -> bool {
12725 name.starts_with("__spg_info_")
12726 || name.starts_with("__spg_pg_")
12727 || name.starts_with("__spg_mysql_")
12728 }
12729 if let Some(from) = &stmt.from {
12730 if is_meta(&from.primary.name) {
12731 return true;
12732 }
12733 for j in &from.joins {
12734 if is_meta(&j.table.name) {
12735 return true;
12736 }
12737 }
12738 }
12739 for cte in &stmt.ctes {
12740 if select_references_meta_view(&cte.body) {
12741 return true;
12742 }
12743 }
12744 false
12745}
12746
12747fn collect_meta_view_names(
12752 stmt: &SelectStatement,
12753 into: &mut alloc::collections::BTreeSet<String>,
12754) {
12755 fn is_meta(name: &str) -> bool {
12756 name.starts_with("__spg_info_")
12757 || name.starts_with("__spg_pg_")
12758 || name.starts_with("__spg_mysql_")
12759 }
12760 if let Some(from) = &stmt.from {
12761 if is_meta(&from.primary.name) {
12762 into.insert(from.primary.name.clone());
12763 }
12764 for j in &from.joins {
12765 if is_meta(&j.table.name) {
12766 into.insert(j.table.name.clone());
12767 }
12768 }
12769 }
12770 for cte in &stmt.ctes {
12771 collect_meta_view_names(&cte.body, into);
12772 }
12773}
12774
12775fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
12776 let mut out = columns.to_vec();
12777 for (col_idx, col) in out.iter_mut().enumerate() {
12778 if col.ty != DataType::Text {
12779 continue;
12780 }
12781 let mut inferred: Option<DataType> = None;
12782 let mut all_null = true;
12783 for row in rows {
12784 let Some(v) = row.values.get(col_idx) else {
12785 continue;
12786 };
12787 let ty = match v {
12788 Value::Null => continue,
12789 Value::SmallInt(_) => DataType::SmallInt,
12790 Value::Int(_) => DataType::Int,
12791 Value::BigInt(_) => DataType::BigInt,
12792 Value::Float(_) => DataType::Float,
12793 Value::Bool(_) => DataType::Bool,
12794 Value::Vector(_) => DataType::Vector {
12795 dim: 0,
12796 encoding: VecEncoding::F32,
12797 },
12798 _ => DataType::Text,
12799 };
12800 all_null = false;
12801 inferred = Some(match inferred {
12802 None => ty,
12803 Some(prev) if prev == ty => prev,
12804 Some(_) => DataType::Text,
12805 });
12806 }
12807 if let Some(t) = inferred {
12808 col.ty = t;
12809 col.nullable = true;
12810 } else if all_null {
12811 col.nullable = true;
12812 }
12813 }
12814 out
12815}
12816
12817#[allow(clippy::too_many_lines, clippy::format_push_string)]
12822fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
12839 use alloc::collections::BTreeSet;
12840 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
12841 let mut out: Vec<String> = Vec::new();
12842 let cat = engine.active_catalog();
12843 let Some(from) = &stmt.from else {
12847 return out;
12848 };
12849 let mut tables: Vec<String> = Vec::new();
12850 tables.push(from.primary.name.clone());
12851 for j in &from.joins {
12852 tables.push(j.table.name.clone());
12853 }
12854 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
12857 if let Some(w) = &stmt.where_ {
12858 collect_column_refs(w, &mut col_refs);
12859 }
12860 for j in &from.joins {
12861 if let Some(on) = &j.on {
12862 collect_column_refs(on, &mut col_refs);
12863 }
12864 }
12865 for cn in &col_refs {
12866 let owner: Option<String> = if let Some(q) = &cn.qualifier {
12869 tables.iter().find(|t| t == &q).cloned()
12870 } else {
12871 tables.iter().find_map(|t| {
12872 cat.get(t).and_then(|tbl| {
12873 if tbl.schema().column_position(&cn.name).is_some() {
12874 Some(t.clone())
12875 } else {
12876 None
12877 }
12878 })
12879 })
12880 };
12881 let Some(owner) = owner else {
12882 continue;
12883 };
12884 let Some(tbl) = cat.get(&owner) else {
12885 continue;
12886 };
12887 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
12888 continue;
12889 };
12890 let already_indexed = tbl.indices().iter().any(|i| {
12893 matches!(i.kind, spg_storage::IndexKind::BTree(_))
12894 && i.column_position == col_pos
12895 && i.expression.is_none()
12896 && i.partial_predicate.is_none()
12897 });
12898 if already_indexed {
12899 continue;
12900 }
12901 if seen.insert((owner.clone(), cn.name.clone())) {
12902 out.push(alloc::format!(
12903 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
12904 owner,
12905 cn.name,
12906 owner,
12907 cn.name
12908 ));
12909 }
12910 }
12911 out
12912}
12913
12914fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
12917 match expr {
12918 Expr::Column(cn) => out.push(cn.clone()),
12919 Expr::FunctionCall { args, .. } => {
12920 for a in args {
12921 collect_column_refs(a, out);
12922 }
12923 }
12924 Expr::Binary { lhs, rhs, .. } => {
12925 collect_column_refs(lhs, out);
12926 collect_column_refs(rhs, out);
12927 }
12928 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
12929 _ => {}
12930 }
12931}
12932
12933fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
12934 let catalog = engine.active_catalog();
12935 let cold_ids = catalog.cold_segment_ids_global();
12936 let any_cold = !cold_ids.is_empty();
12937 let cold_ids_repr = if any_cold {
12938 let mut s = alloc::string::String::from("[");
12939 for (i, id) in cold_ids.iter().enumerate() {
12940 if i > 0 {
12941 s.push(',');
12942 }
12943 s.push_str(&alloc::format!("{id}"));
12944 }
12945 s.push(']');
12946 s
12947 } else {
12948 alloc::string::String::new()
12949 };
12950 for (idx, line) in lines.iter_mut().enumerate() {
12951 let trimmed = line.trim_start();
12952 let is_top_level = idx == 0;
12953 if is_top_level {
12954 line.push_str(&alloc::format!(" (rows={total_rows})"));
12955 continue;
12956 }
12957 if let Some(rest) = trimmed.strip_prefix("From: ") {
12958 let (name, scan_kind) = match rest.split_once(" [") {
12959 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12960 None => (rest.trim(), ""),
12961 };
12962 let bare = name.split_whitespace().next().unwrap_or(name);
12963 let hot = catalog.get(bare).map(|t| t.rows().len());
12964 let annot = match (hot, scan_kind) {
12969 (Some(h), "full scan") => {
12970 let mut s = alloc::format!(" (hot_rows={h}");
12971 if any_cold {
12972 s.push_str(&alloc::format!(
12973 ", cold_tier=present, cold_segments={cold_ids_repr}"
12974 ));
12975 }
12976 s.push(')');
12977 s
12978 }
12979 (Some(h), "index seek") => {
12980 let mut s = alloc::format!(" (hot_rows≤{h}");
12981 if any_cold {
12982 s.push_str(&alloc::format!(
12983 ", cold_tier=present, cold_segments={cold_ids_repr}"
12984 ));
12985 }
12986 s.push(')');
12987 s
12988 }
12989 _ => " (rows=—)".to_string(),
12990 };
12991 line.push_str(&annot);
12992 continue;
12993 }
12994 line.push_str(" (rows=—)");
12996 }
12997}
12998
12999fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
13000 let pad = " ".repeat(depth);
13001 let top = if !stmt.ctes.is_empty() {
13003 if stmt.ctes.iter().any(|c| c.recursive) {
13004 "CTEScan (WITH RECURSIVE)"
13005 } else {
13006 "CTEScan (WITH)"
13007 }
13008 } else if !stmt.unions.is_empty() {
13009 "UnionScan"
13010 } else if select_has_window(stmt) {
13011 "WindowAgg"
13012 } else if aggregate::uses_aggregate(stmt) {
13013 "Aggregate"
13014 } else if stmt.distinct {
13015 "Distinct"
13016 } else if stmt.from.is_some() {
13017 "TableScan"
13018 } else {
13019 "Result"
13020 };
13021 out.push(alloc::format!("{pad}{top}"));
13022 let child = " ".repeat(depth + 1);
13023 for cte in &stmt.ctes {
13025 let head = if cte.recursive {
13026 alloc::format!("{child}CTE (recursive): {}", cte.name)
13027 } else {
13028 alloc::format!("{child}CTE: {}", cte.name)
13029 };
13030 out.push(head);
13031 explain_select(&cte.body, engine, depth + 2, out);
13032 }
13033 if let Some(from) = &stmt.from {
13035 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
13036 if let Some(alias) = &from.primary.alias {
13037 tag.push_str(&alloc::format!(" AS {alias}"));
13038 }
13039 if let Some(w) = &stmt.where_
13042 && let Some(table) = engine.active_catalog().get(&from.primary.name)
13043 {
13044 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
13045 let cols = &table.schema().columns;
13046 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
13047 tag.push_str(" [index seek]");
13048 } else {
13049 tag.push_str(" [full scan]");
13050 }
13051 } else {
13052 tag.push_str(" [full scan]");
13053 }
13054 out.push(tag);
13055 for j in &from.joins {
13056 let kind = match j.kind {
13057 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
13058 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
13059 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
13060 };
13061 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
13062 if let Some(alias) = &j.table.alias {
13063 s.push_str(&alloc::format!(" AS {alias}"));
13064 }
13065 if j.on.is_some() {
13066 s.push_str(" (ON …)");
13067 }
13068 out.push(s);
13069 }
13070 }
13071 if let Some(w) = &stmt.where_ {
13073 let mut s = alloc::format!("{child}Filter: {w}");
13074 if expr_has_subquery(w) {
13075 s.push_str(" [subquery]");
13076 }
13077 out.push(s);
13078 }
13079 if let Some(gs) = &stmt.group_by {
13080 let mut parts = Vec::new();
13081 for g in gs {
13082 parts.push(alloc::format!("{g}"));
13083 }
13084 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
13085 }
13086 if let Some(h) = &stmt.having {
13087 out.push(alloc::format!("{child}Having: {h}"));
13088 }
13089 for o in &stmt.order_by {
13090 let dir = if o.desc { "DESC" } else { "ASC" };
13091 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
13092 }
13093 if let Some(lim) = stmt.limit {
13094 out.push(alloc::format!("{child}Limit: {lim}"));
13095 }
13096 if let Some(off) = stmt.offset {
13097 out.push(alloc::format!("{child}Offset: {off}"));
13098 }
13099 if stmt
13101 .items
13102 .iter()
13103 .any(|it| matches!(it, SelectItem::Wildcard))
13104 {
13105 out.push(alloc::format!("{child}Project: *"));
13106 } else {
13107 out.push(alloc::format!(
13108 "{child}Project: {} item(s)",
13109 stmt.items.len()
13110 ));
13111 }
13112 for (kind, peer) in &stmt.unions {
13114 let label = match kind {
13115 UnionKind::All => "UNION ALL",
13116 UnionKind::Distinct => "UNION",
13117 };
13118 out.push(alloc::format!("{child}{label}"));
13119 explain_select(peer, engine, depth + 2, out);
13120 }
13121}
13122
13123fn is_correlation_error(e: &EngineError) -> bool {
13128 matches!(
13129 e,
13130 EngineError::Eval(
13131 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
13132 )
13133 )
13134}
13135
13136struct JoinedPeer<'a> {
13147 eager_rows: Option<Vec<Row>>,
13148 cols: Vec<ColumnSchema>,
13149 alias: String,
13150 kind: JoinKind,
13151 on: Option<&'a Expr>,
13152 lateral: Option<&'a SelectStatement>,
13153 join_table: Option<String>,
13156}
13157
13158fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
13165 match expr {
13166 Expr::Column(c) => c.name.clone(),
13168 Expr::FunctionCall { name, .. } => name.clone(),
13171 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
13173 _ => alloc::format!("column{}", idx + 1),
13175 }
13176}
13177
13178fn substitute_outer_columns_multi(
13185 stmt: &mut SelectStatement,
13186 outer_row: &Row,
13187 outer_schema: &[ColumnSchema],
13188) {
13189 substitute_outer_in_select(stmt, outer_row, outer_schema);
13190}
13191
13192fn substitute_outer_in_select(
13193 stmt: &mut SelectStatement,
13194 outer_row: &Row,
13195 outer_schema: &[ColumnSchema],
13196) {
13197 for item in &mut stmt.items {
13198 if let SelectItem::Expr { expr, .. } = item {
13199 substitute_outer_in_expr(expr, outer_row, outer_schema);
13200 }
13201 }
13202 if let Some(w) = &mut stmt.where_ {
13203 substitute_outer_in_expr(w, outer_row, outer_schema);
13204 }
13205 if let Some(gs) = &mut stmt.group_by {
13206 for g in gs {
13207 substitute_outer_in_expr(g, outer_row, outer_schema);
13208 }
13209 }
13210 if let Some(h) = &mut stmt.having {
13211 substitute_outer_in_expr(h, outer_row, outer_schema);
13212 }
13213 for o in &mut stmt.order_by {
13214 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
13215 }
13216 for (_, peer) in &mut stmt.unions {
13217 substitute_outer_in_select(peer, outer_row, outer_schema);
13218 }
13219}
13220
13221fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
13222 if let Expr::Column(c) = e
13223 && let Some(qual) = &c.qualifier
13224 {
13225 let composite = alloc::format!("{qual}.{}", c.name);
13226 if let Some(idx) = outer_schema
13227 .iter()
13228 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13229 {
13230 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
13231 if let Ok(lit) = value_to_literal_expr(v) {
13232 *e = lit;
13233 return;
13234 }
13235 }
13236 }
13237 match e {
13238 Expr::Binary { lhs, rhs, .. } => {
13239 substitute_outer_in_expr(lhs, outer_row, outer_schema);
13240 substitute_outer_in_expr(rhs, outer_row, outer_schema);
13241 }
13242 Expr::Unary { expr: inner, .. } => {
13243 substitute_outer_in_expr(inner, outer_row, outer_schema);
13244 }
13245 Expr::FunctionCall { args, .. } => {
13246 for a in args {
13247 substitute_outer_in_expr(a, outer_row, outer_schema);
13248 }
13249 }
13250 Expr::Cast { expr: inner, .. } => {
13251 substitute_outer_in_expr(inner, outer_row, outer_schema);
13252 }
13253 Expr::Case {
13254 operand,
13255 branches,
13256 else_branch,
13257 } => {
13258 if let Some(op) = operand {
13259 substitute_outer_in_expr(op, outer_row, outer_schema);
13260 }
13261 for (cond, val) in branches {
13262 substitute_outer_in_expr(cond, outer_row, outer_schema);
13263 substitute_outer_in_expr(val, outer_row, outer_schema);
13264 }
13265 if let Some(e) = else_branch {
13266 substitute_outer_in_expr(e, outer_row, outer_schema);
13267 }
13268 }
13269 _ => {}
13270 }
13271}
13272
13273fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
13274 let outer_alias = ctx.table_alias.unwrap_or("");
13281 substitute_in_select(stmt, row, ctx, outer_alias);
13282}
13283
13284fn substitute_in_select(
13285 stmt: &mut SelectStatement,
13286 row: &Row,
13287 ctx: &EvalContext<'_>,
13288 outer_alias: &str,
13289) {
13290 for item in &mut stmt.items {
13291 if let SelectItem::Expr { expr, .. } = item {
13292 substitute_in_expr(expr, row, ctx, outer_alias);
13293 }
13294 }
13295 if let Some(w) = &mut stmt.where_ {
13296 substitute_in_expr(w, row, ctx, outer_alias);
13297 }
13298 if let Some(gs) = &mut stmt.group_by {
13299 for g in gs {
13300 substitute_in_expr(g, row, ctx, outer_alias);
13301 }
13302 }
13303 if let Some(h) = &mut stmt.having {
13304 substitute_in_expr(h, row, ctx, outer_alias);
13305 }
13306 for o in &mut stmt.order_by {
13307 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
13308 }
13309 for (_, peer) in &mut stmt.unions {
13310 substitute_in_select(peer, row, ctx, outer_alias);
13311 }
13312}
13313
13314fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
13315 if let Expr::Column(c) = e
13321 && c.qualifier.is_none()
13322 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
13323 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
13324 {
13325 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
13326 if let Ok(lit) = value_to_literal_expr(v) {
13327 *e = lit;
13328 return;
13329 }
13330 }
13331 if let Expr::Column(c) = e
13332 && let Some(qual) = &c.qualifier
13333 {
13334 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
13338 ctx.columns
13339 .iter()
13340 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
13341 } else {
13342 None
13343 }
13344 .or_else(|| {
13345 let composite = alloc::format!("{qual}.{name}", name = c.name);
13346 ctx.columns
13347 .iter()
13348 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
13349 });
13350 if let Some(idx) = idx {
13351 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
13352 if let Ok(lit) = value_to_literal_expr(v) {
13353 *e = lit;
13354 return;
13355 }
13356 }
13357 }
13358 match e {
13359 Expr::AggregateOrdered { call, order_by, .. } => {
13360 substitute_in_expr(call, row, ctx, outer_alias);
13361 for o in order_by.iter_mut() {
13362 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
13363 }
13364 }
13365 Expr::Binary { lhs, rhs, .. } => {
13366 substitute_in_expr(lhs, row, ctx, outer_alias);
13367 substitute_in_expr(rhs, row, ctx, outer_alias);
13368 }
13369 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13370 substitute_in_expr(expr, row, ctx, outer_alias);
13371 }
13372 Expr::Like { expr, pattern, .. } => {
13373 substitute_in_expr(expr, row, ctx, outer_alias);
13374 substitute_in_expr(pattern, row, ctx, outer_alias);
13375 }
13376 Expr::FunctionCall { args, .. } => {
13377 for a in args {
13378 substitute_in_expr(a, row, ctx, outer_alias);
13379 }
13380 }
13381 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
13382 Expr::WindowFunction {
13383 args,
13384 partition_by,
13385 order_by,
13386 ..
13387 } => {
13388 for a in args {
13389 substitute_in_expr(a, row, ctx, outer_alias);
13390 }
13391 for p in partition_by {
13392 substitute_in_expr(p, row, ctx, outer_alias);
13393 }
13394 for (o, _, _) in order_by {
13395 substitute_in_expr(o, row, ctx, outer_alias);
13396 }
13397 }
13398 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
13399 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
13400 substitute_in_select(subquery, row, ctx, outer_alias);
13401 }
13402 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
13403 Expr::Array(items) => {
13404 for elem in items {
13405 substitute_in_expr(elem, row, ctx, outer_alias);
13406 }
13407 }
13408 Expr::ArraySubscript { target, index } => {
13409 substitute_in_expr(target, row, ctx, outer_alias);
13410 substitute_in_expr(index, row, ctx, outer_alias);
13411 }
13412 Expr::AnyAll { expr, array, .. } => {
13413 substitute_in_expr(expr, row, ctx, outer_alias);
13414 substitute_in_expr(array, row, ctx, outer_alias);
13415 }
13416 Expr::Case {
13417 operand,
13418 branches,
13419 else_branch,
13420 } => {
13421 if let Some(o) = operand {
13422 substitute_in_expr(o, row, ctx, outer_alias);
13423 }
13424 for (w, t) in branches {
13425 substitute_in_expr(w, row, ctx, outer_alias);
13426 substitute_in_expr(t, row, ctx, outer_alias);
13427 }
13428 if let Some(e) = else_branch {
13429 substitute_in_expr(e, row, ctx, outer_alias);
13430 }
13431 }
13432 }
13433}
13434
13435fn encode_row_key(row: &Row) -> Vec<u8> {
13439 let mut out = Vec::new();
13440 for v in &row.values {
13441 let s = alloc::format!("{v:?}|");
13442 out.extend_from_slice(s.as_bytes());
13443 }
13444 out
13445}
13446
13447fn select_has_window(stmt: &SelectStatement) -> bool {
13448 for item in &stmt.items {
13449 if let SelectItem::Expr { expr, .. } = item
13450 && expr_has_window(expr)
13451 {
13452 return true;
13453 }
13454 }
13455 false
13456}
13457
13458fn expr_has_window(e: &Expr) -> bool {
13459 match e {
13460 Expr::WindowFunction { .. } => true,
13461 Expr::AggregateOrdered { call, order_by, .. } => {
13462 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
13463 }
13464 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
13465 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13466 expr_has_window(expr)
13467 }
13468 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
13469 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
13470 Expr::Extract { source, .. } => expr_has_window(source),
13471 Expr::ScalarSubquery(_)
13472 | Expr::Exists { .. }
13473 | Expr::InSubquery { .. }
13474 | Expr::Literal(_)
13475 | Expr::Placeholder(_)
13476 | Expr::Column(_) => false,
13477 Expr::Array(items) => items.iter().any(expr_has_window),
13478 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
13479 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
13480 Expr::Case {
13481 operand,
13482 branches,
13483 else_branch,
13484 } => {
13485 operand.as_deref().is_some_and(expr_has_window)
13486 || branches
13487 .iter()
13488 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
13489 || else_branch.as_deref().is_some_and(expr_has_window)
13490 }
13491 }
13492}
13493
13494fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
13495 if let Expr::WindowFunction { .. } = e {
13496 if !out.iter().any(|x| x == e) {
13501 out.push(e.clone());
13502 }
13503 return;
13504 }
13505 match e {
13506 Expr::WindowFunction { .. } => unreachable!(),
13508 Expr::Binary { lhs, rhs, .. } => {
13509 collect_window_nodes(lhs, out);
13510 collect_window_nodes(rhs, out);
13511 }
13512 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13513 collect_window_nodes(expr, out);
13514 }
13515 Expr::FunctionCall { args, .. } => {
13516 for a in args {
13517 collect_window_nodes(a, out);
13518 }
13519 }
13520 Expr::Like { expr, pattern, .. } => {
13521 collect_window_nodes(expr, out);
13522 collect_window_nodes(pattern, out);
13523 }
13524 Expr::Extract { source, .. } => collect_window_nodes(source, out),
13525 _ => {}
13526 }
13527}
13528
13529fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
13530 if let Expr::WindowFunction { .. } = e
13531 && let Some(idx) = window_nodes.iter().position(|w| w == e)
13532 {
13533 *e = Expr::Column(spg_sql::ast::ColumnName {
13534 qualifier: None,
13535 name: alloc::format!("__win_{idx}"),
13536 });
13537 return;
13538 }
13539 match e {
13540 Expr::Binary { lhs, rhs, .. } => {
13541 rewrite_window_to_columns(lhs, window_nodes);
13542 rewrite_window_to_columns(rhs, window_nodes);
13543 }
13544 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13545 rewrite_window_to_columns(expr, window_nodes);
13546 }
13547 Expr::FunctionCall { args, .. } => {
13548 for a in args {
13549 rewrite_window_to_columns(a, window_nodes);
13550 }
13551 }
13552 Expr::Like { expr, pattern, .. } => {
13553 rewrite_window_to_columns(expr, window_nodes);
13554 rewrite_window_to_columns(pattern, window_nodes);
13555 }
13556 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
13557 _ => {}
13558 }
13559}
13560
13561fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
13565 for (x, y) in a.iter().zip(b.iter()) {
13566 let c = value_cmp(x, y);
13567 if c != core::cmp::Ordering::Equal {
13568 return c;
13569 }
13570 }
13571 a.len().cmp(&b.len())
13572}
13573
13574fn order_key_cmp(
13575 a: &[(Value, bool, Option<bool>)],
13576 b: &[(Value, bool, Option<bool>)],
13577) -> core::cmp::Ordering {
13578 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
13581 let c = order_by_value_cmp(*desc, *nf, va, vb);
13582 if c != core::cmp::Ordering::Equal {
13583 return c;
13584 }
13585 }
13586 a.len().cmp(&b.len())
13587}
13588
13589const fn value_is_integer(v: &Value) -> bool {
13595 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
13596}
13597
13598const fn value_to_i64(v: &Value) -> i64 {
13602 match v {
13603 Value::SmallInt(n) => *n as i64,
13604 Value::Int(n) => *n as i64,
13605 Value::BigInt(n) => *n,
13606 _ => panic!("value_to_i64 called on non-integer Value"),
13607 }
13608}
13609
13610fn generate_series_integers(
13616 start: i64,
13617 stop: i64,
13618 step: i64,
13619 cancel: &CancelToken<'_>,
13620) -> Result<alloc::vec::Vec<Row>, EngineError> {
13621 if step == 0 {
13622 return Err(EngineError::Unsupported(
13623 "generate_series(): step argument cannot be zero".into(),
13624 ));
13625 }
13626 let mut out = alloc::vec::Vec::new();
13627 let mut cur = start;
13628 const MAX_ROWS: usize = 10_000_000;
13632 loop {
13633 cancel.check()?;
13634 if step > 0 && cur > stop {
13635 break;
13636 }
13637 if step < 0 && cur < stop {
13638 break;
13639 }
13640 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
13641 if out.len() > MAX_ROWS {
13642 return Err(EngineError::Unsupported(alloc::format!(
13643 "generate_series(): exceeded {MAX_ROWS} rows; \
13644 narrow start/stop or use a larger step"
13645 )));
13646 }
13647 cur = match cur.checked_add(step) {
13648 Some(n) => n,
13649 None => break,
13650 };
13651 }
13652 Ok(out)
13653}
13654
13655fn generate_series_timestamps(
13660 start: i64,
13661 stop: i64,
13662 step: Value,
13663 cancel: &CancelToken<'_>,
13664) -> Result<alloc::vec::Vec<Row>, EngineError> {
13665 let (months, micros) = match &step {
13666 Value::Interval { months, micros } => (*months, *micros),
13667 _ => unreachable!("caller guards step.is_interval"),
13668 };
13669 if months == 0 && micros == 0 {
13670 return Err(EngineError::Unsupported(
13671 "generate_series(): INTERVAL step cannot be zero".into(),
13672 ));
13673 }
13674 let ascending = months > 0 || micros > 0;
13675 let mut out = alloc::vec::Vec::new();
13676 let mut cur = Value::Timestamp(start);
13677 const MAX_ROWS: usize = 10_000_000;
13678 loop {
13679 cancel.check()?;
13680 let cur_t = match cur {
13681 Value::Timestamp(t) => t,
13682 _ => unreachable!("loop invariant: cur is Timestamp"),
13683 };
13684 if ascending && cur_t > stop {
13685 break;
13686 }
13687 if !ascending && cur_t < stop {
13688 break;
13689 }
13690 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
13691 if out.len() > MAX_ROWS {
13692 return Err(EngineError::Unsupported(alloc::format!(
13693 "generate_series(): exceeded {MAX_ROWS} rows; \
13694 narrow start/stop or use a larger step"
13695 )));
13696 }
13697 let next = eval::apply_binary_interval(
13698 spg_sql::ast::BinOp::Add,
13699 &cur,
13700 &Value::Interval { months, micros },
13701 )
13702 .map_err(EngineError::Eval)?;
13703 cur = match next {
13704 Some(v) => v,
13705 None => break,
13706 };
13707 }
13708 Ok(out)
13709}
13710
13711#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
13717 desc: bool,
13718 nulls_first: Option<bool>,
13719 a: &Value,
13720 b: &Value,
13721) -> core::cmp::Ordering {
13722 use core::cmp::Ordering;
13723 let nf = nulls_first.unwrap_or(desc);
13724 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
13725 (true, true) => Ordering::Equal,
13726 (true, false) => {
13727 if nf {
13728 Ordering::Less
13729 } else {
13730 Ordering::Greater
13731 }
13732 }
13733 (false, true) => {
13734 if nf {
13735 Ordering::Greater
13736 } else {
13737 Ordering::Less
13738 }
13739 }
13740 (false, false) => {
13741 let c = value_cmp(a, b);
13742 if desc { c.reverse() } else { c }
13743 }
13744 }
13745}
13746
13747fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
13748 use core::cmp::Ordering;
13749 match (a, b) {
13750 (Value::Null, Value::Null) => Ordering::Equal,
13751 (Value::Null, _) => Ordering::Less,
13752 (_, Value::Null) => Ordering::Greater,
13753 (Value::Int(x), Value::Int(y)) => x.cmp(y),
13754 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
13755 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
13756 (Value::Text(x), Value::Text(y)) => x.cmp(y),
13757 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
13758 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
13759 (Value::Date(x), Value::Date(y)) => x.cmp(y),
13760 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
13761 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
13764 }
13765}
13766
13767#[allow(
13773 clippy::too_many_arguments,
13774 clippy::cast_possible_truncation,
13775 clippy::cast_possible_wrap,
13776 clippy::cast_precision_loss,
13777 clippy::cast_sign_loss,
13778 clippy::doc_markdown,
13779 clippy::too_many_lines,
13780 clippy::type_complexity,
13781 clippy::match_same_arms
13782)]
13783fn compute_window_partition(
13784 name: &str,
13785 args: &[Expr],
13786 ordered: bool,
13787 frame: Option<&WindowFrame>,
13788 null_treatment: spg_sql::ast::NullTreatment,
13789 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13790 filtered_rows: &[&Row],
13791 ctx: &EvalContext<'_>,
13792 out_vals: &mut [Value],
13793) -> Result<(), EngineError> {
13794 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
13795 let lower = name.to_ascii_lowercase();
13796 match lower.as_str() {
13797 "row_number" => {
13798 for (rank, (_, _, idx)) in slice.iter().enumerate() {
13799 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
13800 }
13801 Ok(())
13802 }
13803 "rank" => {
13804 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13805 let mut current_rank: i64 = 1;
13806 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13807 if let Some(p) = prev_key
13808 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13809 {
13810 current_rank = (i + 1) as i64;
13811 }
13812 if prev_key.is_none() {
13813 current_rank = 1;
13814 }
13815 out_vals[*idx] = Value::BigInt(current_rank);
13816 prev_key = Some(okey.as_slice());
13817 }
13818 Ok(())
13819 }
13820 "dense_rank" => {
13821 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13822 let mut current_rank: i64 = 0;
13823 for (_, okey, idx) in slice {
13824 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
13825 current_rank += 1;
13826 }
13827 out_vals[*idx] = Value::BigInt(current_rank);
13828 prev_key = Some(okey.as_slice());
13829 }
13830 Ok(())
13831 }
13832 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
13833 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
13836 slice.iter().map(|_| Value::Null).collect()
13837 } else {
13838 slice
13839 .iter()
13840 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13841 .collect::<Result<_, _>>()
13842 .map_err(EngineError::Eval)?
13843 };
13844 let eff = effective_frame(frame, ordered)?;
13848 #[allow(clippy::needless_range_loop)]
13849 for i in 0..slice.len() {
13850 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13851 let mut sum: f64 = 0.0;
13852 let mut count: i64 = 0;
13853 let mut min_v: Option<f64> = None;
13854 let mut max_v: Option<f64> = None;
13855 let mut row_count: i64 = 0;
13856 if lo <= hi {
13857 for j in lo..=hi {
13858 let v = &arg_values[j];
13859 match lower.as_str() {
13860 "count_star" => row_count += 1,
13861 "count" => {
13862 if !v.is_null() {
13863 count += 1;
13864 }
13865 }
13866 _ => {
13867 if let Some(x) = value_to_f64(v) {
13868 sum += x;
13869 count += 1;
13870 min_v = Some(min_v.map_or(x, |m| m.min(x)));
13871 max_v = Some(max_v.map_or(x, |m| m.max(x)));
13872 }
13873 }
13874 }
13875 }
13876 }
13877 let value = match lower.as_str() {
13878 "count_star" => Value::BigInt(row_count),
13879 "count" => Value::BigInt(count),
13880 "sum" => Value::Float(sum),
13881 "avg" => {
13882 if count == 0 {
13883 Value::Null
13884 } else {
13885 Value::Float(sum / count as f64)
13886 }
13887 }
13888 "min" => min_v.map_or(Value::Null, Value::Float),
13889 "max" => max_v.map_or(Value::Null, Value::Float),
13890 _ => unreachable!(),
13891 };
13892 let (_, _, idx) = &slice[i];
13893 out_vals[*idx] = value;
13894 }
13895 Ok(())
13896 }
13897 "lag" | "lead" => {
13898 if args.is_empty() {
13901 return Err(EngineError::Unsupported(alloc::format!(
13902 "{lower}() requires at least one argument"
13903 )));
13904 }
13905 let offset: i64 = if args.len() >= 2 {
13906 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13907 .map_err(EngineError::Eval)?;
13908 match v {
13909 Value::SmallInt(n) => i64::from(n),
13910 Value::Int(n) => i64::from(n),
13911 Value::BigInt(n) => n,
13912 _ => {
13913 return Err(EngineError::Unsupported(alloc::format!(
13914 "{lower}() offset must be integer"
13915 )));
13916 }
13917 }
13918 } else {
13919 1
13920 };
13921 let default: Value = if args.len() >= 3 {
13922 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
13923 .map_err(EngineError::Eval)?
13924 } else {
13925 Value::Null
13926 };
13927 let values: Vec<Value> = slice
13928 .iter()
13929 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13930 .collect::<Result<_, _>>()
13931 .map_err(EngineError::Eval)?;
13932 let n = slice.len();
13933 for (i, (_, _, idx)) in slice.iter().enumerate() {
13934 let signed_offset = if lower == "lag" { -offset } else { offset };
13935 let v = if ignore_nulls {
13936 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
13940 let needed: i64 = signed_offset.abs();
13941 if needed == 0 {
13942 values[i].clone()
13943 } else {
13944 let mut j: i64 = i as i64;
13945 let mut hits: i64 = 0;
13946 let mut found: Option<Value> = None;
13947 loop {
13948 j += step;
13949 if j < 0 || j >= n as i64 {
13950 break;
13951 }
13952 #[allow(clippy::cast_sign_loss)]
13953 let v = &values[j as usize];
13954 if !v.is_null() {
13955 hits += 1;
13956 if hits == needed {
13957 found = Some(v.clone());
13958 break;
13959 }
13960 }
13961 }
13962 found.unwrap_or_else(|| default.clone())
13963 }
13964 } else {
13965 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
13966 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
13967 default.clone()
13968 } else {
13969 #[allow(clippy::cast_sign_loss)]
13970 {
13971 values[target_signed as usize].clone()
13972 }
13973 }
13974 };
13975 out_vals[*idx] = v;
13976 }
13977 Ok(())
13978 }
13979 "first_value" | "last_value" | "nth_value" => {
13980 if args.is_empty() {
13981 return Err(EngineError::Unsupported(alloc::format!(
13982 "{lower}() requires at least one argument"
13983 )));
13984 }
13985 let values: Vec<Value> = slice
13986 .iter()
13987 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13988 .collect::<Result<_, _>>()
13989 .map_err(EngineError::Eval)?;
13990 let nth: usize = if lower == "nth_value" {
13991 if args.len() < 2 {
13992 return Err(EngineError::Unsupported(
13993 "nth_value() requires (expr, n)".into(),
13994 ));
13995 }
13996 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13997 .map_err(EngineError::Eval)?;
13998 let raw = match v {
13999 Value::SmallInt(n) => i64::from(n),
14000 Value::Int(n) => i64::from(n),
14001 Value::BigInt(n) => n,
14002 _ => {
14003 return Err(EngineError::Unsupported(
14004 "nth_value() n must be integer".into(),
14005 ));
14006 }
14007 };
14008 if raw < 1 {
14009 return Err(EngineError::Unsupported(
14010 "nth_value() n must be >= 1".into(),
14011 ));
14012 }
14013 #[allow(clippy::cast_sign_loss)]
14014 {
14015 raw as usize
14016 }
14017 } else {
14018 0
14019 };
14020 let eff = effective_frame(frame, ordered)?;
14021 for i in 0..slice.len() {
14022 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
14023 let (_, _, idx) = &slice[i];
14024 let v = if lo > hi {
14025 Value::Null
14026 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
14027 if lower == "first_value" {
14030 (lo..=hi)
14031 .find_map(|j| {
14032 let v = &values[j];
14033 (!v.is_null()).then(|| v.clone())
14034 })
14035 .unwrap_or(Value::Null)
14036 } else {
14037 (lo..=hi)
14038 .rev()
14039 .find_map(|j| {
14040 let v = &values[j];
14041 (!v.is_null()).then(|| v.clone())
14042 })
14043 .unwrap_or(Value::Null)
14044 }
14045 } else {
14046 match lower.as_str() {
14047 "first_value" => values[lo].clone(),
14048 "last_value" => values[hi].clone(),
14049 "nth_value" => {
14050 let pos = lo + nth - 1;
14051 if pos > hi {
14052 Value::Null
14053 } else {
14054 values[pos].clone()
14055 }
14056 }
14057 _ => unreachable!(),
14058 }
14059 };
14060 out_vals[*idx] = v;
14061 }
14062 Ok(())
14063 }
14064 "ntile" => {
14065 if args.is_empty() {
14066 return Err(EngineError::Unsupported(
14067 "ntile(n) requires an integer argument".into(),
14068 ));
14069 }
14070 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
14071 .map_err(EngineError::Eval)?;
14072 let bucket_count: i64 = match v {
14073 Value::SmallInt(n) => i64::from(n),
14074 Value::Int(n) => i64::from(n),
14075 Value::BigInt(n) => n,
14076 _ => {
14077 return Err(EngineError::Unsupported(
14078 "ntile() argument must be integer".into(),
14079 ));
14080 }
14081 };
14082 if bucket_count < 1 {
14083 return Err(EngineError::Unsupported(
14084 "ntile() argument must be >= 1".into(),
14085 ));
14086 }
14087 #[allow(clippy::cast_sign_loss)]
14088 let buckets = bucket_count as usize;
14089 let n = slice.len();
14090 let base = n / buckets;
14093 let extras = n % buckets;
14094 let mut bucket: usize = 1;
14095 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
14096 let mut buckets_with_extra_remaining = extras;
14097 for (_, _, idx) in slice {
14098 if remaining_in_bucket == 0 {
14099 bucket += 1;
14100 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
14101 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
14102 base + 1
14103 } else {
14104 base
14105 };
14106 if remaining_in_bucket == 0 {
14109 remaining_in_bucket = 1;
14110 }
14111 }
14112 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
14113 remaining_in_bucket -= 1;
14114 }
14115 Ok(())
14116 }
14117 "percent_rank" => {
14118 let n = slice.len();
14121 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
14122 let mut current_rank: i64 = 1;
14123 for (i, (_, okey, idx)) in slice.iter().enumerate() {
14124 if let Some(p) = prev_key
14125 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
14126 {
14127 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
14128 }
14129 if prev_key.is_none() {
14130 current_rank = 1;
14131 }
14132 #[allow(clippy::cast_precision_loss)]
14133 let pr = if n <= 1 {
14134 0.0
14135 } else {
14136 (current_rank - 1) as f64 / (n - 1) as f64
14137 };
14138 out_vals[*idx] = Value::Float(pr);
14139 prev_key = Some(okey.as_slice());
14140 }
14141 Ok(())
14142 }
14143 "cume_dist" => {
14144 let n = slice.len();
14146 for i in 0..slice.len() {
14148 let peer_end = peer_group_end(slice, i);
14149 #[allow(clippy::cast_precision_loss)]
14150 let cd = (peer_end + 1) as f64 / n as f64;
14151 let (_, _, idx) = &slice[i];
14152 out_vals[*idx] = Value::Float(cd);
14153 }
14154 Ok(())
14155 }
14156 other => Err(EngineError::Unsupported(alloc::format!(
14157 "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)"
14158 ))),
14159 }
14160}
14161
14162fn effective_frame(
14169 frame: Option<&WindowFrame>,
14170 ordered: bool,
14171) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
14172 match frame {
14173 None => {
14174 if ordered {
14175 Ok((
14176 FrameKind::Range,
14177 FrameBound::UnboundedPreceding,
14178 FrameBound::CurrentRow,
14179 ))
14180 } else {
14181 Ok((
14182 FrameKind::Rows,
14183 FrameBound::UnboundedPreceding,
14184 FrameBound::UnboundedFollowing,
14185 ))
14186 }
14187 }
14188 Some(fr) => {
14189 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
14190 if matches!(fr.start, FrameBound::UnboundedFollowing)
14192 || matches!(end, FrameBound::UnboundedPreceding)
14193 {
14194 return Err(EngineError::Unsupported(alloc::format!(
14195 "invalid frame: start={:?} end={:?}",
14196 fr.start,
14197 end
14198 )));
14199 }
14200 if fr.kind == FrameKind::Range
14205 && (matches!(
14206 fr.start,
14207 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
14208 ) || matches!(
14209 end,
14210 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
14211 ))
14212 {
14213 return Err(EngineError::Unsupported(
14214 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
14215 ));
14216 }
14217 Ok((fr.kind, fr.start.clone(), end))
14218 }
14219 }
14220}
14221
14222#[allow(clippy::type_complexity)]
14226fn frame_bounds_for_row(
14227 eff: &(FrameKind, FrameBound, FrameBound),
14228 i: usize,
14229 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14230) -> (usize, usize) {
14231 let (kind, start, end) = eff;
14232 let n = slice.len();
14233 let last = n.saturating_sub(1);
14234 let (mut lo, mut hi) = match kind {
14235 FrameKind::Rows => {
14236 let lo = match start {
14237 FrameBound::UnboundedPreceding => 0,
14238 FrameBound::OffsetPreceding(k) => {
14239 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14240 i.saturating_sub(k)
14241 }
14242 FrameBound::CurrentRow => i,
14243 FrameBound::OffsetFollowing(k) => {
14244 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14245 i.saturating_add(k).min(last)
14246 }
14247 FrameBound::UnboundedFollowing => last,
14248 };
14249 let hi = match end {
14250 FrameBound::UnboundedPreceding => 0,
14251 FrameBound::OffsetPreceding(k) => {
14252 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14253 i.saturating_sub(k)
14254 }
14255 FrameBound::CurrentRow => i,
14256 FrameBound::OffsetFollowing(k) => {
14257 let k = usize::try_from(*k).unwrap_or(usize::MAX);
14258 i.saturating_add(k).min(last)
14259 }
14260 FrameBound::UnboundedFollowing => last,
14261 };
14262 (lo, hi)
14263 }
14264 FrameKind::Range => {
14265 let lo = match start {
14271 FrameBound::UnboundedPreceding => 0,
14272 FrameBound::CurrentRow => peer_group_start(slice, i),
14273 FrameBound::UnboundedFollowing => last,
14274 _ => unreachable!("offset bounds rejected for RANGE"),
14275 };
14276 let hi = match end {
14277 FrameBound::UnboundedPreceding => 0,
14278 FrameBound::CurrentRow => peer_group_end(slice, i),
14279 FrameBound::UnboundedFollowing => last,
14280 _ => unreachable!("offset bounds rejected for RANGE"),
14281 };
14282 (lo, hi)
14283 }
14284 };
14285 if hi >= n {
14286 hi = last;
14287 }
14288 if lo >= n {
14289 lo = last;
14290 }
14291 (lo, hi)
14292}
14293
14294#[allow(clippy::type_complexity)]
14298fn peer_group_start(
14299 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14300 i: usize,
14301) -> usize {
14302 let key = &slice[i].1;
14303 let mut j = i;
14304 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
14305 j -= 1;
14306 }
14307 j
14308}
14309
14310#[allow(clippy::type_complexity)]
14313fn peer_group_end(
14314 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
14315 i: usize,
14316) -> usize {
14317 let key = &slice[i].1;
14318 let mut j = i;
14319 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
14320 j += 1;
14321 }
14322 j
14323}
14324
14325fn value_to_f64(v: &Value) -> Option<f64> {
14326 match v {
14327 Value::SmallInt(n) => Some(f64::from(*n)),
14328 Value::Int(n) => Some(f64::from(*n)),
14329 #[allow(clippy::cast_precision_loss)]
14330 Value::BigInt(n) => Some(*n as f64),
14331 Value::Float(x) => Some(*x),
14332 _ => None,
14333 }
14334}
14335
14336fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
14340 let mut any = false;
14341 for item in &stmt.items {
14342 if let SelectItem::Expr { expr, .. } = item {
14343 any = any || expr_has_subquery(expr);
14344 }
14345 }
14346 if let Some(w) = &stmt.where_ {
14347 any = any || expr_has_subquery(w);
14348 }
14349 if let Some(h) = &stmt.having {
14350 any = any || expr_has_subquery(h);
14351 }
14352 for o in &stmt.order_by {
14353 any = any || expr_has_subquery(&o.expr);
14354 }
14355 for (_, peer) in &stmt.unions {
14356 any = any || expr_tree_has_subquery(peer);
14357 }
14358 any
14359}
14360
14361pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
14362 match e {
14363 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
14364 Expr::AggregateOrdered { call, order_by, .. } => {
14365 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
14366 }
14367 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
14368 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14369 expr_has_subquery(expr)
14370 }
14371 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
14372 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
14373 Expr::Extract { source, .. } => expr_has_subquery(source),
14374 Expr::WindowFunction {
14375 args,
14376 partition_by,
14377 order_by,
14378 ..
14379 } => {
14380 args.iter().any(expr_has_subquery)
14381 || partition_by.iter().any(expr_has_subquery)
14382 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
14383 }
14384 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
14385 Expr::Array(items) => items.iter().any(expr_has_subquery),
14386 Expr::ArraySubscript { target, index } => {
14387 expr_has_subquery(target) || expr_has_subquery(index)
14388 }
14389 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
14390 Expr::Case {
14391 operand,
14392 branches,
14393 else_branch,
14394 } => {
14395 operand.as_deref().is_some_and(expr_has_subquery)
14396 || branches
14397 .iter()
14398 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
14399 || else_branch.as_deref().is_some_and(expr_has_subquery)
14400 }
14401 }
14402}
14403
14404fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
14411 let lit = match v {
14412 Value::Null => Literal::Null,
14413 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14414 Value::Int(n) => Literal::Integer(i64::from(n)),
14415 Value::BigInt(n) => Literal::Integer(n),
14416 Value::Float(x) => Literal::Float(x),
14417 Value::Text(s) | Value::Json(s) => Literal::String(s),
14418 Value::Bool(b) => Literal::Bool(b),
14419 other => {
14420 return Err(EngineError::Unsupported(alloc::format!(
14421 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
14422 other.data_type()
14423 )));
14424 }
14425 };
14426 Ok(Expr::Literal(lit))
14427}
14428
14429fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
14435 let lit = match v {
14436 Value::Null => Literal::Null,
14437 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14438 Value::Int(n) => Literal::Integer(i64::from(n)),
14439 Value::BigInt(n) => Literal::Integer(n),
14440 Value::Float(x) => Literal::Float(x),
14441 Value::Text(s) | Value::Json(s) => Literal::String(s),
14442 Value::Bool(b) => Literal::Bool(b),
14443 Value::Vector(xs) => Literal::Vector(xs),
14444 Value::Date(days) => {
14448 let micros = (i64::from(days)) * 86_400_000_000;
14449 Literal::String(format_timestamp_micros_as_date(micros))
14450 }
14451 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
14452 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
14453 other => {
14454 return Err(EngineError::Unsupported(alloc::format!(
14455 "INSERT … SELECT cannot materialise value of type {:?}; \
14456 add an explicit CAST in the inner SELECT",
14457 other.data_type()
14458 )));
14459 }
14460 };
14461 Ok(Expr::Literal(lit))
14462}
14463
14464fn format_timestamp_micros(us: i64) -> String {
14465 let days = us.div_euclid(86_400_000_000);
14467 let intra_day = us.rem_euclid(86_400_000_000);
14468 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
14469 let secs = intra_day / 1_000_000;
14470 let us_rem = intra_day % 1_000_000;
14471 let h = (secs / 3600) % 24;
14472 let m = (secs / 60) % 60;
14473 let s = secs % 60;
14474 if us_rem == 0 {
14475 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
14476 } else {
14477 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
14478 }
14479}
14480
14481fn format_timestamp_micros_as_date(us: i64) -> String {
14482 let days = us.div_euclid(86_400_000_000);
14485 let jdn = days + 2_440_588;
14487 let (y, mo, d) = jdn_to_ymd(jdn);
14488 alloc::format!("{y:04}-{mo:02}-{d:02}")
14489}
14490
14491fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
14492 let l = jdn + 68569;
14494 let n = (4 * l) / 146_097;
14495 let l = l - (146_097 * n + 3) / 4;
14496 let i = (4000 * (l + 1)) / 1_461_001;
14497 let l = l - (1461 * i) / 4 + 31;
14498 let j = (80 * l) / 2447;
14499 let day = (l - (2447 * j) / 80) as u32;
14500 let l = j / 11;
14501 let month = (j + 2 - 12 * l) as u32;
14502 let year = 100 * (n - 49) + i + l;
14503 (year, month, day)
14504}
14505
14506fn format_numeric(scaled: i128, scale: u8) -> String {
14507 if scale == 0 {
14508 return alloc::format!("{scaled}");
14509 }
14510 let abs = scaled.unsigned_abs();
14511 let divisor = 10u128.pow(u32::from(scale));
14512 let whole = abs / divisor;
14513 let frac = abs % divisor;
14514 let sign = if scaled < 0 { "-" } else { "" };
14515 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
14516}
14517
14518fn rewrite_column_in_source(
14542 src: &str,
14543 old: &str,
14544 new: &str,
14545) -> Result<alloc::string::String, EngineError> {
14546 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14547 EngineError::Unsupported(alloc::format!(
14548 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
14549 failed to parse for rewrite ({e})"
14550 ))
14551 })?;
14552 rewrite_column_in_expr(&mut expr, old, new);
14553 Ok(alloc::format!("{expr}"))
14554}
14555
14556fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
14564 match e {
14565 Expr::AggregateOrdered { call, order_by, .. } => {
14566 rewrite_column_in_expr(call, old, new);
14567 for o in order_by.iter_mut() {
14568 rewrite_column_in_expr(&mut o.expr, old, new);
14569 }
14570 }
14571 Expr::Column(c) => {
14572 if c.name.eq_ignore_ascii_case(old) {
14573 c.name = new.to_string();
14574 }
14575 }
14576 Expr::Binary { lhs, rhs, .. } => {
14577 rewrite_column_in_expr(lhs, old, new);
14578 rewrite_column_in_expr(rhs, old, new);
14579 }
14580 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14581 rewrite_column_in_expr(expr, old, new);
14582 }
14583 Expr::FunctionCall { args, .. } => {
14584 for a in args {
14585 rewrite_column_in_expr(a, old, new);
14586 }
14587 }
14588 Expr::Like { expr, pattern, .. } => {
14589 rewrite_column_in_expr(expr, old, new);
14590 rewrite_column_in_expr(pattern, old, new);
14591 }
14592 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
14593 Expr::WindowFunction {
14594 args,
14595 partition_by,
14596 order_by,
14597 ..
14598 } => {
14599 for a in args {
14600 rewrite_column_in_expr(a, old, new);
14601 }
14602 for p in partition_by {
14603 rewrite_column_in_expr(p, old, new);
14604 }
14605 for (o, _, _) in order_by {
14606 rewrite_column_in_expr(o, old, new);
14607 }
14608 }
14609 Expr::Array(items) => {
14610 for elem in items {
14611 rewrite_column_in_expr(elem, old, new);
14612 }
14613 }
14614 Expr::ArraySubscript { target, index } => {
14615 rewrite_column_in_expr(target, old, new);
14616 rewrite_column_in_expr(index, old, new);
14617 }
14618 Expr::AnyAll { expr, array, .. } => {
14619 rewrite_column_in_expr(expr, old, new);
14620 rewrite_column_in_expr(array, old, new);
14621 }
14622 Expr::Case {
14623 operand,
14624 branches,
14625 else_branch,
14626 } => {
14627 if let Some(o) = operand {
14628 rewrite_column_in_expr(o, old, new);
14629 }
14630 for (w, t) in branches {
14631 rewrite_column_in_expr(w, old, new);
14632 rewrite_column_in_expr(t, old, new);
14633 }
14634 if let Some(e) = else_branch {
14635 rewrite_column_in_expr(e, old, new);
14636 }
14637 }
14638 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
14642 Expr::Literal(_) | Expr::Placeholder(_) => {}
14643 }
14644}
14645
14646pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
14654 match stmt {
14655 Statement::Select(s) => substitute_select(s, params)?,
14656 Statement::Insert(ins) => {
14657 for row in &mut ins.rows {
14658 for e in row {
14659 substitute_expr(e, params)?;
14660 }
14661 }
14662 if let Some(clause) = &mut ins.on_conflict
14666 && let spg_sql::ast::OnConflictAction::Update {
14667 assignments,
14668 where_,
14669 } = &mut clause.action
14670 {
14671 for (_, e) in assignments.iter_mut() {
14672 substitute_expr(e, params)?;
14673 }
14674 if let Some(w) = where_ {
14675 substitute_expr(w, params)?;
14676 }
14677 }
14678 }
14679 Statement::Update(u) => {
14680 for (_, e) in &mut u.assignments {
14681 substitute_expr(e, params)?;
14682 }
14683 if let Some(w) = &mut u.where_ {
14684 substitute_expr(w, params)?;
14685 }
14686 }
14687 Statement::Delete(d) => {
14688 if let Some(w) = &mut d.where_ {
14689 substitute_expr(w, params)?;
14690 }
14691 }
14692 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
14693 _ => {}
14696 }
14697 Ok(())
14698}
14699
14700pub(crate) fn walk_select_exprs_mut(
14710 s: &mut SelectStatement,
14711 f: &mut impl FnMut(&mut Expr) -> Result<(), EngineError>,
14712) -> Result<(), EngineError> {
14713 for cte in &mut s.ctes {
14714 walk_select_exprs_mut(&mut cte.body, f)?;
14715 }
14716 for item in &mut s.items {
14717 if let SelectItem::Expr { expr, .. } = item {
14718 f(expr)?;
14719 }
14720 }
14721 if let Some(from) = &mut s.from {
14722 if let Some(sub) = &mut from.primary.lateral_subquery {
14723 walk_select_exprs_mut(sub, f)?;
14724 }
14725 for j in &mut from.joins {
14726 if let Some(sub) = &mut j.table.lateral_subquery {
14727 walk_select_exprs_mut(sub, f)?;
14728 }
14729 if let Some(on) = &mut j.on {
14730 f(on)?;
14731 }
14732 }
14733 }
14734 if let Some(w) = &mut s.where_ {
14735 f(w)?;
14736 }
14737 if let Some(gs) = &mut s.group_by {
14738 for g in gs {
14739 f(g)?;
14740 }
14741 }
14742 if let Some(h) = &mut s.having {
14743 f(h)?;
14744 }
14745 for o in &mut s.order_by {
14746 f(&mut o.expr)?;
14747 }
14748 for (_, peer) in &mut s.unions {
14749 walk_select_exprs_mut(peer, f)?;
14750 }
14751 Ok(())
14752}
14753
14754fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
14755 walk_select_exprs_mut(s, &mut |e| substitute_expr(e, params))?;
14756 for cte in &mut s.ctes {
14761 resolve_limit_offset_placeholders(&mut cte.body, params)?;
14762 }
14763 for (_, peer) in &mut s.unions {
14764 resolve_limit_offset_placeholders(peer, params)?;
14765 }
14766 if let Some(le) = s.limit {
14771 s.limit = Some(resolve_limit_placeholder(le, params)?);
14772 }
14773 if let Some(le) = s.offset {
14774 s.offset = Some(resolve_limit_placeholder(le, params)?);
14775 }
14776 Ok(())
14777}
14778
14779fn resolve_limit_offset_placeholders(
14782 s: &mut SelectStatement,
14783 params: &[Value],
14784) -> Result<(), EngineError> {
14785 if let Some(le) = s.limit {
14786 s.limit = Some(resolve_limit_placeholder(le, params)?);
14787 }
14788 if let Some(le) = s.offset {
14789 s.offset = Some(resolve_limit_placeholder(le, params)?);
14790 }
14791 for cte in &mut s.ctes {
14792 resolve_limit_offset_placeholders(&mut cte.body, params)?;
14793 }
14794 for (_, peer) in &mut s.unions {
14795 resolve_limit_offset_placeholders(peer, params)?;
14796 }
14797 Ok(())
14798}
14799
14800fn resolve_limit_placeholder(
14801 le: spg_sql::ast::LimitExpr,
14802 params: &[Value],
14803) -> Result<spg_sql::ast::LimitExpr, EngineError> {
14804 use spg_sql::ast::LimitExpr;
14805 match le {
14806 LimitExpr::Literal(_) => Ok(le),
14807 LimitExpr::Placeholder(n) => {
14808 let idx = usize::from(n).saturating_sub(1);
14809 let v = params.get(idx).ok_or_else(|| {
14810 EngineError::Eval(EvalError::PlaceholderOutOfRange {
14811 n,
14812 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
14813 })
14814 })?;
14815 let int = match v {
14816 Value::SmallInt(x) => Some(i64::from(*x)),
14817 Value::Int(x) => Some(i64::from(*x)),
14818 Value::BigInt(x) => Some(*x),
14819 _ => None,
14820 }
14821 .ok_or_else(|| {
14822 EngineError::Unsupported(alloc::format!(
14823 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
14824 ))
14825 })?;
14826 if int < 0 {
14827 return Err(EngineError::Unsupported(alloc::format!(
14828 "LIMIT/OFFSET ${n} bound to negative value {int}"
14829 )));
14830 }
14831 let bounded = u32::try_from(int).map_err(|_| {
14832 EngineError::Unsupported(alloc::format!(
14833 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
14834 ))
14835 })?;
14836 Ok(LimitExpr::Literal(bounded))
14837 }
14838 }
14839}
14840
14841fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
14842 if let Expr::Placeholder(n) = e {
14843 let idx = usize::from(*n).saturating_sub(1);
14844 let v = params.get(idx).ok_or_else(|| {
14845 EngineError::Eval(EvalError::PlaceholderOutOfRange {
14846 n: *n,
14847 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
14848 })
14849 })?;
14850 *e = Expr::Literal(value_to_literal(v.clone()));
14851 return Ok(());
14852 }
14853 match e {
14854 Expr::AggregateOrdered { call, order_by, .. } => {
14855 substitute_expr(call, params)?;
14856 for o in order_by.iter_mut() {
14857 substitute_expr(&mut o.expr, params)?;
14858 }
14859 }
14860 Expr::Binary { lhs, rhs, .. } => {
14861 substitute_expr(lhs, params)?;
14862 substitute_expr(rhs, params)?;
14863 }
14864 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14865 substitute_expr(expr, params)?;
14866 }
14867 Expr::FunctionCall { args, .. } => {
14868 for a in args {
14869 substitute_expr(a, params)?;
14870 }
14871 }
14872 Expr::Like { expr, pattern, .. } => {
14873 substitute_expr(expr, params)?;
14874 substitute_expr(pattern, params)?;
14875 }
14876 Expr::Extract { source, .. } => substitute_expr(source, params)?,
14877 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
14878 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
14879 Expr::InSubquery { expr, subquery, .. } => {
14880 substitute_expr(expr, params)?;
14881 substitute_select(subquery, params)?;
14882 }
14883 Expr::WindowFunction {
14884 args,
14885 partition_by,
14886 order_by,
14887 ..
14888 } => {
14889 for a in args {
14890 substitute_expr(a, params)?;
14891 }
14892 for p in partition_by {
14893 substitute_expr(p, params)?;
14894 }
14895 for (e, _, _) in order_by {
14896 substitute_expr(e, params)?;
14897 }
14898 }
14899 Expr::Literal(_) | Expr::Column(_) => {}
14900 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
14902 Expr::Array(items) => {
14903 for elem in items {
14904 substitute_expr(elem, params)?;
14905 }
14906 }
14907 Expr::ArraySubscript { target, index } => {
14908 substitute_expr(target, params)?;
14909 substitute_expr(index, params)?;
14910 }
14911 Expr::AnyAll { expr, array, .. } => {
14912 substitute_expr(expr, params)?;
14913 substitute_expr(array, params)?;
14914 }
14915 Expr::Case {
14916 operand,
14917 branches,
14918 else_branch,
14919 } => {
14920 if let Some(o) = operand {
14921 substitute_expr(o, params)?;
14922 }
14923 for (w, t) in branches {
14924 substitute_expr(w, params)?;
14925 substitute_expr(t, params)?;
14926 }
14927 if let Some(e) = else_branch {
14928 substitute_expr(e, params)?;
14929 }
14930 }
14931 }
14932 Ok(())
14933}
14934
14935fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
14953 use core::cmp::Ordering;
14954 match (a, b) {
14955 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
14956 (Value::Int(a), Value::Int(b)) => a.cmp(b),
14957 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
14958 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
14959 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
14960 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14961 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
14962 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14963 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
14964 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14965 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
14966 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
14967 (Value::Date(a), Value::Date(b)) => a.cmp(b),
14968 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
14969 (Value::SmallInt(n), Value::Float(x)) => {
14971 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14972 }
14973 (Value::Float(x), Value::SmallInt(n)) => {
14974 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14975 }
14976 (Value::Int(n), Value::Float(x)) => {
14977 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14978 }
14979 (Value::Float(x), Value::Int(n)) => {
14980 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14981 }
14982 (Value::BigInt(n), Value::Float(x)) => {
14983 #[allow(clippy::cast_precision_loss)]
14984 let nf = *n as f64;
14985 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
14986 }
14987 (Value::Float(x), Value::BigInt(n)) => {
14988 #[allow(clippy::cast_precision_loss)]
14989 let nf = *n as f64;
14990 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
14991 }
14992 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
14995 }
14996}
14997
14998fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
15005 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
15006 out.push('[');
15007 for (i, b) in bounds.iter().enumerate() {
15008 if i > 0 {
15009 out.push_str(", ");
15010 }
15011 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
15012 if needs_quote {
15013 out.push('"');
15014 for ch in b.chars() {
15015 if ch == '"' || ch == '\\' {
15016 out.push('\\');
15017 }
15018 out.push(ch);
15019 }
15020 out.push('"');
15021 } else {
15022 out.push_str(b);
15023 }
15024 }
15025 out.push(']');
15026 out
15027}
15028
15029pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
15039 match v {
15040 Value::Null => "NULL".to_string(),
15041 Value::SmallInt(n) => alloc::format!("{n}"),
15042 Value::Int(n) => alloc::format!("{n}"),
15043 Value::BigInt(n) => alloc::format!("{n}"),
15044 Value::Float(x) => alloc::format!("{x:?}"),
15045 Value::Text(s) | Value::Json(s) => s.clone(),
15046 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
15047 Value::Date(d) => eval::format_date(*d),
15048 Value::Timestamp(t) => eval::format_timestamp(*t),
15049 Value::Time(us) => eval::format_time(*us),
15051 Value::Year(y) => alloc::format!("{y:04}"),
15053 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
15055 Value::Money(c) => eval::format_money(*c),
15057 v @ Value::Range { .. } => format_range_str(v),
15059 Value::Hstore(pairs) => format_hstore_str(pairs),
15061 Value::IntArray2D(rows) => format_int_2d_text(rows),
15063 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
15064 Value::TextArray2D(rows) => format_text_2d_text(rows),
15065 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
15066 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
15067 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
15068 alloc::format!("{v:?}")
15072 }
15073 _ => alloc::format!("{v:?}"),
15077 }
15078}
15079
15080const fn is_internal_table_name(_name: &str) -> bool {
15087 false
15088}
15089
15090fn value_to_literal(v: Value) -> Literal {
15091 match v {
15092 Value::Null => Literal::Null,
15093 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
15094 Value::Int(n) => Literal::Integer(i64::from(n)),
15095 Value::BigInt(n) => Literal::Integer(n),
15096 Value::Float(x) => Literal::Float(x),
15097 Value::Text(s) | Value::Json(s) => Literal::String(s),
15098 Value::Bool(b) => Literal::Bool(b),
15099 Value::Vector(v) => Literal::Vector(v),
15100 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
15101 Value::Date(d) => Literal::String(eval::format_date(d)),
15102 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
15103 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
15109 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
15114 Value::TextArray(items) => Literal::TextArray(items),
15119 Value::IntArray(items) => Literal::IntArray(items),
15120 Value::BigIntArray(items) => Literal::BigIntArray(items),
15121 Value::Interval { months, micros } => Literal::Interval {
15122 months,
15123 micros,
15124 text: eval::format_interval(months, micros),
15125 },
15126 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
15129 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
15130 v => Literal::String(alloc::format!("{v:?}")),
15134 }
15135}
15136
15137fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
15138 let Some(now) = now_micros else {
15139 return;
15140 };
15141 match stmt {
15142 Statement::Select(s) => rewrite_select_clock(s, now),
15143 Statement::Insert(ins) => {
15144 for row in &mut ins.rows {
15145 for e in row {
15146 rewrite_expr_clock(e, now);
15147 }
15148 }
15149 if let Some(clause) = &mut ins.on_conflict
15153 && let spg_sql::ast::OnConflictAction::Update {
15154 assignments,
15155 where_,
15156 } = &mut clause.action
15157 {
15158 for (_, e) in assignments.iter_mut() {
15159 rewrite_expr_clock(e, now);
15160 }
15161 if let Some(w) = where_ {
15162 rewrite_expr_clock(w, now);
15163 }
15164 }
15165 }
15166 Statement::Update(u) => {
15170 for (_, e) in &mut u.assignments {
15171 rewrite_expr_clock(e, now);
15172 }
15173 if let Some(w) = &mut u.where_ {
15174 rewrite_expr_clock(w, now);
15175 }
15176 }
15177 Statement::Delete(d) => {
15178 if let Some(w) = &mut d.where_ {
15179 rewrite_expr_clock(w, now);
15180 }
15181 }
15182 _ => {}
15183 }
15184}
15185
15186fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
15187 let _ = walk_select_exprs_mut(s, &mut |e| {
15192 rewrite_expr_clock(e, now);
15193 Ok(())
15194 });
15195}
15196
15197fn rewrite_expr_clock(e: &mut Expr, now: i64) {
15205 if let Some(replacement) = clock_replacement_for(e, now) {
15209 *e = replacement;
15210 return;
15211 }
15212 match e {
15213 Expr::AggregateOrdered { call, order_by, .. } => {
15214 rewrite_expr_clock(call, now);
15215 for o in order_by.iter_mut() {
15216 rewrite_expr_clock(&mut o.expr, now);
15217 }
15218 }
15219 Expr::Binary { lhs, rhs, .. } => {
15220 rewrite_expr_clock(lhs, now);
15221 rewrite_expr_clock(rhs, now);
15222 }
15223 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
15224 rewrite_expr_clock(expr, now);
15225 }
15226 Expr::FunctionCall { args, .. } => {
15227 for a in args {
15228 rewrite_expr_clock(a, now);
15229 }
15230 }
15231 Expr::Like { expr, pattern, .. } => {
15232 rewrite_expr_clock(expr, now);
15233 rewrite_expr_clock(pattern, now);
15234 }
15235 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
15236 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
15240 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
15241 Expr::InSubquery { expr, subquery, .. } => {
15242 rewrite_expr_clock(expr, now);
15243 rewrite_select_clock(subquery, now);
15244 }
15245 Expr::WindowFunction {
15248 args,
15249 partition_by,
15250 order_by,
15251 ..
15252 } => {
15253 for a in args {
15254 rewrite_expr_clock(a, now);
15255 }
15256 for p in partition_by {
15257 rewrite_expr_clock(p, now);
15258 }
15259 for (e, _, _) in order_by {
15260 rewrite_expr_clock(e, now);
15261 }
15262 }
15263 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
15264 Expr::Array(items) => {
15265 for elem in items {
15266 rewrite_expr_clock(elem, now);
15267 }
15268 }
15269 Expr::ArraySubscript { target, index } => {
15270 rewrite_expr_clock(target, now);
15271 rewrite_expr_clock(index, now);
15272 }
15273 Expr::AnyAll { expr, array, .. } => {
15274 rewrite_expr_clock(expr, now);
15275 rewrite_expr_clock(array, now);
15276 }
15277 Expr::Case {
15278 operand,
15279 branches,
15280 else_branch,
15281 } => {
15282 if let Some(o) = operand {
15283 rewrite_expr_clock(o, now);
15284 }
15285 for (w, t) in branches {
15286 rewrite_expr_clock(w, now);
15287 rewrite_expr_clock(t, now);
15288 }
15289 if let Some(e) = else_branch {
15290 rewrite_expr_clock(e, now);
15291 }
15292 }
15293 }
15294}
15295
15296fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
15303 let (kind, name) = match e {
15304 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
15305 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
15306 _ => return None,
15307 };
15308 enum ClockShape {
15316 Timestamp,
15317 Date,
15318 UnixSeconds,
15319 }
15320 let shape = match name.len() {
15321 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
15322 Some(ClockShape::Timestamp)
15323 }
15324 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
15325 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
15326 Some(ClockShape::UnixSeconds)
15327 }
15328 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
15329 _ => None,
15330 };
15331 let shape = shape?;
15332 let payload = match shape {
15333 ClockShape::Timestamp => now,
15334 ClockShape::Date => now.div_euclid(86_400_000_000),
15335 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
15336 };
15337 let target = match shape {
15338 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
15339 ClockShape::Date => spg_sql::ast::CastTarget::Date,
15340 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
15341 };
15342 Some(Expr::Cast {
15343 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
15344 target,
15345 })
15346}
15347
15348#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15349enum ClockSite {
15350 Fn,
15351 BareIdent,
15352}
15353
15354fn expand_group_by_all(s: &mut SelectStatement) {
15365 if !s.group_by_all {
15366 for (_, peer) in &mut s.unions {
15367 expand_group_by_all(peer);
15368 }
15369 return;
15370 }
15371 let mut groups: Vec<Expr> = Vec::new();
15372 for item in &s.items {
15373 if let SelectItem::Expr { expr, .. } = item
15374 && !aggregate::contains_aggregate(expr)
15375 {
15376 groups.push(expr.clone());
15377 }
15378 }
15379 s.group_by = Some(groups);
15380 s.group_by_all = false;
15381 for (_, peer) in &mut s.unions {
15382 expand_group_by_all(peer);
15383 }
15384}
15385
15386fn resolve_order_by_position(s: &mut SelectStatement) {
15387 for order in &mut s.order_by {
15392 match &order.expr {
15393 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
15394 if let Ok(idx_one_based) = usize::try_from(*n) {
15395 let idx = idx_one_based - 1;
15396 if idx < s.items.len()
15397 && let SelectItem::Expr { expr, .. } = &s.items[idx]
15398 {
15399 order.expr = expr.clone();
15400 }
15401 }
15402 }
15403 Expr::Column(c) if c.qualifier.is_none() => {
15404 for item in &s.items {
15406 if let SelectItem::Expr {
15407 expr,
15408 alias: Some(a),
15409 } = item
15410 && a == &c.name
15411 {
15412 order.expr = expr.clone();
15413 break;
15414 }
15415 }
15416 }
15417 _ => {}
15418 }
15419 }
15420 for (_, peer) in &mut s.unions {
15421 resolve_order_by_position(peer);
15422 }
15423}
15424
15425fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
15438 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
15439 match keep {
15440 Some(k) if k < tagged.len() && k > 0 => {
15441 let pivot = k - 1;
15442 tagged.select_nth_unstable_by(pivot, cmp);
15443 tagged[..k].sort_by(cmp);
15444 tagged.truncate(k);
15445 }
15446 _ => {
15447 tagged.sort_by(cmp);
15448 }
15449 }
15450}
15451
15452fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
15453 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
15454}
15455
15456fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
15460 use core::cmp::Ordering;
15461 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
15462 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
15463 let ord = if descs.get(i).copied().unwrap_or(false) {
15464 ord.reverse()
15465 } else {
15466 ord
15467 };
15468 if ord != Ordering::Equal {
15469 return ord;
15470 }
15471 }
15472 Ordering::Equal
15473}
15474
15475fn build_order_keys(
15478 order_by: &[OrderBy],
15479 row: &Row,
15480 ctx: &EvalContext,
15481) -> Result<Vec<f64>, EngineError> {
15482 let mut keys = Vec::with_capacity(order_by.len());
15483 for o in order_by {
15484 let v = eval::eval_expr(&o.expr, row, ctx)?;
15485 if matches!(v, Value::Null) {
15492 let nf = o.nulls_first.unwrap_or(o.desc);
15493 keys.push(if nf == o.desc {
15494 f64::INFINITY
15495 } else {
15496 f64::NEG_INFINITY
15497 });
15498 } else {
15499 keys.push(value_to_order_key(&v)?);
15500 }
15501 }
15502 Ok(keys)
15503}
15504
15505fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
15509 if let Some(off) = offset {
15510 let off = off as usize;
15511 if off >= rows.len() {
15512 rows.clear();
15513 } else {
15514 rows.drain(..off);
15515 }
15516 }
15517 if let Some(n) = limit {
15518 rows.truncate(n as usize);
15519 }
15520}
15521
15522fn apply_offset_and_limit_tagged(
15533 tagged: &mut Vec<(Vec<f64>, Row)>,
15534 offset: Option<u32>,
15535 limit: Option<u32>,
15536 with_ties: bool,
15537) {
15538 if let Some(off) = offset {
15539 let off = off as usize;
15540 if off >= tagged.len() {
15541 tagged.clear();
15542 } else {
15543 tagged.drain(..off);
15544 }
15545 }
15546 if let Some(n) = limit {
15547 let n = n as usize;
15548 if with_ties && n > 0 && n < tagged.len() {
15549 let cutoff_key = tagged[n - 1].0.clone();
15550 let mut end = n;
15551 while end < tagged.len() && tagged[end].0 == cutoff_key {
15552 end += 1;
15553 }
15554 tagged.truncate(end);
15555 } else {
15556 tagged.truncate(n);
15557 }
15558 }
15559}
15560
15561fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
15567 if stmt.limit_with_ties && stmt.order_by.is_empty() {
15568 return Err(EngineError::Unsupported(alloc::string::String::from(
15569 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
15570 )));
15571 }
15572 Ok(())
15573}
15574
15575fn resolve_foreign_key(
15589 local_table_name: &str,
15590 local_cols: &[ColumnSchema],
15591 fk: spg_sql::ast::ForeignKeyConstraint,
15592 catalog: &Catalog,
15593) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
15594 let mut local_columns = Vec::with_capacity(fk.columns.len());
15596 for name in &fk.columns {
15597 let pos = local_cols
15598 .iter()
15599 .position(|c| c.name == *name)
15600 .ok_or_else(|| {
15601 EngineError::Unsupported(alloc::format!(
15602 "FOREIGN KEY references unknown local column {name:?}"
15603 ))
15604 })?;
15605 local_columns.push(pos);
15606 }
15607 let is_self_ref = fk.parent_table == local_table_name;
15611 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
15612 (local_cols, local_table_name)
15613 } else {
15614 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
15615 EngineError::Storage(StorageError::TableNotFound {
15616 name: fk.parent_table.clone(),
15617 })
15618 })?;
15619 (
15620 parent_table.schema().columns.as_slice(),
15621 fk.parent_table.as_str(),
15622 )
15623 };
15624 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
15629 if fk.columns.len() != 1 {
15630 return Err(EngineError::Unsupported(
15631 "composite FOREIGN KEY without explicit parent column list is not supported \
15632 — list the parent columns explicitly"
15633 .into(),
15634 ));
15635 }
15636 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
15638 .ok_or_else(|| {
15639 EngineError::Unsupported(alloc::format!(
15640 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
15641 to default the FOREIGN KEY against"
15642 ))
15643 })?;
15644 alloc::vec![pos]
15645 } else {
15646 let mut out = Vec::with_capacity(fk.parent_columns.len());
15647 for name in &fk.parent_columns {
15648 let pos = parent_cols_for_lookup
15649 .iter()
15650 .position(|c| c.name == *name)
15651 .ok_or_else(|| {
15652 EngineError::Unsupported(alloc::format!(
15653 "FOREIGN KEY references unknown parent column \
15654 {name:?} on table {parent_table_str:?}"
15655 ))
15656 })?;
15657 out.push(pos);
15658 }
15659 out
15660 };
15661 if parent_columns.len() != local_columns.len() {
15662 return Err(EngineError::Unsupported(alloc::format!(
15663 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
15664 local_columns.len(),
15665 parent_columns.len()
15666 )));
15667 }
15668 if !is_self_ref {
15678 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
15679 let primary_parent_col = parent_columns[0];
15680 let has_btree = parent_table
15681 .schema()
15682 .columns
15683 .get(primary_parent_col)
15684 .is_some()
15685 && parent_table.indices().iter().any(|idx| {
15686 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15687 && idx.column_position == primary_parent_col
15688 && idx.partial_predicate.is_none()
15689 });
15690 if !has_btree {
15691 return Err(EngineError::Unsupported(alloc::format!(
15692 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
15693 index — create one with `CREATE INDEX ... ON {} ({})` first",
15694 parent_table_str,
15695 parent_table_str,
15696 parent_table.schema().columns[primary_parent_col].name,
15697 )));
15698 }
15699 }
15700 let on_delete = fk_action_sql_to_storage(fk.on_delete);
15701 let on_update = fk_action_sql_to_storage(fk.on_update);
15702 Ok(spg_storage::ForeignKeyConstraint {
15703 name: fk.name,
15704 local_columns,
15705 parent_table: fk.parent_table,
15706 parent_columns,
15707 on_delete,
15708 on_update,
15709 })
15710}
15711
15712fn pick_pk_index_column(
15718 catalog: &Catalog,
15719 parent_name: &str,
15720 is_self_ref: bool,
15721 local_cols: &[ColumnSchema],
15722) -> Option<usize> {
15723 if is_self_ref {
15724 let _ = local_cols;
15728 return Some(0);
15729 }
15730 let parent = catalog.get(parent_name)?;
15731 parent.indices().iter().find_map(|idx| {
15732 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15733 && idx.partial_predicate.is_none()
15734 && idx.included_columns.is_empty()
15735 && idx.expression.is_none()
15736 {
15737 Some(idx.column_position)
15738 } else {
15739 None
15740 }
15741 })
15742}
15743
15744fn resolve_on_conflict_columns(
15751 catalog: &Catalog,
15752 table_name: &str,
15753 target: &[String],
15754) -> Result<Vec<usize>, EngineError> {
15755 let table = catalog.get(table_name).ok_or_else(|| {
15756 EngineError::Storage(StorageError::TableNotFound {
15757 name: table_name.into(),
15758 })
15759 })?;
15760 if target.is_empty() {
15761 if let Some(uc) = table.schema().uniqueness_constraints.first() {
15771 return Ok(uc.columns.clone());
15772 }
15773 let pos = table
15774 .indices()
15775 .iter()
15776 .find_map(|idx| {
15777 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15778 && idx.partial_predicate.is_none()
15779 && idx.included_columns.is_empty()
15780 && idx.expression.is_none()
15781 {
15782 Some(idx.column_position)
15783 } else {
15784 None
15785 }
15786 })
15787 .ok_or_else(|| {
15788 EngineError::Unsupported(alloc::format!(
15789 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
15790 ))
15791 })?;
15792 return Ok(alloc::vec![pos]);
15793 }
15794 let mut out = Vec::with_capacity(target.len());
15795 for name in target {
15796 let pos = table
15797 .schema()
15798 .columns
15799 .iter()
15800 .position(|c| c.name == *name)
15801 .ok_or_else(|| {
15802 EngineError::Unsupported(alloc::format!(
15803 "ON CONFLICT target column {name:?} not found on {table_name:?}"
15804 ))
15805 })?;
15806 out.push(pos);
15807 }
15808 Ok(out)
15809}
15810
15811fn on_conflict_key_exists(
15814 catalog: &Catalog,
15815 table_name: &str,
15816 column_pos: usize,
15817 key: &Value,
15818) -> bool {
15819 let Some(table) = catalog.get(table_name) else {
15820 return false;
15821 };
15822 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
15823 return false;
15824 };
15825 table.indices().iter().any(|idx| {
15826 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15827 && idx.column_position == column_pos
15828 && idx.partial_predicate.is_none()
15829 && !idx.lookup_eq(&idx_key).is_empty()
15830 })
15831}
15832
15833fn lookup_row_position_by_keys(
15839 catalog: &Catalog,
15840 table_name: &str,
15841 column_positions: &[usize],
15842 key: &[&Value],
15843) -> Option<usize> {
15844 let table = catalog.get(table_name)?;
15845 table.rows().iter().position(|r| {
15846 column_positions
15847 .iter()
15848 .enumerate()
15849 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15850 })
15851}
15852
15853fn on_conflict_keys_exist(
15858 catalog: &Catalog,
15859 table_name: &str,
15860 column_positions: &[usize],
15861 key: &[&Value],
15862) -> bool {
15863 if column_positions.len() == 1 {
15864 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
15865 }
15866 let Some(table) = catalog.get(table_name) else {
15867 return false;
15868 };
15869 table.rows().iter().any(|r| {
15870 column_positions
15871 .iter()
15872 .enumerate()
15873 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15874 })
15875}
15876
15877fn apply_on_conflict_assignments(
15890 catalog: &Catalog,
15891 table_name: &str,
15892 target_pos: usize,
15893 incoming: &[Value],
15894 assignments: &[(String, Expr)],
15895 where_: Option<&Expr>,
15896) -> Result<Option<Vec<Value>>, EngineError> {
15897 let table = catalog.get(table_name).ok_or_else(|| {
15898 EngineError::Storage(StorageError::TableNotFound {
15899 name: table_name.into(),
15900 })
15901 })?;
15902 let schema_cols = table.schema().columns.clone();
15903 let existing = table
15904 .rows()
15905 .get(target_pos)
15906 .ok_or_else(|| {
15907 EngineError::Unsupported(alloc::format!(
15908 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
15909 ))
15910 })?
15911 .clone();
15912 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
15913 if let Some(w) = where_ {
15915 let pred = w.clone();
15916 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
15917 let v = eval::eval_expr(&pred, &existing, &ctx)?;
15918 if !matches!(v, Value::Bool(true)) {
15919 return Ok(None);
15920 }
15921 }
15922 let mut new_values = existing.values.clone();
15923 for (col_name, expr) in assignments {
15924 let target_idx = schema_cols
15925 .iter()
15926 .position(|c| c.name == *col_name)
15927 .ok_or_else(|| {
15928 EngineError::Eval(EvalError::ColumnNotFound {
15929 name: col_name.clone(),
15930 })
15931 })?;
15932 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
15933 let v = eval::eval_expr(&sub, &existing, &ctx)?;
15934 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
15935 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
15936 new_values[target_idx] = coerced;
15937 }
15938 Ok(Some(new_values))
15939}
15940
15941fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
15946 use spg_sql::ast::ColumnName;
15947 match expr {
15948 Expr::Column(ColumnName { qualifier, name })
15949 if qualifier
15950 .as_deref()
15951 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
15952 {
15953 let pos = schema_cols.iter().position(|c| c.name == name);
15954 match pos {
15955 Some(p) => {
15956 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
15957 value_to_literal_expr(v)
15958 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
15959 }
15960 None => Expr::Column(ColumnName { qualifier, name }),
15961 }
15962 }
15963 Expr::Binary { op, lhs, rhs } => Expr::Binary {
15964 op,
15965 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
15966 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
15967 },
15968 Expr::Unary { op, expr } => Expr::Unary {
15969 op,
15970 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
15971 },
15972 Expr::FunctionCall { name, args } => Expr::FunctionCall {
15973 name,
15974 args: args
15975 .into_iter()
15976 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
15977 .collect(),
15978 },
15979 other => other,
15980 }
15981}
15982
15983fn enforce_uniqueness_inserts(
16006 catalog: &Catalog,
16007 child_table: &str,
16008 constraints: &[spg_storage::UniquenessConstraint],
16009 rows: &[Vec<Value>],
16010) -> Result<(), EngineError> {
16011 if constraints.is_empty() {
16012 return Ok(());
16013 }
16014 let table = catalog.get(child_table).ok_or_else(|| {
16015 EngineError::Storage(StorageError::TableNotFound {
16016 name: child_table.into(),
16017 })
16018 })?;
16019 let schema = table.schema();
16020 for uc in constraints {
16021 for (batch_idx, row_values) in rows.iter().enumerate() {
16022 let key: Vec<Value> = uc
16031 .columns
16032 .iter()
16033 .map(|&i| collated_key_cell(&row_values[i], i, schema))
16034 .collect();
16035 let has_null = key.iter().any(|v| matches!(v, Value::Null));
16036 if has_null && !uc.nulls_not_distinct {
16041 continue;
16042 }
16043 let collides_in_table = table.rows().iter().any(|prow| {
16045 uc.columns.iter().enumerate().all(|(i, &p)| {
16046 prow.values
16047 .get(p)
16048 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
16049 })
16050 });
16051 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
16053 uc.columns.iter().enumerate().all(|(i, &p)| {
16054 earlier
16055 .get(p)
16056 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
16057 })
16058 });
16059 if collides_in_table || collides_in_batch {
16060 let kind = if uc.is_primary_key {
16061 "PRIMARY KEY"
16062 } else {
16063 "UNIQUE"
16064 };
16065 let col_names: Vec<String> = uc
16066 .columns
16067 .iter()
16068 .map(|&i| table.schema().columns[i].name.clone())
16069 .collect();
16070 return Err(EngineError::Unsupported(alloc::format!(
16071 "{kind} violation on {child_table:?} columns {col_names:?}: \
16072 row #{batch_idx} duplicates an existing key"
16073 )));
16074 }
16075 }
16076 }
16077 Ok(())
16078}
16079
16080fn collated_key_cell(
16087 v: &spg_storage::Value,
16088 column_position: usize,
16089 schema: &spg_storage::TableSchema,
16090) -> spg_storage::Value {
16091 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
16092 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
16093 spg_storage::Value::Text(s.to_ascii_lowercase())
16094 }
16095 _ => v.clone(),
16096 }
16097}
16098
16099fn predicate_truthy(v: &spg_storage::Value) -> bool {
16107 use spg_storage::Value as V;
16108 match v {
16109 V::Bool(b) => *b,
16110 V::Int(n) => *n != 0,
16111 V::BigInt(n) => *n != 0,
16112 V::SmallInt(n) => *n != 0,
16113 _ => false,
16114 }
16115}
16116
16117fn check_existing_unique_violation(
16122 idx: &spg_storage::Index,
16123 schema: &spg_storage::TableSchema,
16124 rows: &[spg_storage::Row],
16125) -> Result<(), EngineError> {
16126 let predicate_expr = match idx.partial_predicate.as_deref() {
16127 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16128 EngineError::Unsupported(alloc::format!(
16129 "stored partial predicate {s:?} failed to re-parse: {e:?}"
16130 ))
16131 })?),
16132 None => None,
16133 };
16134 let ctx = eval::EvalContext::new(&schema.columns, None);
16135 let key_positions = unique_key_positions(idx);
16136 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
16137 for row in rows {
16138 if let Some(expr) = &predicate_expr {
16139 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
16140 EngineError::Unsupported(alloc::format!(
16141 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
16142 ))
16143 })?;
16144 if !predicate_truthy(&v) {
16145 continue;
16146 }
16147 }
16148 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
16149 .iter()
16150 .map(|&p| {
16151 let v = row
16152 .values
16153 .get(p)
16154 .cloned()
16155 .unwrap_or(spg_storage::Value::Null);
16156 collated_key_cell(&v, p, schema)
16157 })
16158 .collect();
16159 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16160 continue;
16161 }
16162 if seen.iter().any(|other| *other == key) {
16163 return Err(EngineError::Unsupported(alloc::format!(
16164 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
16165 idx.name
16166 )));
16167 }
16168 seen.push(key);
16169 }
16170 Ok(())
16171}
16172
16173fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
16177 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
16178 out.push(idx.column_position);
16179 out.extend_from_slice(&idx.extra_column_positions);
16180 out
16181}
16182
16183fn enforce_unique_index_inserts(
16191 catalog: &Catalog,
16192 table_name: &str,
16193 rows: &[alloc::vec::Vec<spg_storage::Value>],
16194) -> Result<(), EngineError> {
16195 let table = catalog.get(table_name).ok_or_else(|| {
16196 EngineError::Storage(StorageError::TableNotFound {
16197 name: table_name.into(),
16198 })
16199 })?;
16200 let schema = table.schema();
16201 let ctx = eval::EvalContext::new(&schema.columns, None);
16202 for idx in table.indices() {
16203 if !idx.is_unique {
16204 continue;
16205 }
16206 let predicate_expr = match idx.partial_predicate.as_deref() {
16208 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
16209 EngineError::Unsupported(alloc::format!(
16210 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
16211 idx.name
16212 ))
16213 })?),
16214 None => None,
16215 };
16216 let key_positions = unique_key_positions(idx);
16217 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
16218 key_positions
16222 .iter()
16223 .map(|&p| {
16224 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
16225 collated_key_cell(&v, p, schema)
16226 })
16227 .collect()
16228 };
16229 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
16233 let Some(expr) = &predicate_expr else {
16234 return Ok(true);
16235 };
16236 let tmp_row = spg_storage::Row {
16237 values: values.to_vec(),
16238 };
16239 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
16240 EngineError::Unsupported(alloc::format!(
16241 "UNIQUE INDEX {:?} predicate eval: {e:?}",
16242 idx.name
16243 ))
16244 })?;
16245 Ok(predicate_truthy(&v))
16246 };
16247 for (batch_idx, row_values) in rows.iter().enumerate() {
16248 if !participates(row_values)? {
16249 continue;
16250 }
16251 let key = key_of(row_values);
16252 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
16253 continue;
16254 }
16255 for prow in table.rows() {
16257 if !participates(&prow.values)? {
16258 continue;
16259 }
16260 if key_of(&prow.values) == key {
16261 return Err(EngineError::Unsupported(alloc::format!(
16262 "UNIQUE INDEX {:?} violation on {table_name:?}: \
16263 row #{batch_idx} duplicates an existing key",
16264 idx.name
16265 )));
16266 }
16267 }
16268 for earlier in &rows[..batch_idx] {
16270 if !participates(earlier)? {
16271 continue;
16272 }
16273 if key_of(earlier) == key {
16274 return Err(EngineError::Unsupported(alloc::format!(
16275 "UNIQUE INDEX {:?} violation on {table_name:?}: \
16276 row #{batch_idx} duplicates an earlier row in the same batch",
16277 idx.name
16278 )));
16279 }
16280 }
16281 }
16282 }
16283 Ok(())
16284}
16285
16286fn any_column_changed(
16294 filter_cols: &[String],
16295 schema_cols: &[ColumnSchema],
16296 old_row: &Row,
16297 new_row: &Row,
16298) -> bool {
16299 for col_name in filter_cols {
16300 let Some(pos) = schema_cols
16301 .iter()
16302 .position(|c| c.name.eq_ignore_ascii_case(col_name))
16303 else {
16304 continue;
16305 };
16306 let old_v = old_row.values.get(pos);
16307 let new_v = new_row.values.get(pos);
16308 if old_v != new_v {
16309 return true;
16310 }
16311 }
16312 false
16313}
16314
16315fn enforce_check_constraints(
16320 catalog: &Catalog,
16321 table_name: &str,
16322 rows: &[alloc::vec::Vec<spg_storage::Value>],
16323) -> Result<(), EngineError> {
16324 let table = catalog.get(table_name).ok_or_else(|| {
16325 EngineError::Storage(StorageError::TableNotFound {
16326 name: table_name.into(),
16327 })
16328 })?;
16329 let schema = table.schema();
16330 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
16334 alloc::vec::Vec::new();
16335 for (idx, col) in schema.columns.iter().enumerate() {
16336 let Some(dname) = &col.user_domain_type else {
16337 continue;
16338 };
16339 let Some(dom) = catalog.domain_types().get(dname) else {
16340 continue;
16341 };
16342 let mut parsed_for_col: alloc::vec::Vec<Expr> =
16343 alloc::vec::Vec::with_capacity(dom.checks.len());
16344 for src in &dom.checks {
16345 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
16346 EngineError::Unsupported(alloc::format!(
16347 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
16348 col.name
16349 ))
16350 })?;
16351 parsed_for_col.push(expr);
16352 }
16353 if !parsed_for_col.is_empty() {
16354 domain_checks_per_col.push((idx, parsed_for_col));
16355 }
16356 }
16357 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
16358 return Ok(());
16359 }
16360 let ctx = eval::EvalContext::new(&schema.columns, None);
16361 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
16362 for (i, src) in schema.checks.iter().enumerate() {
16363 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
16364 EngineError::Unsupported(alloc::format!(
16365 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
16366 ))
16367 })?;
16368 parsed.push((i, expr));
16369 }
16370 for (batch_idx, row_values) in rows.iter().enumerate() {
16371 let tmp_row = spg_storage::Row {
16372 values: row_values.clone(),
16373 };
16374 for (i, expr) in &parsed {
16375 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
16376 EngineError::Unsupported(alloc::format!(
16377 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
16378 ))
16379 })?;
16380 if matches!(v, spg_storage::Value::Bool(false)) {
16382 return Err(EngineError::Unsupported(alloc::format!(
16383 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
16384 schema.checks[*i]
16385 )));
16386 }
16387 }
16388 for (col_idx, checks) in &domain_checks_per_col {
16394 let cell = row_values
16395 .get(*col_idx)
16396 .cloned()
16397 .unwrap_or(spg_storage::Value::Null);
16398 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
16399 "value",
16400 schema.columns[*col_idx].ty,
16401 schema.columns[*col_idx].nullable,
16402 )];
16403 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
16404 let synth_row = spg_storage::Row {
16405 values: alloc::vec![cell],
16406 };
16407 for (ci, expr) in checks.iter().enumerate() {
16408 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
16409 EngineError::Unsupported(alloc::format!(
16410 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
16411 schema.columns[*col_idx].name
16412 ))
16413 })?;
16414 if matches!(v, spg_storage::Value::Bool(false)) {
16415 return Err(EngineError::Unsupported(alloc::format!(
16416 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
16417 schema.columns[*col_idx].name
16418 )));
16419 }
16420 }
16421 }
16422 }
16423 Ok(())
16424}
16425
16426fn enforce_fk_inserts(
16427 catalog: &Catalog,
16428 child_table: &str,
16429 fks: &[spg_storage::ForeignKeyConstraint],
16430 rows: &[Vec<Value>],
16431) -> Result<(), EngineError> {
16432 for fk in fks {
16433 let parent_is_self = fk.parent_table == child_table;
16434 let parent = if parent_is_self {
16435 catalog.get(child_table).ok_or_else(|| {
16438 EngineError::Storage(StorageError::TableNotFound {
16439 name: child_table.into(),
16440 })
16441 })?
16442 } else {
16443 catalog.get(&fk.parent_table).ok_or_else(|| {
16444 EngineError::Storage(StorageError::TableNotFound {
16445 name: fk.parent_table.clone(),
16446 })
16447 })?
16448 };
16449 for (batch_idx, row_values) in rows.iter().enumerate() {
16450 if fk.local_columns.len() == 1 {
16454 let v = &row_values[fk.local_columns[0]];
16455 if matches!(v, Value::Null) {
16456 continue;
16457 }
16458 let parent_col = fk.parent_columns[0];
16459 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
16460 EngineError::Unsupported(alloc::format!(
16461 "FOREIGN KEY column value of type {:?} is not index-eligible",
16462 v.data_type()
16463 ))
16464 })?;
16465 let present_committed = parent.indices().iter().any(|idx| {
16466 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
16467 && idx.column_position == parent_col
16468 && idx.partial_predicate.is_none()
16469 && !idx.lookup_eq(&key).is_empty()
16470 });
16471 let present_in_batch = parent_is_self
16475 && rows[..batch_idx]
16476 .iter()
16477 .any(|earlier| earlier.get(parent_col) == Some(v));
16478 if !(present_committed || present_in_batch) {
16479 return Err(EngineError::Unsupported(alloc::format!(
16480 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
16481 fk.parent_table,
16482 parent
16483 .schema()
16484 .columns
16485 .get(parent_col)
16486 .map_or("?", |c| c.name.as_str()),
16487 v,
16488 )));
16489 }
16490 } else {
16491 if fk
16495 .local_columns
16496 .iter()
16497 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
16498 {
16499 continue;
16500 }
16501 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
16502 let parent_match_committed = parent.rows().iter().any(|prow| {
16503 fk.parent_columns
16504 .iter()
16505 .enumerate()
16506 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
16507 });
16508 let parent_match_in_batch = parent_is_self
16509 && rows[..batch_idx].iter().any(|earlier| {
16510 fk.parent_columns
16511 .iter()
16512 .enumerate()
16513 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
16514 });
16515 if !(parent_match_committed || parent_match_in_batch) {
16516 return Err(EngineError::Unsupported(alloc::format!(
16517 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
16518 fk.parent_table,
16519 )));
16520 }
16521 }
16522 }
16523 }
16524 Ok(())
16525}
16526
16527#[derive(Debug, Clone)]
16531struct FkChildStep {
16532 child_table: String,
16533 action: FkChildAction,
16534}
16535
16536#[derive(Debug, Clone)]
16537enum FkChildAction {
16538 Delete { positions: Vec<usize> },
16540 SetNull {
16544 positions: Vec<usize>,
16545 columns: Vec<usize>,
16546 },
16547 SetDefault {
16551 positions: Vec<usize>,
16552 columns: Vec<usize>,
16553 defaults: Vec<Value>,
16554 },
16555}
16556
16557fn plan_fk_parent_deletions(
16573 catalog: &Catalog,
16574 parent_table_name: &str,
16575 to_delete_positions: &[usize],
16576 to_delete_rows: &[Vec<Value>],
16577) -> Result<Vec<FkChildStep>, EngineError> {
16578 use alloc::collections::{BTreeMap, BTreeSet};
16579 if to_delete_rows.is_empty() {
16580 return Ok(Vec::new());
16581 }
16582 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
16583 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
16585 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
16586 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
16587 for &p in to_delete_positions {
16588 visited.insert((parent_table_name.to_string(), p));
16589 }
16590 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
16591 .iter()
16592 .map(|r| (parent_table_name.to_string(), r.clone()))
16593 .collect();
16594 while let Some((cur_parent, parent_row)) = work.pop() {
16595 for child_name in catalog.table_names() {
16596 let child = catalog
16597 .get(&child_name)
16598 .expect("table_names → catalog.get round-trip is total");
16599 for fk in &child.schema().foreign_keys {
16600 if fk.parent_table != cur_parent {
16601 continue;
16602 }
16603 let parent_key: Vec<&Value> = fk
16604 .parent_columns
16605 .iter()
16606 .map(|&pi| &parent_row[pi])
16607 .collect();
16608 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
16609 continue;
16610 }
16611 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
16612 if child_name == cur_parent
16613 && visited.contains(&(child_name.clone(), child_row_idx))
16614 {
16615 continue;
16616 }
16617 let matches_key = fk
16618 .local_columns
16619 .iter()
16620 .enumerate()
16621 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
16622 if !matches_key {
16623 continue;
16624 }
16625 match fk.on_delete {
16626 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
16627 return Err(EngineError::Unsupported(alloc::format!(
16628 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
16629 restricted by FK from {child_name:?}.{:?}",
16630 fk.local_columns,
16631 )));
16632 }
16633 spg_storage::FkAction::Cascade => {
16634 if visited.insert((child_name.clone(), child_row_idx)) {
16635 delete_plan
16636 .entry(child_name.clone())
16637 .or_default()
16638 .insert(child_row_idx);
16639 work.push((child_name.clone(), child_row.values.clone()));
16640 }
16641 }
16642 spg_storage::FkAction::SetNull => {
16643 for &li in &fk.local_columns {
16645 let col = child.schema().columns.get(li).ok_or_else(|| {
16646 EngineError::Unsupported(alloc::format!(
16647 "FK local column {li} missing in {child_name:?}"
16648 ))
16649 })?;
16650 if !col.nullable {
16651 return Err(EngineError::Unsupported(alloc::format!(
16652 "FOREIGN KEY ON DELETE SET NULL: column \
16653 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
16654 col.name,
16655 )));
16656 }
16657 }
16658 let entry = setnull_plan.entry(child_name.clone()).or_default();
16659 for &li in &fk.local_columns {
16660 entry.insert((child_row_idx, li));
16661 }
16662 }
16663 spg_storage::FkAction::SetDefault => {
16664 let entry = setdefault_plan.entry(child_name.clone()).or_default();
16666 for &li in &fk.local_columns {
16667 let col = child.schema().columns.get(li).ok_or_else(|| {
16668 EngineError::Unsupported(alloc::format!(
16669 "FK local column {li} missing in {child_name:?}"
16670 ))
16671 })?;
16672 let default = col.default.clone().ok_or_else(|| {
16673 EngineError::Unsupported(alloc::format!(
16674 "FOREIGN KEY ON DELETE SET DEFAULT: column \
16675 {child_name:?}.{:?} has no DEFAULT declared",
16676 col.name,
16677 ))
16678 })?;
16679 entry.insert((child_row_idx, li), default);
16680 }
16681 }
16682 }
16683 }
16684 }
16685 }
16686 }
16687 let mut steps: Vec<FkChildStep> = Vec::new();
16695 for (child_table, entries) in setnull_plan {
16696 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16697 steps.push(FkChildStep {
16698 child_table,
16699 action: FkChildAction::SetNull { positions, columns },
16700 });
16701 }
16702 for (child_table, entries) in setdefault_plan {
16703 let mut positions = Vec::with_capacity(entries.len());
16704 let mut columns = Vec::with_capacity(entries.len());
16705 let mut defaults = Vec::with_capacity(entries.len());
16706 for ((p, c), v) in entries {
16707 positions.push(p);
16708 columns.push(c);
16709 defaults.push(v);
16710 }
16711 steps.push(FkChildStep {
16712 child_table,
16713 action: FkChildAction::SetDefault {
16714 positions,
16715 columns,
16716 defaults,
16717 },
16718 });
16719 }
16720 for (child_table, positions) in delete_plan {
16721 steps.push(FkChildStep {
16722 child_table,
16723 action: FkChildAction::Delete {
16724 positions: positions.into_iter().collect(),
16725 },
16726 });
16727 }
16728 Ok(steps)
16729}
16730
16731fn plan_fk_parent_updates(
16748 catalog: &Catalog,
16749 parent_table_name: &str,
16750 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
16751) -> Result<Vec<FkChildStep>, EngineError> {
16752 use alloc::collections::BTreeMap;
16753 if plan_with_old.is_empty() {
16754 return Ok(Vec::new());
16755 }
16756 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
16761 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
16762 BTreeMap::new();
16763 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
16764 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
16766
16767 for child_name in catalog.table_names() {
16768 let child = catalog
16769 .get(&child_name)
16770 .expect("table_names → catalog.get total");
16771 for fk in &child.schema().foreign_keys {
16772 if fk.parent_table != parent_table_name {
16773 continue;
16774 }
16775 for (_pos, old_row, new_row) in plan_with_old {
16776 let key_changed = fk
16778 .parent_columns
16779 .iter()
16780 .any(|&pi| old_row.get(pi) != new_row.get(pi));
16781 if !key_changed {
16782 continue;
16783 }
16784 let old_key: Vec<&Value> =
16786 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
16787 if old_key.iter().any(|v| matches!(v, Value::Null)) {
16788 continue;
16790 }
16791 let new_key: Vec<&Value> =
16792 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
16793 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
16794 if child_name == parent_table_name
16797 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
16798 {
16799 continue;
16800 }
16801 let matches_key = fk
16802 .local_columns
16803 .iter()
16804 .enumerate()
16805 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
16806 if !matches_key {
16807 continue;
16808 }
16809 match fk.on_update {
16810 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
16811 return Err(EngineError::Unsupported(alloc::format!(
16812 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
16813 restricted by FK from {child_name:?}.{:?}",
16814 fk.local_columns,
16815 )));
16816 }
16817 spg_storage::FkAction::Cascade => {
16818 let entry = cascade_plan.entry(child_name.clone()).or_default();
16820 for (i, &li) in fk.local_columns.iter().enumerate() {
16821 entry.insert((child_row_idx, li), new_key[i].clone());
16822 }
16823 }
16824 spg_storage::FkAction::SetNull => {
16825 for &li in &fk.local_columns {
16826 let col = child.schema().columns.get(li).ok_or_else(|| {
16827 EngineError::Unsupported(alloc::format!(
16828 "FK local column {li} missing in {child_name:?}"
16829 ))
16830 })?;
16831 if !col.nullable {
16832 return Err(EngineError::Unsupported(alloc::format!(
16833 "FOREIGN KEY ON UPDATE SET NULL: column \
16834 {child_name:?}.{:?} is NOT NULL",
16835 col.name,
16836 )));
16837 }
16838 }
16839 let entry = setnull_plan.entry(child_name.clone()).or_default();
16840 for &li in &fk.local_columns {
16841 entry.insert((child_row_idx, li));
16842 }
16843 }
16844 spg_storage::FkAction::SetDefault => {
16845 let entry = setdefault_plan.entry(child_name.clone()).or_default();
16846 for &li in &fk.local_columns {
16847 let col = child.schema().columns.get(li).ok_or_else(|| {
16848 EngineError::Unsupported(alloc::format!(
16849 "FK local column {li} missing in {child_name:?}"
16850 ))
16851 })?;
16852 let default = col.default.clone().ok_or_else(|| {
16853 EngineError::Unsupported(alloc::format!(
16854 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
16855 {child_name:?}.{:?} has no DEFAULT",
16856 col.name,
16857 ))
16858 })?;
16859 entry.insert((child_row_idx, li), default);
16860 }
16861 }
16862 }
16863 }
16864 }
16865 }
16866 }
16867 let mut steps: Vec<FkChildStep> = Vec::new();
16870 for (child_table, entries) in cascade_plan {
16871 let mut positions = Vec::with_capacity(entries.len());
16872 let mut columns = Vec::with_capacity(entries.len());
16873 let mut defaults = Vec::with_capacity(entries.len());
16874 for ((p, c), v) in entries {
16875 positions.push(p);
16876 columns.push(c);
16877 defaults.push(v);
16878 }
16879 steps.push(FkChildStep {
16884 child_table,
16885 action: FkChildAction::SetDefault {
16886 positions,
16887 columns,
16888 defaults,
16889 },
16890 });
16891 }
16892 for (child_table, entries) in setnull_plan {
16893 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16894 steps.push(FkChildStep {
16895 child_table,
16896 action: FkChildAction::SetNull { positions, columns },
16897 });
16898 }
16899 for (child_table, entries) in setdefault_plan {
16900 let mut positions = Vec::with_capacity(entries.len());
16901 let mut columns = Vec::with_capacity(entries.len());
16902 let mut defaults = Vec::with_capacity(entries.len());
16903 for ((p, c), v) in entries {
16904 positions.push(p);
16905 columns.push(c);
16906 defaults.push(v);
16907 }
16908 steps.push(FkChildStep {
16909 child_table,
16910 action: FkChildAction::SetDefault {
16911 positions,
16912 columns,
16913 defaults,
16914 },
16915 });
16916 }
16917 let _ = delete_plan; Ok(steps)
16919}
16920
16921fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
16925 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
16926 EngineError::Storage(StorageError::TableNotFound {
16927 name: step.child_table.clone(),
16928 })
16929 })?;
16930 match &step.action {
16931 FkChildAction::Delete { positions } => {
16932 let _ = child.delete_rows(positions);
16933 }
16934 FkChildAction::SetNull { positions, columns } => {
16935 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
16936 }
16937 FkChildAction::SetDefault {
16938 positions,
16939 columns,
16940 defaults,
16941 } => {
16942 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
16943 }
16944 }
16945 Ok(())
16946}
16947
16948fn apply_per_cell_writes(
16954 child: &mut spg_storage::Table,
16955 positions: &[usize],
16956 columns: &[usize],
16957 mut value_for: impl FnMut(usize) -> Value,
16958) -> Result<(), EngineError> {
16959 use alloc::collections::BTreeMap;
16960 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
16961 for i in 0..positions.len() {
16962 by_row
16963 .entry(positions[i])
16964 .or_default()
16965 .push((columns[i], value_for(i)));
16966 }
16967 for (pos, mutations) in by_row {
16968 let mut new_values = child.rows()[pos].values.clone();
16969 for (col, v) in mutations {
16970 if let Some(slot) = new_values.get_mut(col) {
16971 *slot = v;
16972 }
16973 }
16974 child
16975 .update_row(pos, new_values)
16976 .map_err(EngineError::Storage)?;
16977 }
16978 Ok(())
16979}
16980
16981fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
16982 match a {
16983 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
16984 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
16985 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
16986 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
16987 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
16988 }
16989}
16990
16991fn resolve_column_default_free(
16997 col: &ColumnSchema,
16998 clock_fn: Option<ClockFn>,
16999) -> Result<Value, EngineError> {
17000 if let Some(rt) = &col.runtime_default {
17001 return eval_runtime_default_free(rt, col.ty, clock_fn);
17002 }
17003 Ok(col.default.clone().unwrap_or(Value::Null))
17004}
17005
17006fn eval_runtime_default_free(
17007 rt: &str,
17008 ty: DataType,
17009 clock_fn: Option<ClockFn>,
17010) -> Result<Value, EngineError> {
17011 let s = rt.trim().to_ascii_lowercase();
17012 let with_no_parens = s.trim_end_matches("()");
17018 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
17019 if with_no_parens.ends_with(')') {
17020 &with_no_parens[..open_idx]
17021 } else {
17022 with_no_parens
17023 }
17024 } else {
17025 with_no_parens
17026 };
17027 let now_us = match clock_fn {
17028 Some(f) => f(),
17029 None => 0,
17030 };
17031 let v = match canonical {
17032 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
17033 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
17034 "current_time" | "localtime" => Value::Timestamp(now_us),
17035 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
17041 other => {
17042 return Err(EngineError::Unsupported(alloc::format!(
17043 "runtime DEFAULT expression {other:?} not supported \
17044 (v7.17.0 whitelist: now() / current_timestamp / \
17045 current_date / current_time / localtimestamp / \
17046 localtime / gen_random_uuid() / \
17047 uuid_generate_v4())"
17048 )));
17049 }
17050 };
17051 coerce_value(v, ty, "DEFAULT", 0)
17052}
17053
17054fn is_runtime_default_expr(expr: &Expr) -> bool {
17060 match expr {
17061 Expr::FunctionCall { .. } => true,
17062 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
17063 _ => false,
17064 }
17065}
17066
17067fn canonicalize_set_value(
17080 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17081 col_idx: usize,
17082 col_name: &str,
17083 value: Value,
17084) -> Result<Value, EngineError> {
17085 let Some(variants) = lookup.get(&col_idx) else {
17086 return Ok(value);
17087 };
17088 match value {
17089 Value::Null => Ok(Value::Null),
17090 Value::Text(s) => {
17091 if s.is_empty() {
17092 return Ok(Value::Text(alloc::string::String::new()));
17093 }
17094 let mut present = alloc::vec![false; variants.len()];
17097 for raw in s.split(',') {
17098 let tok = raw.trim();
17099 if tok.is_empty() {
17100 continue;
17101 }
17102 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
17103 EngineError::Unsupported(alloc::format!(
17104 "column {col_name:?}: invalid SET token {tok:?}; \
17105 allowed: {variants:?}"
17106 ))
17107 })?;
17108 present[idx] = true;
17109 }
17110 let mut out = alloc::string::String::new();
17112 let mut first = true;
17113 for (i, keep) in present.iter().enumerate() {
17114 if !keep {
17115 continue;
17116 }
17117 if !first {
17118 out.push(',');
17119 }
17120 first = false;
17121 out.push_str(&variants[i]);
17122 }
17123 Ok(Value::Text(out))
17124 }
17125 other => Err(EngineError::Unsupported(alloc::format!(
17126 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
17127 other.data_type()
17128 ))),
17129 }
17130}
17131
17132fn enforce_enum_label(
17133 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
17134 col_idx: usize,
17135 col_name: &str,
17136 value: &Value,
17137) -> Result<(), EngineError> {
17138 if let Some(labels) = lookup.get(&col_idx) {
17139 match value {
17140 Value::Null => Ok(()),
17141 Value::Text(s) => {
17142 if labels.iter().any(|l| l == s) {
17143 Ok(())
17144 } else {
17145 Err(EngineError::Unsupported(alloc::format!(
17146 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
17147 )))
17148 }
17149 }
17150 other => Err(EngineError::Unsupported(alloc::format!(
17151 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
17152 other.data_type()
17153 ))),
17154 }
17155 } else {
17156 Ok(())
17157 }
17158}
17159
17160fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
17161 let ty = column_type_to_data_type(c.ty);
17162 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
17163 if let Some(name) = c.user_type_ref {
17170 schema.user_enum_type = Some(name);
17171 }
17172 if let Some(expr) = c.on_update_runtime {
17175 schema.on_update_runtime = Some(alloc::format!("{expr}"));
17176 }
17177 schema.collation = match c.collation {
17181 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
17182 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
17183 };
17184 schema.is_unsigned = c.is_unsigned;
17187 schema.inline_enum_variants = c.inline_enum_variants;
17191 schema.inline_set_variants = c.inline_set_variants;
17195 if let Some(default_expr) = c.default {
17196 if is_runtime_default_expr(&default_expr) {
17202 let display = alloc::format!("{default_expr}");
17203 schema = schema.with_runtime_default(display);
17204 } else {
17205 let raw = literal_expr_to_value(default_expr)?;
17206 let coerced = coerce_value(raw, ty, &c.name, 0)?;
17207 schema = schema.with_default(coerced);
17208 }
17209 }
17210 if c.auto_increment {
17211 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
17213 return Err(EngineError::Unsupported(alloc::format!(
17214 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
17215 )));
17216 }
17217 schema = schema.with_auto_increment();
17218 }
17219 Ok(schema)
17220}
17221
17222fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
17227 let s = s.trim();
17228 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
17229 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
17231 if cleaned.len() % 2 != 0 {
17232 return Err("odd-length hex literal");
17233 }
17234 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
17235 let cleaned_bytes = cleaned.as_bytes();
17236 for i in (0..cleaned_bytes.len()).step_by(2) {
17237 let hi = hex_nibble(cleaned_bytes[i])?;
17238 let lo = hex_nibble(cleaned_bytes[i + 1])?;
17239 out.push((hi << 4) | lo);
17240 }
17241 return Ok(out);
17242 }
17243 let bytes = s.as_bytes();
17246 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
17247 let mut i = 0;
17248 while i < bytes.len() {
17249 let b = bytes[i];
17250 if b == b'\\' && i + 1 < bytes.len() {
17251 let n = bytes[i + 1];
17252 if n == b'\\' {
17253 out.push(b'\\');
17254 i += 2;
17255 continue;
17256 }
17257 if n.is_ascii_digit()
17258 && i + 3 < bytes.len()
17259 && bytes[i + 2].is_ascii_digit()
17260 && bytes[i + 3].is_ascii_digit()
17261 {
17262 let oct = |x: u8| (x - b'0') as u32;
17263 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
17264 if v <= 0xFF {
17265 out.push(v as u8);
17266 i += 4;
17267 continue;
17268 }
17269 }
17270 }
17271 out.push(b);
17272 i += 1;
17273 }
17274 Ok(out)
17275}
17276
17277fn hex_nibble(b: u8) -> Result<u8, &'static str> {
17278 match b {
17279 b'0'..=b'9' => Ok(b - b'0'),
17280 b'a'..=b'f' => Ok(b - b'a' + 10),
17281 b'A'..=b'F' => Ok(b - b'A' + 10),
17282 _ => Err("invalid hex digit"),
17283 }
17284}
17285
17286fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
17300 let mut has_text = false;
17301 let mut has_bigint = false;
17302 let mut has_int = false;
17303 for v in &items {
17304 match v {
17305 Value::Null => {}
17306 Value::Text(_) | Value::Json(_) => has_text = true,
17307 Value::BigInt(_) => has_bigint = true,
17308 Value::Int(_) | Value::SmallInt(_) => has_int = true,
17309 _ => has_text = true,
17310 }
17311 }
17312 if has_text || (!has_bigint && !has_int) {
17313 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
17314 .into_iter()
17315 .map(|v| match v {
17316 Value::Null => None,
17317 Value::Text(s) | Value::Json(s) => Some(s),
17318 other => Some(alloc::format!("{other:?}")),
17319 })
17320 .collect();
17321 return Value::TextArray(out);
17322 }
17323 if has_bigint {
17324 let out: alloc::vec::Vec<Option<i64>> = items
17325 .into_iter()
17326 .map(|v| match v {
17327 Value::Null => None,
17328 Value::Int(n) => Some(i64::from(n)),
17329 Value::SmallInt(n) => Some(i64::from(n)),
17330 Value::BigInt(n) => Some(n),
17331 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
17332 })
17333 .collect();
17334 return Value::BigIntArray(out);
17335 }
17336 let out: alloc::vec::Vec<Option<i32>> = items
17337 .into_iter()
17338 .map(|v| match v {
17339 Value::Null => None,
17340 Value::Int(n) => Some(n),
17341 Value::SmallInt(n) => Some(i32::from(n)),
17342 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
17343 })
17344 .collect();
17345 Value::IntArray(out)
17346}
17347
17348fn decode_text_array_literal(
17349 s: &str,
17350) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
17351 let trimmed = s.trim();
17352 let inner = trimmed
17353 .strip_prefix('{')
17354 .and_then(|x| x.strip_suffix('}'))
17355 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
17356 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
17357 if inner.trim().is_empty() {
17358 return Ok(out);
17359 }
17360 let bytes = inner.as_bytes();
17361 let mut i = 0;
17362 while i <= bytes.len() {
17363 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
17365 i += 1;
17366 }
17367 if i < bytes.len() && bytes[i] == b'"' {
17369 i += 1; let mut buf = alloc::string::String::new();
17371 while i < bytes.len() && bytes[i] != b'"' {
17372 if bytes[i] == b'\\' && i + 1 < bytes.len() {
17373 buf.push(bytes[i + 1] as char);
17374 i += 2;
17375 } else {
17376 buf.push(bytes[i] as char);
17377 i += 1;
17378 }
17379 }
17380 if i >= bytes.len() {
17381 return Err("unterminated quoted element");
17382 }
17383 i += 1; out.push(Some(buf));
17385 } else {
17386 let start = i;
17388 while i < bytes.len() && bytes[i] != b',' {
17389 i += 1;
17390 }
17391 let raw = inner[start..i].trim();
17392 if raw.eq_ignore_ascii_case("NULL") {
17393 out.push(None);
17394 } else {
17395 out.push(Some(alloc::string::ToString::to_string(raw)));
17396 }
17397 }
17398 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
17400 i += 1;
17401 }
17402 if i >= bytes.len() {
17403 break;
17404 }
17405 if bytes[i] != b',' {
17406 return Err("expected ',' between TEXT[] elements");
17407 }
17408 i += 1;
17409 }
17410 Ok(out)
17411}
17412
17413fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
17418 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
17419 out.push('{');
17420 for (i, item) in items.iter().enumerate() {
17421 if i > 0 {
17422 out.push(',');
17423 }
17424 match item {
17425 None => out.push_str("NULL"),
17426 Some(s) => {
17427 let needs_quote = s.is_empty()
17428 || s.eq_ignore_ascii_case("NULL")
17429 || s.chars()
17430 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
17431 if needs_quote {
17432 out.push('"');
17433 for c in s.chars() {
17434 if c == '"' || c == '\\' {
17435 out.push('\\');
17436 }
17437 out.push(c);
17438 }
17439 out.push('"');
17440 } else {
17441 out.push_str(s);
17442 }
17443 }
17444 }
17445 }
17446 out.push('}');
17447 out
17448}
17449
17450fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
17454 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
17455 out.push_str("\\x");
17456 for byte in b {
17457 let hi = byte >> 4;
17458 let lo = byte & 0x0F;
17459 out.push(hex_digit(hi));
17460 out.push(hex_digit(lo));
17461 }
17462 out
17463}
17464
17465const fn hex_digit(n: u8) -> char {
17466 match n {
17467 0..=9 => (b'0' + n) as char,
17468 10..=15 => (b'a' + n - 10) as char,
17469 _ => '?',
17470 }
17471}
17472
17473fn parse_hstore_str(
17485 s: &str,
17486) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
17487 let bytes = s.as_bytes();
17488 let mut i = 0;
17489 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
17490 let skip_ws = |bytes: &[u8], i: &mut usize| {
17491 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
17492 *i += 1;
17493 }
17494 };
17495 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
17496 if *i >= bytes.len() {
17497 return None;
17498 }
17499 if bytes[*i] == b'"' {
17500 *i += 1;
17501 let mut out = alloc::string::String::new();
17502 while *i < bytes.len() {
17503 match bytes[*i] {
17504 b'"' => {
17505 *i += 1;
17506 return Some(out);
17507 }
17508 b'\\' if *i + 1 < bytes.len() => {
17509 out.push(bytes[*i + 1] as char);
17510 *i += 2;
17511 }
17512 c => {
17513 out.push(c as char);
17514 *i += 1;
17515 }
17516 }
17517 }
17518 None
17519 } else {
17520 let start = *i;
17521 while *i < bytes.len()
17522 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
17523 {
17524 *i += 1;
17525 }
17526 if *i == start {
17527 return None;
17528 }
17529 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
17530 }
17531 };
17532 skip_ws(bytes, &mut i);
17533 while i < bytes.len() {
17534 let key = parse_token(bytes, &mut i)?;
17535 skip_ws(bytes, &mut i);
17536 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
17537 return None;
17538 }
17539 i += 2;
17540 skip_ws(bytes, &mut i);
17541 let val_token = if i + 4 <= bytes.len()
17543 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
17544 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
17545 {
17546 i += 4;
17547 None
17548 } else {
17549 Some(parse_token(bytes, &mut i)?)
17550 };
17551 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
17553 out[pos] = (key, val_token);
17554 } else {
17555 out.push((key, val_token));
17556 }
17557 skip_ws(bytes, &mut i);
17558 if i >= bytes.len() {
17559 break;
17560 }
17561 if bytes[i] == b',' {
17562 i += 1;
17563 skip_ws(bytes, &mut i);
17564 continue;
17565 }
17566 return None;
17567 }
17568 Some(out)
17569}
17570
17571fn format_hstore_str(
17575 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
17576) -> alloc::string::String {
17577 let mut out = alloc::string::String::new();
17578 for (i, (k, v)) in pairs.iter().enumerate() {
17579 if i > 0 {
17580 out.push_str(", ");
17581 }
17582 out.push('"');
17583 out.push_str(k);
17584 out.push_str("\"=>");
17585 match v {
17586 None => out.push_str("NULL"),
17587 Some(val) => {
17588 out.push('"');
17589 out.push_str(val);
17590 out.push('"');
17591 }
17592 }
17593 }
17594 out
17595}
17596
17597pub fn format_hstore_text(
17600 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
17601) -> alloc::string::String {
17602 format_hstore_str(pairs)
17603}
17604
17605fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
17610 let s = s.trim();
17611 let outer = s
17612 .strip_prefix('{')
17613 .and_then(|x| x.strip_suffix('}'))
17614 .ok_or("missing outer '{...}' braces")?;
17615 let trimmed = outer.trim();
17616 if trimmed.is_empty() {
17617 return Ok(Vec::new());
17618 }
17619 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
17620 let mut i = 0;
17621 let bytes = trimmed.as_bytes();
17622 while i < bytes.len() {
17623 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
17624 i += 1;
17625 }
17626 if i >= bytes.len() {
17627 break;
17628 }
17629 if bytes[i] != b'{' {
17630 return Err("expected '{' opening a row");
17631 }
17632 i += 1;
17633 let row_start = i;
17634 let mut depth = 1;
17635 while i < bytes.len() && depth > 0 {
17636 match bytes[i] {
17637 b'{' => depth += 1,
17638 b'}' => depth -= 1,
17639 _ => {}
17640 }
17641 if depth > 0 {
17642 i += 1;
17643 }
17644 }
17645 if depth != 0 {
17646 return Err("unbalanced '{...}' in row");
17647 }
17648 let row_text = &trimmed[row_start..i];
17649 i += 1;
17650 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
17651 Vec::new()
17652 } else {
17653 row_text.split(',').map(|t| t.trim().to_string()).collect()
17654 };
17655 rows.push(cells);
17656 }
17657 if let Some(first) = rows.first() {
17658 let cols = first.len();
17659 for r in &rows {
17660 if r.len() != cols {
17661 return Err("ragged 2D array (rows have different column counts)");
17662 }
17663 }
17664 }
17665 Ok(rows)
17666}
17667
17668fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
17669 let raw = split_2d_literal(s)?;
17670 raw.into_iter()
17671 .map(|row| {
17672 row.into_iter()
17673 .map(|cell| {
17674 if cell.eq_ignore_ascii_case("NULL") {
17675 Ok(None)
17676 } else {
17677 cell.parse::<i32>()
17678 .map(Some)
17679 .map_err(|_| "invalid int element")
17680 }
17681 })
17682 .collect()
17683 })
17684 .collect()
17685}
17686
17687fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
17688 let raw = split_2d_literal(s)?;
17689 raw.into_iter()
17690 .map(|row| {
17691 row.into_iter()
17692 .map(|cell| {
17693 if cell.eq_ignore_ascii_case("NULL") {
17694 Ok(None)
17695 } else {
17696 cell.parse::<i64>()
17697 .map(Some)
17698 .map_err(|_| "invalid bigint element")
17699 }
17700 })
17701 .collect()
17702 })
17703 .collect()
17704}
17705
17706fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
17707 let raw = split_2d_literal(s)?;
17708 Ok(raw
17709 .into_iter()
17710 .map(|row| {
17711 row.into_iter()
17712 .map(|cell| {
17713 if cell.eq_ignore_ascii_case("NULL") {
17714 None
17715 } else {
17716 Some(cell.trim_matches('"').to_string())
17717 }
17718 })
17719 .collect()
17720 })
17721 .collect())
17722}
17723
17724fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
17725 let mut out = alloc::string::String::from("{");
17726 for (i, row) in rows.iter().enumerate() {
17727 if i > 0 {
17728 out.push(',');
17729 }
17730 out.push('{');
17731 for (j, cell) in row.iter().enumerate() {
17732 if j > 0 {
17733 out.push(',');
17734 }
17735 match cell {
17736 None => out.push_str("NULL"),
17737 Some(n) => out.push_str(&alloc::format!("{n}")),
17738 }
17739 }
17740 out.push('}');
17741 }
17742 out.push('}');
17743 out
17744}
17745
17746fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
17747 let mut out = alloc::string::String::from("{");
17748 for (i, row) in rows.iter().enumerate() {
17749 if i > 0 {
17750 out.push(',');
17751 }
17752 out.push('{');
17753 for (j, cell) in row.iter().enumerate() {
17754 if j > 0 {
17755 out.push(',');
17756 }
17757 match cell {
17758 None => out.push_str("NULL"),
17759 Some(n) => out.push_str(&alloc::format!("{n}")),
17760 }
17761 }
17762 out.push('}');
17763 }
17764 out.push('}');
17765 out
17766}
17767
17768fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
17769 let mut out = alloc::string::String::from("{");
17770 for (i, row) in rows.iter().enumerate() {
17771 if i > 0 {
17772 out.push(',');
17773 }
17774 out.push('{');
17775 for (j, cell) in row.iter().enumerate() {
17776 if j > 0 {
17777 out.push(',');
17778 }
17779 match cell {
17780 None => out.push_str("NULL"),
17781 Some(s) => out.push_str(s),
17782 }
17783 }
17784 out.push('}');
17785 }
17786 out.push('}');
17787 out
17788}
17789
17790pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
17793 format_int_2d_text(rows)
17794}
17795pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
17796 format_bigint_2d_text(rows)
17797}
17798pub fn format_text_2d_text_pub(
17799 rows: &[Vec<Option<alloc::string::String>>],
17800) -> alloc::string::String {
17801 format_text_2d_text(rows)
17802}
17803
17804fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17809 let s = s.trim();
17810 if s.eq_ignore_ascii_case("empty") {
17811 return Some(Value::Range {
17812 kind,
17813 lower: None,
17814 upper: None,
17815 lower_inc: false,
17816 upper_inc: false,
17817 empty: true,
17818 });
17819 }
17820 let bytes = s.as_bytes();
17821 if bytes.len() < 3 {
17822 return None;
17823 }
17824 let lower_inc = match bytes[0] {
17825 b'[' => true,
17826 b'(' => false,
17827 _ => return None,
17828 };
17829 let upper_inc = match bytes[bytes.len() - 1] {
17830 b']' => true,
17831 b')' => false,
17832 _ => return None,
17833 };
17834 let inner = &s[1..s.len() - 1];
17835 let (lo_text, up_text) = inner.split_once(',')?;
17836 let lower = if lo_text.is_empty() {
17837 None
17838 } else {
17839 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
17840 };
17841 let upper = if up_text.is_empty() {
17842 None
17843 } else {
17844 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
17845 };
17846 Some(Value::Range {
17847 kind,
17848 lower,
17849 upper,
17850 lower_inc,
17851 upper_inc,
17852 empty: false,
17853 })
17854}
17855
17856fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17859 let text = text.trim().trim_matches('"');
17860 use spg_storage::RangeKind as K;
17861 match kind {
17862 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
17863 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
17864 K::Num => {
17865 let dot = text.find('.');
17868 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
17869 let digits: alloc::string::String = text
17870 .chars()
17871 .filter(|c| *c == '-' || c.is_ascii_digit())
17872 .collect();
17873 let scaled: i128 = digits.parse().ok()?;
17874 Some(Value::Numeric { scaled, scale })
17875 }
17876 K::Ts | K::TsTz => {
17877 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
17882 }
17883 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
17884 }
17885}
17886
17887pub fn format_range_text(v: &Value) -> alloc::string::String {
17891 format_range_str(v)
17892}
17893
17894fn format_range_str(v: &Value) -> alloc::string::String {
17895 let Value::Range {
17896 lower,
17897 upper,
17898 lower_inc,
17899 upper_inc,
17900 empty,
17901 ..
17902 } = v
17903 else {
17904 return alloc::string::String::new();
17905 };
17906 if *empty {
17907 return "empty".into();
17908 }
17909 let mut out = alloc::string::String::new();
17910 out.push(if *lower_inc { '[' } else { '(' });
17911 if let Some(l) = lower {
17912 out.push_str(&format_range_element(l));
17913 }
17914 out.push(',');
17915 if let Some(u) = upper {
17916 out.push_str(&format_range_element(u));
17917 }
17918 out.push(if *upper_inc { ']' } else { ')' });
17919 out
17920}
17921
17922fn format_range_element(v: &Value) -> alloc::string::String {
17923 match v {
17924 Value::Int(n) => alloc::format!("{n}"),
17925 Value::BigInt(n) => alloc::format!("{n}"),
17926 Value::Date(d) => crate::eval::format_date(*d),
17927 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
17928 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
17929 other => alloc::format!("{other:?}"),
17930 }
17931}
17932
17933fn parse_money_str(s: &str) -> Option<i64> {
17944 let s = s.trim();
17945 let (neg, rest) = match s.strip_prefix('-') {
17946 Some(r) => (true, r.trim_start()),
17947 None => (false, s),
17948 };
17949 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
17950 let (int_part, frac_part) = match rest.split_once('.') {
17951 Some((i, f)) => (i, Some(f)),
17952 None => (rest, None),
17953 };
17954 if int_part.is_empty() {
17955 return None;
17956 }
17957 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
17959 for b in int_part.bytes() {
17960 match b {
17961 b',' => {}
17962 b'0'..=b'9' => int_digits.push(b as char),
17963 _ => return None,
17964 }
17965 }
17966 if int_digits.is_empty() {
17967 return None;
17968 }
17969 let dollars: i64 = int_digits.parse().ok()?;
17970 let cents: i64 = match frac_part {
17971 None => 0,
17972 Some(f) => {
17973 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
17974 return None;
17975 }
17976 let padded = if f.len() == 1 {
17977 alloc::format!("{f}0")
17978 } else {
17979 f.to_string()
17980 };
17981 padded.parse().ok()?
17982 }
17983 };
17984 let total = dollars.checked_mul(100)?.checked_add(cents)?;
17985 Some(if neg { -total } else { total })
17986}
17987
17988fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
17999 let s = s.trim();
18000 let bytes = s.as_bytes();
18004 let sign_pos = bytes
18005 .iter()
18006 .enumerate()
18007 .rev()
18008 .find(|&(_, &b)| b == b'+' || b == b'-')
18009 .map(|(i, _)| i)?;
18010 if sign_pos == 0 {
18011 return None; }
18013 let time_part = &s[..sign_pos];
18014 let offset_part = &s[sign_pos..];
18015 let us = parse_time_str(time_part)?;
18016 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
18017 let offset_body = &offset_part[1..];
18018 let (hh_str, mm_str) = match offset_body.split_once(':') {
18019 Some((h, m)) => (h, m),
18020 None => (offset_body, "0"),
18021 };
18022 let hh: i32 = hh_str.parse().ok()?;
18023 let mm: i32 = mm_str.parse().ok()?;
18024 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
18025 return None;
18026 }
18027 let total = sign * (hh * 3600 + mm * 60);
18028 if total.abs() > 50_400 {
18029 return None;
18030 }
18031 Some((us, total))
18032}
18033
18034fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
18039 if n == 0 || (1901..=2155).contains(&n) {
18040 return Ok(Value::Year(n as u16));
18043 }
18044 Err(EngineError::Eval(EvalError::TypeMismatch {
18045 detail: alloc::format!(
18046 "year value out of range: {n} (column `{col_name}`; \
18047 MySQL accepts 0 or 1901..=2155)"
18048 ),
18049 }))
18050}
18051
18052fn parse_time_str(s: &str) -> Option<i64> {
18064 let s = s.trim();
18065 let (hms, frac) = match s.split_once('.') {
18066 Some((h, f)) => (h, Some(f)),
18067 None => (s, None),
18068 };
18069 let mut parts = hms.split(':');
18070 let hh: u32 = parts.next()?.parse().ok()?;
18071 let mm: u32 = parts.next()?.parse().ok()?;
18072 let ss: u32 = parts.next()?.parse().ok()?;
18073 if parts.next().is_some() {
18074 return None;
18075 }
18076 if hh > 23 || mm > 59 || ss > 59 {
18077 return None;
18078 }
18079 let frac_us: i64 = match frac {
18080 None => 0,
18081 Some(f) => {
18082 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
18083 return None;
18084 }
18085 let mut padded = alloc::string::String::with_capacity(6);
18087 padded.push_str(f);
18088 while padded.len() < 6 {
18089 padded.push('0');
18090 }
18091 padded.parse().ok()?
18092 }
18093 };
18094 Some(
18095 i64::from(hh) * 3_600_000_000
18096 + i64::from(mm) * 60_000_000
18097 + i64::from(ss) * 1_000_000
18098 + frac_us,
18099 )
18100}
18101
18102const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
18103 match t {
18104 ColumnTypeName::SmallInt => DataType::SmallInt,
18105 ColumnTypeName::Int => DataType::Int,
18106 ColumnTypeName::BigInt => DataType::BigInt,
18107 ColumnTypeName::Float => DataType::Float,
18108 ColumnTypeName::Text => DataType::Text,
18109 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
18110 ColumnTypeName::Char(n) => DataType::Char(n),
18111 ColumnTypeName::Bool => DataType::Bool,
18112 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
18113 dim,
18114 encoding: match encoding {
18115 SqlVecEncoding::F32 => VecEncoding::F32,
18116 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
18117 SqlVecEncoding::F16 => VecEncoding::F16,
18118 },
18119 },
18120 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
18121 ColumnTypeName::Date => DataType::Date,
18122 ColumnTypeName::Timestamp => DataType::Timestamp,
18123 ColumnTypeName::Timestamptz => DataType::Timestamptz,
18124 ColumnTypeName::Json => DataType::Json,
18125 ColumnTypeName::Jsonb => DataType::Jsonb,
18126 ColumnTypeName::Bytes => DataType::Bytes,
18127 ColumnTypeName::TextArray => DataType::TextArray,
18128 ColumnTypeName::IntArray => DataType::IntArray,
18129 ColumnTypeName::BigIntArray => DataType::BigIntArray,
18130 ColumnTypeName::TsVector => DataType::TsVector,
18131 ColumnTypeName::TsQuery => DataType::TsQuery,
18132 ColumnTypeName::Uuid => DataType::Uuid,
18133 ColumnTypeName::Time => DataType::Time,
18134 ColumnTypeName::Year => DataType::Year,
18135 ColumnTypeName::TimeTz => DataType::TimeTz,
18136 ColumnTypeName::Money => DataType::Money,
18137 ColumnTypeName::Range(k) => DataType::Range(match k {
18138 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
18139 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
18140 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
18141 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
18142 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
18143 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
18144 }),
18145 ColumnTypeName::Hstore => DataType::Hstore,
18146 ColumnTypeName::IntArray2D => DataType::IntArray2D,
18147 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
18148 ColumnTypeName::TextArray2D => DataType::TextArray2D,
18149 }
18150}
18151
18152fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
18156 match expr {
18157 Expr::Literal(l) => Ok(literal_to_value(l)),
18158 Expr::Cast { expr, target } => {
18159 let inner_value = literal_expr_to_value(*expr)?;
18160 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
18161 }
18162 Expr::Unary {
18163 op: UnOp::Neg,
18164 expr,
18165 } => match *expr {
18166 Expr::Literal(Literal::Integer(n)) => {
18167 let neg = n.checked_neg().ok_or_else(|| {
18170 EngineError::Unsupported("integer literal overflow on negation".into())
18171 })?;
18172 Ok(int_value_for(neg))
18173 }
18174 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
18175 other => Err(EngineError::Unsupported(alloc::format!(
18176 "unary minus over non-literal expression: {other:?}"
18177 ))),
18178 },
18179 Expr::Array(items) => {
18187 let mut materialised: alloc::vec::Vec<Value> =
18188 alloc::vec::Vec::with_capacity(items.len());
18189 for elem in items {
18190 materialised.push(literal_expr_to_value(elem)?);
18191 }
18192 Ok(array_literal_widen(materialised))
18193 }
18194 other => {
18207 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
18208 let ctx = EvalContext::new(&empty_schema, None);
18209 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
18210 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
18211 }
18212 }
18213}
18214
18215fn literal_to_value(l: Literal) -> Value {
18216 match l {
18217 Literal::Integer(n) => int_value_for(n),
18218 Literal::Float(x) => Value::Float(x),
18219 Literal::String(s) => Value::Text(s),
18220 Literal::Bool(b) => Value::Bool(b),
18221 Literal::Null => Value::Null,
18222 Literal::Vector(v) => Value::Vector(v),
18223 Literal::TextArray(items) => Value::TextArray(items),
18224 Literal::IntArray(items) => Value::IntArray(items),
18225 Literal::BigIntArray(items) => Value::BigIntArray(items),
18226 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
18227 }
18228}
18229
18230fn int_value_for(n: i64) -> Value {
18234 if let Ok(small) = i32::try_from(n) {
18235 Value::Int(small)
18236 } else {
18237 Value::BigInt(n)
18238 }
18239}
18240
18241#[allow(clippy::too_many_lines)]
18247fn check_unsigned_range(
18252 v: &Value,
18253 schema: &ColumnSchema,
18254 position: usize,
18255) -> Result<(), EngineError> {
18256 if !schema.is_unsigned {
18257 return Ok(());
18258 }
18259 let n = match v {
18260 Value::SmallInt(x) => i64::from(*x),
18261 Value::Int(x) => i64::from(*x),
18262 Value::BigInt(x) => *x,
18263 _ => return Ok(()), };
18265 if n < 0 {
18266 return Err(EngineError::Unsupported(alloc::format!(
18267 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
18268 schema.name
18269 )));
18270 }
18271 Ok(())
18272}
18273
18274fn coerce_value(
18275 v: Value,
18276 expected: DataType,
18277 col_name: &str,
18278 position: usize,
18279) -> Result<Value, EngineError> {
18280 if v.is_null() {
18281 return Ok(Value::Null);
18282 }
18283 let actual = v.data_type().expect("non-null");
18284 if actual == expected {
18285 return Ok(v);
18286 }
18287 let coerced = match (v, expected) {
18288 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
18289 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
18290 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
18291 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18292 i128::from(n),
18293 precision,
18294 scale,
18295 col_name,
18296 )?),
18297 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
18298 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
18299 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
18300 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18301 i128::from(n),
18302 precision,
18303 scale,
18304 col_name,
18305 )?),
18306 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
18307 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
18308 #[allow(clippy::cast_precision_loss)]
18309 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
18310 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
18311 i128::from(n),
18312 precision,
18313 scale,
18314 col_name,
18315 )?),
18316 (Value::Float(x), DataType::Numeric { precision, scale }) => {
18317 Some(numeric_from_float(x, precision, scale, col_name)?)
18318 }
18319 (Value::Text(s), DataType::Numeric { precision, scale }) => {
18330 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
18331 return Err(EngineError::Eval(EvalError::TypeMismatch {
18332 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
18333 }));
18334 };
18335 Some(numeric_rescale(
18336 mantissa, src_scale, precision, scale, col_name,
18337 )?)
18338 }
18339 (Value::Text(s), DataType::Date) => {
18341 let d = eval::parse_date_literal(&s).ok_or_else(|| {
18342 EngineError::Eval(EvalError::TypeMismatch {
18343 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
18344 })
18345 })?;
18346 Some(Value::Date(d))
18347 }
18348 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
18355 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
18356 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
18357 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
18358 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
18359 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
18360 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
18361 _ => None,
18362 },
18363 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
18372 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
18373 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
18374 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
18378 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
18379 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
18387 (Value::Text(s), DataType::Bytes) => {
18394 let bytes = decode_bytea_literal(&s).map_err(|e| {
18395 EngineError::Eval(EvalError::TypeMismatch {
18396 detail: alloc::format!(
18397 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
18398 ),
18399 })
18400 })?;
18401 Some(Value::Bytes(bytes))
18402 }
18403 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
18407 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
18415 Some(b) => Some(Value::Uuid(b)),
18416 None => {
18417 return Err(EngineError::Eval(EvalError::TypeMismatch {
18418 detail: alloc::format!(
18419 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
18420 ),
18421 }));
18422 }
18423 },
18424 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
18429 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
18435 Some(us) => Some(Value::Time(us)),
18436 None => {
18437 return Err(EngineError::Eval(EvalError::TypeMismatch {
18438 detail: alloc::format!(
18439 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
18440 ),
18441 }));
18442 }
18443 },
18444 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
18446 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
18451 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
18452 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
18453 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
18457 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
18458 Err(_) => {
18459 return Err(EngineError::Eval(EvalError::TypeMismatch {
18460 detail: alloc::format!(
18461 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
18462 ),
18463 }));
18464 }
18465 },
18466 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
18468 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
18472 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
18473 None => {
18474 return Err(EngineError::Eval(EvalError::TypeMismatch {
18475 detail: alloc::format!(
18476 "invalid input syntax for type time with time zone: \
18477 {s:?} (column `{col_name}`)"
18478 ),
18479 }));
18480 }
18481 },
18482 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
18484 Some(Value::Text(eval::format_timetz(us, offset_secs)))
18485 }
18486 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
18490 Some(c) => Some(Value::Money(c)),
18491 None => {
18492 return Err(EngineError::Eval(EvalError::TypeMismatch {
18493 detail: alloc::format!(
18494 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
18495 ),
18496 }));
18497 }
18498 },
18499 (Value::SmallInt(n), DataType::Money) => {
18503 Some(Value::Money(i64::from(n).saturating_mul(100)))
18504 }
18505 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
18506 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
18507 (Value::Float(x), DataType::Money) => {
18508 let scaled = x * 100.0;
18511 let cents = if scaled >= 0.0 {
18512 (scaled + 0.5) as i64
18513 } else {
18514 (scaled - 0.5) as i64
18515 };
18516 Some(Value::Money(cents))
18517 }
18518 (Value::Numeric { scaled, scale }, DataType::Money) => {
18519 let cents = if scale == 2 {
18522 scaled
18523 } else if scale < 2 {
18524 let mult = 10_i128.pow(u32::from(2 - scale));
18525 scaled.saturating_mul(mult)
18526 } else {
18527 let div = 10_i128.pow(u32::from(scale - 2));
18528 let half = div / 2;
18529 let bias = if scaled >= 0 { half } else { -half };
18530 (scaled + bias) / div
18531 };
18532 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
18533 }
18534 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
18536 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
18540 Some(v) => Some(v),
18541 None => {
18542 return Err(EngineError::Eval(EvalError::TypeMismatch {
18543 detail: alloc::format!(
18544 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
18545 ),
18546 }));
18547 }
18548 },
18549 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
18551 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
18553 Some(pairs) => Some(Value::Hstore(pairs)),
18554 None => {
18555 return Err(EngineError::Eval(EvalError::TypeMismatch {
18556 detail: alloc::format!(
18557 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
18558 ),
18559 }));
18560 }
18561 },
18562 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
18564 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
18567 Ok(m) => Some(Value::IntArray2D(m)),
18568 Err(e) => {
18569 return Err(EngineError::Eval(EvalError::TypeMismatch {
18570 detail: alloc::format!(
18571 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
18572 ),
18573 }));
18574 }
18575 },
18576 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
18577 Ok(m) => Some(Value::BigIntArray2D(m)),
18578 Err(e) => {
18579 return Err(EngineError::Eval(EvalError::TypeMismatch {
18580 detail: alloc::format!(
18581 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
18582 ),
18583 }));
18584 }
18585 },
18586 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
18587 Ok(m) => Some(Value::TextArray2D(m)),
18588 Err(e) => {
18589 return Err(EngineError::Eval(EvalError::TypeMismatch {
18590 detail: alloc::format!(
18591 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
18592 ),
18593 }));
18594 }
18595 },
18596 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
18598 (Value::BigIntArray2D(rows), DataType::Text) => {
18599 Some(Value::Text(format_bigint_2d_text(&rows)))
18600 }
18601 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
18602 (Value::Text(s), DataType::TextArray) => {
18607 let arr = decode_text_array_literal(&s).map_err(|e| {
18608 EngineError::Eval(EvalError::TypeMismatch {
18609 detail: alloc::format!(
18610 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
18611 ),
18612 })
18613 })?;
18614 Some(Value::TextArray(arr))
18615 }
18616 (Value::Text(s), DataType::IntArray) => {
18622 let arr = decode_text_array_literal(&s).map_err(|e| {
18623 EngineError::Eval(EvalError::TypeMismatch {
18624 detail: alloc::format!(
18625 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
18626 ),
18627 })
18628 })?;
18629 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
18630 for elem in arr {
18631 match elem {
18632 None => out.push(None),
18633 Some(t) => {
18634 let n: i32 = t.parse().map_err(|_| {
18635 EngineError::Eval(EvalError::TypeMismatch {
18636 detail: alloc::format!(
18637 "cannot parse {t:?} as INT element for `{col_name}`"
18638 ),
18639 })
18640 })?;
18641 out.push(Some(n));
18642 }
18643 }
18644 }
18645 Some(Value::IntArray(out))
18646 }
18647 (Value::Text(s), DataType::BigIntArray) => {
18648 let arr = decode_text_array_literal(&s).map_err(|e| {
18649 EngineError::Eval(EvalError::TypeMismatch {
18650 detail: alloc::format!(
18651 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
18652 ),
18653 })
18654 })?;
18655 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
18656 for elem in arr {
18657 match elem {
18658 None => out.push(None),
18659 Some(t) => {
18660 let n: i64 = t.parse().map_err(|_| {
18661 EngineError::Eval(EvalError::TypeMismatch {
18662 detail: alloc::format!(
18663 "cannot parse {t:?} as BIGINT element for `{col_name}`"
18664 ),
18665 })
18666 })?;
18667 out.push(Some(n));
18668 }
18669 }
18670 }
18671 Some(Value::BigIntArray(out))
18672 }
18673 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
18677 (Value::Text(s), DataType::Vector { dim, encoding }) => {
18686 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
18687 EngineError::Eval(EvalError::TypeMismatch {
18688 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
18689 })
18690 })?;
18691 if parsed.len() != dim as usize {
18692 return Err(EngineError::Eval(EvalError::TypeMismatch {
18693 detail: alloc::format!(
18694 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
18695 parsed.len()
18696 ),
18697 }));
18698 }
18699 Some(match encoding {
18700 VecEncoding::F32 => Value::Vector(parsed),
18701 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
18702 VecEncoding::F16 => {
18703 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
18704 }
18705 })
18706 }
18707 (Value::Text(s), DataType::TsVector) => {
18717 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
18718 EngineError::Eval(EvalError::TypeMismatch {
18719 detail: alloc::format!(
18720 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
18721 ),
18722 })
18723 })?;
18724 Some(Value::TsVector(lexs))
18725 }
18726 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
18727 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
18728 EngineError::Eval(EvalError::TypeMismatch {
18729 detail: alloc::format!(
18730 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
18731 ),
18732 })
18733 })?;
18734 Some(Value::Timestamp(t))
18735 }
18736 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
18739 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
18740 }
18741 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
18745 (Value::Timestamp(t), DataType::Date) => {
18746 let days = t.div_euclid(86_400_000_000);
18747 i32::try_from(days).ok().map(Value::Date)
18748 }
18749 (
18750 Value::Numeric {
18751 scaled,
18752 scale: src_scale,
18753 },
18754 DataType::Numeric { precision, scale },
18755 ) => Some(numeric_rescale(
18756 scaled, src_scale, precision, scale, col_name,
18757 )?),
18758 #[allow(clippy::cast_precision_loss)]
18759 (Value::Numeric { scaled, scale }, DataType::Float) => {
18760 let mut div = 1.0_f64;
18761 for _ in 0..scale {
18762 div *= 10.0;
18763 }
18764 Some(Value::Float((scaled as f64) / div))
18765 }
18766 (Value::Numeric { scaled, scale }, DataType::Int) => {
18767 let truncated = numeric_truncate_to_integer(scaled, scale);
18768 i32::try_from(truncated).ok().map(Value::Int)
18769 }
18770 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
18771 let truncated = numeric_truncate_to_integer(scaled, scale);
18772 i64::try_from(truncated).ok().map(Value::BigInt)
18773 }
18774 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
18775 let truncated = numeric_truncate_to_integer(scaled, scale);
18776 i16::try_from(truncated).ok().map(Value::SmallInt)
18777 }
18778 (Value::Text(s), DataType::Varchar(max)) => {
18780 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
18781 Some(Value::Text(s))
18782 } else {
18783 return Err(EngineError::Unsupported(alloc::format!(
18784 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
18785 {} chars",
18786 s.chars().count()
18787 )));
18788 }
18789 }
18790 (
18798 Value::Vector(v),
18799 DataType::Vector {
18800 dim,
18801 encoding: VecEncoding::Sq8,
18802 },
18803 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
18804 (
18809 Value::Vector(v),
18810 DataType::Vector {
18811 dim,
18812 encoding: VecEncoding::F16,
18813 },
18814 ) if v.len() == dim as usize => Some(Value::HalfVector(
18815 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
18816 )),
18817 (Value::Text(s), DataType::Char(size)) => {
18821 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
18822 if len > size {
18823 return Err(EngineError::Unsupported(alloc::format!(
18824 "value for CHAR({size}) column `{col_name}` exceeds length: \
18825 {len} chars"
18826 )));
18827 }
18828 let need = (size - len) as usize;
18829 let mut padded = s;
18830 padded.reserve(need);
18831 for _ in 0..need {
18832 padded.push(' ');
18833 }
18834 Some(Value::Text(padded))
18835 }
18836 _ => None,
18837 };
18838 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
18839 column: col_name.into(),
18840 expected,
18841 actual,
18842 position,
18843 }))
18844}
18845
18846fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
18852 use core::fmt::Write;
18853 let mut out = alloc::string::String::from("(");
18854 for (i, a) in args.iter().enumerate() {
18855 if i > 0 {
18856 out.push_str(", ");
18857 }
18858 match a.mode {
18859 spg_sql::ast::FunctionArgMode::In => {}
18860 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
18861 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
18862 }
18863 if let Some(n) = &a.name {
18864 out.push_str(n);
18865 out.push(' ');
18866 }
18867 match &a.ty {
18868 spg_sql::ast::FunctionArgType::Typed(t) => {
18869 let _ = write!(out, "{t}");
18870 }
18871 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
18872 }
18873 }
18874 out.push(')');
18875 out
18876}
18877
18878fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
18887 match expr {
18888 spg_sql::ast::Expr::FunctionCall { name, args } => {
18889 name.eq_ignore_ascii_case("unnest") && args.len() == 1
18890 }
18891 _ => false,
18892 }
18893}
18894
18895fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
18899 match expr {
18900 spg_sql::ast::Expr::FunctionCall { name, args }
18901 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
18902 {
18903 Some(&args[0])
18904 }
18905 _ => None,
18906 }
18907}
18908
18909fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
18914 match v {
18915 Value::Null => Ok(Vec::new()),
18916 Value::TextArray(items) => Ok(items
18917 .iter()
18918 .map(|opt| {
18919 opt.as_ref()
18920 .map(|s| Value::Text(s.clone()))
18921 .unwrap_or(Value::Null)
18922 })
18923 .collect()),
18924 Value::IntArray(items) => Ok(items
18925 .iter()
18926 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
18927 .collect()),
18928 Value::BigIntArray(items) => Ok(items
18929 .iter()
18930 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
18931 .collect()),
18932 other => Err(EngineError::Eval(EvalError::TypeMismatch {
18933 detail: alloc::format!(
18934 "unnest() expects an array argument, got {:?}",
18935 other.data_type()
18936 ),
18937 })),
18938 }
18939}
18940
18941#[cfg(test)]
18942mod tests {
18943 use super::*;
18944 use alloc::vec;
18945
18946 fn unwrap_command_ok(r: &QueryResult) -> usize {
18947 match r {
18948 QueryResult::CommandOk { affected, .. } => *affected,
18949 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
18950 }
18951 }
18952
18953 #[test]
18954 fn update_seek_positions_engages_on_indexed_eq() {
18955 let mut e = Engine::new();
18956 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
18957 .unwrap();
18958 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
18959 for i in 0..100 {
18960 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
18961 .unwrap();
18962 }
18963 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
18964 .expect("parse");
18965 let Statement::Update(u) = stmt else {
18966 panic!("expected Update, got {stmt:?}");
18967 };
18968 let w = u.where_.as_ref().expect("where");
18969 let table = e.catalog().get("b").unwrap();
18970 let schema_cols = table.schema().columns.clone();
18971 let Expr::Binary { lhs, op, rhs } = w else {
18973 panic!("WHERE not Binary: {w:?}");
18974 };
18975 assert_eq!(*op, BinOp::Eq, "op not Eq");
18976 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
18977 assert!(
18978 pair.is_some(),
18979 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
18980 );
18981 let (col_pos, value) = pair.unwrap();
18982 assert!(
18983 table.index_on(col_pos).is_some(),
18984 "no index on col {col_pos}"
18985 );
18986 assert!(
18987 IndexKey::from_value(&value).is_some(),
18988 "IndexKey::from_value None for {value:?}"
18989 );
18990 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
18991 assert_eq!(positions, Some(vec![42]), "seek did not engage");
18992 }
18993
18994 #[test]
18995 fn create_table_registers_schema() {
18996 let mut e = Engine::new();
18997 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
18998 .unwrap();
18999 assert_eq!(e.catalog().table_count(), 1);
19000 let t = e.catalog().get("foo").unwrap();
19001 assert_eq!(t.schema().columns.len(), 2);
19002 assert_eq!(t.schema().columns[0].ty, DataType::Int);
19003 assert!(!t.schema().columns[0].nullable);
19004 assert_eq!(t.schema().columns[1].ty, DataType::Text);
19005 }
19006
19007 #[test]
19008 fn create_table_vector_default_is_f32_encoded() {
19009 let mut e = Engine::new();
19010 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
19011 let t = e.catalog().get("t").unwrap();
19012 assert_eq!(
19013 t.schema().columns[0].ty,
19014 DataType::Vector {
19015 dim: 8,
19016 encoding: VecEncoding::F32,
19017 },
19018 );
19019 }
19020
19021 #[test]
19022 fn create_table_vector_using_sq8_succeeds() {
19023 let mut e = Engine::new();
19027 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
19028 let t = e.catalog().get("t").unwrap();
19029 assert_eq!(
19030 t.schema().columns[0].ty,
19031 DataType::Vector {
19032 dim: 8,
19033 encoding: VecEncoding::Sq8,
19034 },
19035 );
19036 }
19037
19038 #[test]
19039 fn insert_into_sq8_column_quantises_f32_payload() {
19040 let mut e = Engine::new();
19047 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
19048 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19049 .unwrap();
19050 let t = e.catalog().get("t").unwrap();
19051 assert_eq!(t.rows().len(), 1);
19052 match &t.rows()[0].values[0] {
19053 Value::Sq8Vector(q) => {
19054 assert_eq!(q.bytes.len(), 4);
19055 assert!((q.min - 0.0).abs() < 1e-6);
19057 assert!((q.max - 1.0).abs() < 1e-6);
19058 }
19059 other => panic!("expected Sq8Vector cell, got {other:?}"),
19060 }
19061 }
19062
19063 #[test]
19064 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
19065 let mut e = Engine::new();
19072 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
19073 .unwrap();
19074 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
19075 .unwrap();
19076 let t = e.catalog().get("t").unwrap();
19077 assert_eq!(t.rows().len(), 1);
19078 match &t.rows()[0].values[0] {
19079 Value::HalfVector(h) => {
19080 assert_eq!(h.dim(), 4);
19081 let back = h.to_f32_vec();
19082 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
19083 for (g, e) in back.iter().zip(expected.iter()) {
19084 assert!(
19085 (g - e).abs() < 1e-6,
19086 "{g} vs {e} should be exact on f16 grid"
19087 );
19088 }
19089 }
19090 other => panic!("expected HalfVector cell, got {other:?}"),
19091 }
19092 }
19093
19094 #[test]
19095 fn alter_index_rebuild_in_place_succeeds() {
19096 let mut e = Engine::new();
19101 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19102 .unwrap();
19103 for i in 0..8_i32 {
19104 #[allow(clippy::cast_precision_loss)]
19105 let base = (i as f32) * 0.1;
19106 e.execute(&alloc::format!(
19107 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
19108 b1 = base + 0.01,
19109 b2 = base + 0.02,
19110 ))
19111 .unwrap();
19112 }
19113 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19114 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
19115 assert_eq!(
19117 e.catalog().get("t").unwrap().schema().columns[1].ty,
19118 DataType::Vector {
19119 dim: 3,
19120 encoding: VecEncoding::F32,
19121 },
19122 );
19123 }
19124
19125 #[test]
19126 fn alter_index_rebuild_with_encoding_switches_cell_type() {
19127 let mut e = Engine::new();
19132 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
19133 .unwrap();
19134 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
19135 .unwrap();
19136 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
19137 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
19138 .unwrap();
19139 let t = e.catalog().get("t").unwrap();
19140 assert_eq!(
19141 t.schema().columns[1].ty,
19142 DataType::Vector {
19143 dim: 4,
19144 encoding: VecEncoding::Sq8,
19145 },
19146 );
19147 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
19148 }
19149
19150 #[test]
19151 fn alter_index_rebuild_unknown_index_errors() {
19152 let mut e = Engine::new();
19153 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
19154 assert!(
19155 matches!(
19156 &err,
19157 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
19158 ),
19159 "got: {err}"
19160 );
19161 }
19162
19163 #[test]
19164 fn alter_index_rebuild_on_btree_index_errors() {
19165 let mut e = Engine::new();
19168 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19169 e.execute("INSERT INTO t VALUES (1)").unwrap();
19170 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
19171 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
19172 assert!(
19173 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
19174 "got: {err}"
19175 );
19176 }
19177
19178 #[test]
19179 fn prepared_insert_substitutes_placeholders() {
19180 let mut e = Engine::new();
19186 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
19187 .unwrap();
19188 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
19189 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
19190 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
19191 .unwrap();
19192 }
19193 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
19195 let QueryResult::Rows { rows, .. } = rows_result else {
19196 panic!("expected Rows")
19197 };
19198 assert_eq!(rows.len(), 3);
19199 }
19200
19201 #[test]
19202 fn prepared_select_with_placeholder_filters_rows() {
19203 let mut e = Engine::new();
19204 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
19205 .unwrap();
19206 for i in 0..10_i32 {
19207 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
19208 .unwrap();
19209 }
19210 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
19211 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
19212 else {
19213 panic!("expected Rows")
19214 };
19215 assert_eq!(rows.len(), 1);
19217 assert_eq!(rows[0].values[0], Value::Int(5));
19218 }
19219
19220 #[test]
19221 fn prepared_too_few_params_errors() {
19222 let mut e = Engine::new();
19223 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19224 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
19225 let err = e.execute_prepared(stmt, &[]).unwrap_err();
19226 assert!(
19227 matches!(
19228 &err,
19229 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
19230 ),
19231 "got: {err}"
19232 );
19233 }
19234
19235 #[test]
19236 fn bytea_cast_round_trips_text_input() {
19237 let e = Engine::new();
19240 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
19241 let QueryResult::Rows { rows, .. } = r else {
19242 panic!("expected Rows")
19243 };
19244 assert_eq!(rows.len(), 1);
19245 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
19246 }
19247
19248 #[test]
19249 fn bytea_cast_pg_escape_hex_form() {
19250 let e = Engine::new();
19254 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
19255 let QueryResult::Rows { rows, .. } = r else {
19256 panic!("expected Rows")
19257 };
19258 assert_eq!(
19259 rows[0].values[0],
19260 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
19261 );
19262 }
19263
19264 #[test]
19265 fn bytea_cast_chains_through_octet_length() {
19266 let e = Engine::new();
19270 let r = e
19271 .execute_readonly("SELECT octet_length('hello'::bytea)")
19272 .unwrap();
19273 let QueryResult::Rows { rows, .. } = r else {
19274 panic!("expected Rows")
19275 };
19276 match &rows[0].values[0] {
19277 Value::Int(n) => assert_eq!(*n, 5),
19278 Value::BigInt(n) => assert_eq!(*n, 5),
19279 other => panic!("expected integer length, got {other:?}"),
19280 }
19281 }
19282
19283 #[test]
19284 fn readonly_prepared_on_snapshot_select_with_placeholder() {
19285 let mut e = Engine::new();
19291 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
19292 .unwrap();
19293 for i in 0..10_i32 {
19294 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
19295 .unwrap();
19296 }
19297 let snapshot = e.clone_snapshot();
19298 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
19299 let QueryResult::Rows { rows, .. } =
19300 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
19301 .unwrap()
19302 else {
19303 panic!("expected Rows")
19304 };
19305 assert_eq!(rows.len(), 1);
19306 assert_eq!(rows[0].values[0], Value::Int(5));
19307 }
19308
19309 #[test]
19310 fn readonly_prepared_on_snapshot_rejects_writes() {
19311 let mut e = Engine::new();
19315 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19316 let snapshot = e.clone_snapshot();
19317 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
19318 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
19319 .unwrap_err();
19320 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
19321 }
19322
19323 #[test]
19324 fn readonly_prepared_on_snapshot_frozen_view() {
19325 let mut e = Engine::new();
19331 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19332 e.execute("INSERT INTO t VALUES (1)").unwrap();
19333 let snapshot = e.clone_snapshot();
19334 e.execute("INSERT INTO t VALUES (2)").unwrap();
19335 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
19336 let QueryResult::Rows { rows, .. } =
19337 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
19338 .unwrap()
19339 else {
19340 panic!("expected Rows")
19341 };
19342 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
19343 }
19344
19345 #[test]
19346 fn describe_prepared_on_snapshot_resolves_columns() {
19347 let mut e = Engine::new();
19352 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
19353 .unwrap();
19354 let snapshot = e.clone_snapshot();
19355 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
19356 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
19357 assert_eq!(cols.len(), 2);
19358 assert_eq!(cols[0].name, "id");
19359 assert_eq!(cols[0].ty, DataType::Int);
19360 assert_eq!(cols[1].name, "name");
19361 assert_eq!(cols[1].ty, DataType::Text);
19362 }
19363
19364 #[test]
19365 fn insert_into_half_column_dim_mismatch_errors() {
19366 let mut e = Engine::new();
19367 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
19368 .unwrap();
19369 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
19370 assert!(matches!(
19371 &err,
19372 EngineError::Storage(StorageError::TypeMismatch { .. })
19373 ));
19374 }
19375
19376 #[test]
19377 fn insert_into_sq8_column_dim_mismatch_errors() {
19378 let mut e = Engine::new();
19383 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
19384 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
19385 assert!(
19386 matches!(
19387 &err,
19388 EngineError::Storage(StorageError::TypeMismatch { .. })
19389 ),
19390 "got: {err}",
19391 );
19392 }
19393
19394 #[test]
19395 fn create_table_duplicate_errors() {
19396 let mut e = Engine::new();
19397 e.execute("CREATE TABLE foo (a INT)").unwrap();
19398 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
19399 assert!(matches!(
19400 err,
19401 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
19402 ));
19403 }
19404
19405 #[test]
19406 fn insert_into_unknown_table_errors() {
19407 let mut e = Engine::new();
19408 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
19409 assert!(matches!(
19410 err,
19411 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
19412 ));
19413 }
19414
19415 #[test]
19416 fn insert_happy_path_reports_one_affected() {
19417 let mut e = Engine::new();
19418 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
19419 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
19420 assert_eq!(unwrap_command_ok(&r), 1);
19421 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
19422 }
19423
19424 #[test]
19425 fn insert_arity_mismatch_propagates() {
19426 let mut e = Engine::new();
19427 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
19428 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
19429 assert!(matches!(
19430 err,
19431 EngineError::Storage(StorageError::ArityMismatch { .. })
19432 ));
19433 }
19434
19435 #[test]
19436 fn insert_negative_integer_via_unary_minus() {
19437 let mut e = Engine::new();
19438 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
19439 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
19440 let rows = e.catalog().get("foo").unwrap().rows();
19441 assert_eq!(rows[0].values[0], Value::Int(-7));
19442 }
19443
19444 #[test]
19445 fn insert_expression_evaluated_against_empty_context() {
19446 let mut e = Engine::new();
19451 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
19452 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
19453 let rows = e.catalog().get("foo").unwrap().rows();
19454 assert_eq!(rows[0].values[0], Value::Int(3));
19455 }
19456
19457 #[test]
19458 fn select_star_returns_all_rows_in_insertion_order() {
19459 let mut e = Engine::new();
19460 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
19461 .unwrap();
19462 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
19463 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
19464 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
19465
19466 let r = e.execute("SELECT * FROM foo").unwrap();
19467 let QueryResult::Rows { columns, rows } = r else {
19468 panic!("expected Rows")
19469 };
19470 assert_eq!(columns.len(), 2);
19471 assert_eq!(columns[0].name, "a");
19472 assert_eq!(rows.len(), 3);
19473 assert_eq!(
19474 rows[1].values,
19475 vec![Value::Int(2), Value::Text("two".into())]
19476 );
19477 }
19478
19479 #[test]
19480 fn select_star_on_empty_table_returns_zero_rows() {
19481 let mut e = Engine::new();
19482 e.execute("CREATE TABLE foo (a INT)").unwrap();
19483 let r = e.execute("SELECT * FROM foo").unwrap();
19484 match r {
19485 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
19486 QueryResult::CommandOk { .. } => panic!("expected Rows"),
19487 }
19488 }
19489
19490 fn make_three_row_users(e: &mut Engine) {
19493 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
19494 .unwrap();
19495 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
19496 .unwrap();
19497 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
19498 .unwrap();
19499 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
19500 .unwrap();
19501 }
19502
19503 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
19504 match r {
19505 QueryResult::Rows { columns, rows } => (columns, rows),
19506 QueryResult::CommandOk { .. } => panic!("expected Rows"),
19507 }
19508 }
19509
19510 #[test]
19511 fn where_filter_passes_only_true_rows() {
19512 let mut e = Engine::new();
19513 make_three_row_users(&mut e);
19514 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
19515 let (_, rows) = unwrap_rows(r);
19516 assert_eq!(rows.len(), 2);
19517 assert_eq!(rows[0].values[0], Value::Int(2));
19518 assert_eq!(rows[1].values[0], Value::Int(3));
19519 }
19520
19521 #[test]
19522 fn where_with_null_result_filters_out_row() {
19523 let mut e = Engine::new();
19524 make_three_row_users(&mut e);
19525 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
19527 let (_, rows) = unwrap_rows(r);
19528 assert_eq!(rows.len(), 1);
19529 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
19530 }
19531
19532 #[test]
19533 fn projection_named_columns() {
19534 let mut e = Engine::new();
19535 make_three_row_users(&mut e);
19536 let r = e.execute("SELECT name, score FROM users").unwrap();
19537 let (cols, rows) = unwrap_rows(r);
19538 assert_eq!(cols.len(), 2);
19539 assert_eq!(cols[0].name, "name");
19540 assert_eq!(cols[1].name, "score");
19541 assert_eq!(rows.len(), 3);
19542 assert_eq!(
19543 rows[0].values,
19544 vec![Value::Text("alice".into()), Value::Int(90)]
19545 );
19546 }
19547
19548 #[test]
19549 fn projection_with_column_alias() {
19550 let mut e = Engine::new();
19551 make_three_row_users(&mut e);
19552 let r = e
19553 .execute("SELECT name AS who FROM users WHERE id = 1")
19554 .unwrap();
19555 let (cols, rows) = unwrap_rows(r);
19556 assert_eq!(cols[0].name, "who");
19557 assert_eq!(rows.len(), 1);
19558 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
19559 }
19560
19561 #[test]
19562 fn qualified_column_with_table_alias_resolves() {
19563 let mut e = Engine::new();
19564 make_three_row_users(&mut e);
19565 let r = e
19566 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
19567 .unwrap();
19568 let (cols, rows) = unwrap_rows(r);
19569 assert_eq!(cols.len(), 2);
19570 assert_eq!(rows.len(), 2);
19571 }
19572
19573 #[test]
19574 fn qualified_column_with_wrong_alias_errors() {
19575 let mut e = Engine::new();
19576 make_three_row_users(&mut e);
19577 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
19578 assert!(matches!(
19579 err,
19580 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
19581 ));
19582 }
19583
19584 #[test]
19585 fn select_unknown_column_errors_in_projection() {
19586 let mut e = Engine::new();
19587 make_three_row_users(&mut e);
19588 let err = e.execute("SELECT ghost FROM users").unwrap_err();
19589 assert!(matches!(
19590 err,
19591 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
19592 ));
19593 }
19594
19595 #[test]
19596 fn where_unknown_column_errors() {
19597 let mut e = Engine::new();
19598 make_three_row_users(&mut e);
19599 let err = e
19600 .execute("SELECT * FROM users WHERE ghost = 1")
19601 .unwrap_err();
19602 assert!(matches!(
19603 err,
19604 EngineError::Eval(EvalError::ColumnNotFound { .. })
19605 ));
19606 }
19607
19608 #[test]
19609 fn expression_projection_evaluates_and_renders() {
19610 let mut e = Engine::new();
19613 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
19614 e.execute("INSERT INTO t VALUES (3)").unwrap();
19615 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
19616 assert_eq!(rows.len(), 1);
19617 assert_eq!(rows[0].values[0], Value::Int(3));
19620 }
19621
19622 #[test]
19623 fn select_unknown_table_errors() {
19624 let mut e = Engine::new();
19625 let err = e.execute("SELECT * FROM ghost").unwrap_err();
19626 assert!(matches!(
19627 err,
19628 EngineError::Storage(StorageError::TableNotFound { .. })
19629 ));
19630 }
19631
19632 #[test]
19633 fn invalid_sql_returns_parse_error() {
19634 let mut e = Engine::new();
19637 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
19638 assert!(matches!(err, EngineError::Parse(_)));
19639 }
19640
19641 #[test]
19644 fn create_index_registers_on_table() {
19645 let mut e = Engine::new();
19646 make_three_row_users(&mut e);
19647 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
19648 let t = e.catalog().get("users").unwrap();
19649 assert_eq!(t.indices().len(), 1);
19650 assert_eq!(t.indices()[0].name, "by_name");
19651 }
19652
19653 #[test]
19654 fn create_index_on_unknown_table_errors() {
19655 let mut e = Engine::new();
19656 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
19657 assert!(matches!(
19658 err,
19659 EngineError::Storage(StorageError::TableNotFound { .. })
19660 ));
19661 }
19662
19663 #[test]
19664 fn create_index_on_unknown_column_errors() {
19665 let mut e = Engine::new();
19666 make_three_row_users(&mut e);
19667 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
19668 assert!(matches!(
19669 err,
19670 EngineError::Storage(StorageError::ColumnNotFound { .. })
19671 ));
19672 }
19673
19674 #[test]
19675 fn select_eq_uses_index_returns_same_rows_as_scan() {
19676 let mut without = Engine::new();
19680 make_three_row_users(&mut without);
19681 let mut with = Engine::new();
19682 make_three_row_users(&mut with);
19683 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
19684
19685 let q = "SELECT * FROM users WHERE id = 2";
19686 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
19687 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
19688 assert_eq!(no_idx_rows, idx_rows);
19689 assert_eq!(idx_rows.len(), 1);
19690 }
19691
19692 #[test]
19693 fn select_eq_with_no_matching_index_value_returns_empty() {
19694 let mut e = Engine::new();
19695 make_three_row_users(&mut e);
19696 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
19697 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
19698 assert_eq!(rows.len(), 0);
19699 }
19700
19701 #[test]
19704 fn begin_sets_in_transaction_flag() {
19705 let mut e = Engine::new();
19706 assert!(!e.in_transaction());
19707 e.execute("BEGIN").unwrap();
19708 assert!(e.in_transaction());
19709 }
19710
19711 #[test]
19712 fn double_begin_errors() {
19713 let mut e = Engine::new();
19714 e.execute("BEGIN").unwrap();
19715 let err = e.execute("BEGIN").unwrap_err();
19716 assert_eq!(err, EngineError::TransactionAlreadyOpen);
19717 }
19718
19719 #[test]
19720 fn commit_without_begin_errors() {
19721 let mut e = Engine::new();
19722 let err = e.execute("COMMIT").unwrap_err();
19723 assert_eq!(err, EngineError::NoActiveTransaction);
19724 }
19725
19726 #[test]
19727 fn rollback_without_begin_errors() {
19728 let mut e = Engine::new();
19729 let err = e.execute("ROLLBACK").unwrap_err();
19730 assert_eq!(err, EngineError::NoActiveTransaction);
19731 }
19732
19733 #[test]
19734 fn commit_applies_shadow_to_committed_catalog() {
19735 let mut e = Engine::new();
19736 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
19737 e.execute("BEGIN").unwrap();
19738 e.execute("INSERT INTO t VALUES (1)").unwrap();
19739 e.execute("INSERT INTO t VALUES (2)").unwrap();
19740 e.execute("COMMIT").unwrap();
19741 assert!(!e.in_transaction());
19742 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
19743 }
19744
19745 #[test]
19746 fn rollback_discards_shadow() {
19747 let mut e = Engine::new();
19748 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
19749 e.execute("BEGIN").unwrap();
19750 e.execute("INSERT INTO t VALUES (1)").unwrap();
19751 e.execute("INSERT INTO t VALUES (2)").unwrap();
19752 e.execute("ROLLBACK").unwrap();
19753 assert!(!e.in_transaction());
19754 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
19755 }
19756
19757 #[test]
19758 fn select_during_tx_sees_uncommitted_writes_own_session() {
19759 let mut e = Engine::new();
19762 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
19763 e.execute("BEGIN").unwrap();
19764 e.execute("INSERT INTO t VALUES (42)").unwrap();
19765 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
19766 assert_eq!(rows.len(), 1);
19767 assert_eq!(rows[0].values[0], Value::Int(42));
19768 }
19769
19770 #[test]
19771 fn snapshot_with_no_users_is_bare_catalog_format() {
19772 let mut e = Engine::new();
19773 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19774 let bytes = e.snapshot();
19775 assert_eq!(
19776 &bytes[..8],
19777 b"SPGDB001",
19778 "must be the bare v3.x catalog magic"
19779 );
19780 let e2 = Engine::restore_envelope(&bytes).unwrap();
19781 assert!(e2.users().is_empty());
19782 assert_eq!(e2.catalog().table_count(), 1);
19783 }
19784
19785 #[test]
19786 fn snapshot_with_users_round_trips_both_via_envelope() {
19787 let mut e = Engine::new();
19788 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19789 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
19790 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
19791 .unwrap();
19792 let bytes = e.snapshot();
19793 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
19794 let e2 = Engine::restore_envelope(&bytes).unwrap();
19795 assert_eq!(e2.users().len(), 2);
19796 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
19797 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
19798 assert_eq!(e2.verify_user("alice", "wrong"), None);
19799 assert_eq!(e2.catalog().table_count(), 1);
19800 }
19801
19802 #[test]
19803 fn ddl_inside_tx_also_rolled_back() {
19804 let mut e = Engine::new();
19805 e.execute("BEGIN").unwrap();
19806 e.execute("CREATE TABLE t (v INT)").unwrap();
19807 e.execute("SELECT * FROM t").unwrap();
19809 e.execute("ROLLBACK").unwrap();
19810 let err = e.execute("SELECT * FROM t").unwrap_err();
19812 assert!(matches!(
19813 err,
19814 EngineError::Storage(StorageError::TableNotFound { .. })
19815 ));
19816 }
19817
19818 #[test]
19821 fn create_publication_lands_in_catalog() {
19822 let mut e = Engine::new();
19823 assert!(e.publications().is_empty());
19824 e.execute("CREATE PUBLICATION pub_a").unwrap();
19825 assert_eq!(e.publications().len(), 1);
19826 assert!(e.publications().contains("pub_a"));
19827 }
19828
19829 #[test]
19830 fn create_publication_duplicate_errors() {
19831 let mut e = Engine::new();
19832 e.execute("CREATE PUBLICATION pub_a").unwrap();
19833 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
19834 assert!(
19835 alloc::format!("{err:?}").contains("DuplicateName"),
19836 "got {err:?}"
19837 );
19838 }
19839
19840 #[test]
19841 fn drop_publication_silent_when_absent() {
19842 let mut e = Engine::new();
19843 let r = e.execute("DROP PUBLICATION nope").unwrap();
19846 match r {
19847 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19848 other => panic!("expected CommandOk, got {other:?}"),
19849 }
19850 }
19851
19852 #[test]
19853 fn drop_publication_present_reports_one_affected() {
19854 let mut e = Engine::new();
19855 e.execute("CREATE PUBLICATION pub_a").unwrap();
19856 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
19857 match r {
19858 QueryResult::CommandOk {
19859 affected,
19860 modified_catalog,
19861 } => {
19862 assert_eq!(affected, 1);
19863 assert!(modified_catalog);
19864 }
19865 other => panic!("expected CommandOk, got {other:?}"),
19866 }
19867 assert!(e.publications().is_empty());
19868 }
19869
19870 #[test]
19871 fn publications_persist_across_snapshot_restore() {
19872 let mut e = Engine::new();
19877 e.execute("CREATE PUBLICATION pub_a").unwrap();
19878 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
19879 .unwrap();
19880 let snap = e.snapshot();
19881 let e2 = Engine::restore_envelope(&snap).unwrap();
19882 assert_eq!(e2.publications().len(), 2);
19883 assert!(e2.publications().contains("pub_a"));
19884 assert!(e2.publications().contains("pub_b"));
19885 }
19886
19887 #[test]
19888 fn create_publication_allowed_inside_transaction() {
19889 let mut e = Engine::new();
19893 e.execute("BEGIN").unwrap();
19894 e.execute("CREATE PUBLICATION pub_a").unwrap();
19895 e.execute("COMMIT").unwrap();
19896 assert!(e.publications().contains("pub_a"));
19897 }
19898
19899 #[test]
19902 fn create_publication_for_table_list_lands_with_scope() {
19903 let mut e = Engine::new();
19904 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19905 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
19906 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
19907 .unwrap();
19908 let scope = e.publications().get("pub_a").cloned();
19909 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
19910 panic!("expected ForTables scope, got {scope:?}")
19911 };
19912 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19913 }
19914
19915 #[test]
19916 fn create_publication_all_tables_except_lands_with_scope() {
19917 let mut e = Engine::new();
19918 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
19919 .unwrap();
19920 let scope = e.publications().get("pub_a").cloned();
19921 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
19922 panic!("expected AllTablesExcept scope, got {scope:?}")
19923 };
19924 assert_eq!(ts, alloc::vec!["t3".to_string()]);
19925 }
19926
19927 #[test]
19928 fn show_publications_empty_returns_zero_rows() {
19929 let e = Engine::new();
19930 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19931 let QueryResult::Rows { rows, columns } = r else {
19932 panic!()
19933 };
19934 assert!(rows.is_empty());
19935 assert_eq!(columns.len(), 3);
19936 assert_eq!(columns[0].name, "name");
19937 assert_eq!(columns[1].name, "scope");
19938 assert_eq!(columns[2].name, "table_count");
19939 }
19940
19941 #[test]
19942 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
19943 let mut e = Engine::new();
19944 e.execute("CREATE PUBLICATION z_pub").unwrap();
19945 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
19946 .unwrap();
19947 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
19948 .unwrap();
19949 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19950 let QueryResult::Rows { rows, .. } = r else {
19951 panic!()
19952 };
19953 assert_eq!(rows.len(), 3);
19954 let names: Vec<&str> = rows
19956 .iter()
19957 .map(|r| {
19958 if let Value::Text(s) = &r.values[0] {
19959 s.as_str()
19960 } else {
19961 panic!()
19962 }
19963 })
19964 .collect();
19965 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
19966 match &rows[0].values[1] {
19968 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
19969 other => panic!("expected Text, got {other:?}"),
19970 }
19971 assert_eq!(rows[0].values[2], Value::Int(2));
19972 match &rows[1].values[1] {
19974 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
19975 other => panic!("expected Text, got {other:?}"),
19976 }
19977 assert_eq!(rows[1].values[2], Value::Int(1));
19978 match &rows[2].values[1] {
19980 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
19981 other => panic!("expected Text, got {other:?}"),
19982 }
19983 assert_eq!(rows[2].values[2], Value::Null);
19984 }
19985
19986 #[test]
19987 fn for_list_scopes_persist_across_snapshot() {
19988 let mut e = Engine::new();
19991 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
19992 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
19993 .unwrap();
19994 let snap = e.snapshot();
19995 let e2 = Engine::restore_envelope(&snap).unwrap();
19996 assert_eq!(e2.publications().len(), 2);
19997 let p1 = e2.publications().get("p1").cloned();
19998 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
19999 panic!("p1 scope lost: {p1:?}")
20000 };
20001 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
20002 let p2 = e2.publications().get("p2").cloned();
20003 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
20004 panic!("p2 scope lost: {p2:?}")
20005 };
20006 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
20007 }
20008
20009 #[test]
20012 fn create_subscription_lands_in_catalog_with_defaults() {
20013 let mut e = Engine::new();
20014 e.execute(
20015 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
20016 )
20017 .unwrap();
20018 let s = e.subscriptions().get("sub_a").cloned().expect("present");
20019 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
20020 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
20021 assert!(s.enabled);
20022 assert_eq!(s.last_received_pos, 0);
20023 }
20024
20025 #[test]
20026 fn create_subscription_duplicate_name_errors() {
20027 let mut e = Engine::new();
20028 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
20029 .unwrap();
20030 let err = e
20031 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
20032 .unwrap_err();
20033 assert!(
20034 alloc::format!("{err:?}").contains("DuplicateName"),
20035 "got {err:?}"
20036 );
20037 }
20038
20039 #[test]
20040 fn drop_subscription_silent_when_absent() {
20041 let mut e = Engine::new();
20042 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
20043 match r {
20044 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
20045 other => panic!("expected CommandOk, got {other:?}"),
20046 }
20047 }
20048
20049 #[test]
20050 fn subscription_advance_updates_last_pos_monotone() {
20051 let mut e = Engine::new();
20052 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20053 .unwrap();
20054 assert!(e.subscription_advance("s", 100));
20055 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20056 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
20058 assert!(e.subscription_advance("s", 200));
20059 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
20060 assert!(!e.subscription_advance("missing", 1));
20061 }
20062
20063 #[test]
20064 fn show_subscriptions_returns_rows_ordered_by_name() {
20065 let mut e = Engine::new();
20066 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
20067 .unwrap();
20068 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
20069 .unwrap();
20070 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
20071 let QueryResult::Rows { rows, columns } = r else {
20072 panic!()
20073 };
20074 assert_eq!(rows.len(), 2);
20075 assert_eq!(columns.len(), 5);
20076 assert_eq!(columns[0].name, "name");
20077 assert_eq!(columns[4].name, "last_received_pos");
20078 let names: Vec<&str> = rows
20080 .iter()
20081 .map(|r| {
20082 if let Value::Text(s) = &r.values[0] {
20083 s.as_str()
20084 } else {
20085 panic!()
20086 }
20087 })
20088 .collect();
20089 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
20090 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
20092 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
20093 assert_eq!(rows[0].values[3], Value::Bool(true));
20094 assert_eq!(rows[0].values[4], Value::BigInt(0));
20095 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
20097 }
20098
20099 #[test]
20100 fn subscriptions_persist_across_snapshot_envelope_v4() {
20101 let mut e = Engine::new();
20102 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
20103 .unwrap();
20104 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
20105 .unwrap();
20106 e.subscription_advance("s2", 42);
20107 let snap = e.snapshot();
20108 let e2 = Engine::restore_envelope(&snap).unwrap();
20109 assert_eq!(e2.subscriptions().len(), 2);
20110 let s1 = e2.subscriptions().get("s1").unwrap();
20111 assert_eq!(s1.conn_str, "h=A");
20112 assert_eq!(
20113 s1.publications,
20114 alloc::vec!["p1".to_string(), "p2".to_string()]
20115 );
20116 assert_eq!(s1.last_received_pos, 0);
20117 let s2 = e2.subscriptions().get("s2").unwrap();
20118 assert_eq!(s2.last_received_pos, 42);
20119 }
20120
20121 #[test]
20122 fn v3_envelope_loads_with_empty_subscriptions() {
20123 let mut e = Engine::new();
20127 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
20128 let catalog = e.catalog.serialize();
20129 let users = crate::users::serialize_users(&e.users);
20130 let pubs = e.publications.serialize();
20131 let mut buf = Vec::new();
20132 buf.extend_from_slice(b"SPGENV01");
20133 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
20135 buf.extend_from_slice(&catalog);
20136 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
20137 buf.extend_from_slice(&users);
20138 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
20139 buf.extend_from_slice(&pubs);
20140 let crc = spg_crypto::crc32::crc32(&buf);
20141 buf.extend_from_slice(&crc.to_le_bytes());
20142
20143 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
20144 assert!(e2.subscriptions().is_empty());
20145 assert!(e2.publications().contains("pub_legacy"));
20146 }
20147
20148 #[test]
20149 fn create_subscription_allowed_inside_transaction() {
20150 let mut e = Engine::new();
20151 e.execute("BEGIN").unwrap();
20152 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
20153 .unwrap();
20154 e.execute("COMMIT").unwrap();
20155 assert!(e.subscriptions().contains("s"));
20156 }
20157
20158 #[test]
20160 fn analyze_populates_histogram_bounds() {
20161 let mut e = Engine::new();
20162 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
20163 .unwrap();
20164 for i in 0..50 {
20165 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
20166 .unwrap();
20167 }
20168 e.execute("ANALYZE t").unwrap();
20169 let stats = e.statistics();
20170 let id_stats = stats.get("t", "id").unwrap();
20171 assert!(id_stats.histogram_bounds.len() >= 2);
20172 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
20173 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
20174 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
20175 assert_eq!(id_stats.n_distinct, 50);
20176 }
20177
20178 #[test]
20179 fn reanalyze_overwrites_prior_stats() {
20180 let mut e = Engine::new();
20181 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20182 for i in 0..10 {
20183 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20184 .unwrap();
20185 }
20186 e.execute("ANALYZE t").unwrap();
20187 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
20188 assert_eq!(n1, 10);
20189 for i in 10..30 {
20190 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20191 .unwrap();
20192 }
20193 e.execute("ANALYZE t").unwrap();
20194 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
20195 assert_eq!(n2, 30);
20196 }
20197
20198 #[test]
20199 fn analyze_unknown_table_errors() {
20200 let mut e = Engine::new();
20201 let err = e.execute("ANALYZE nonexistent").unwrap_err();
20202 assert!(matches!(
20203 err,
20204 EngineError::Storage(StorageError::TableNotFound { .. })
20205 ));
20206 }
20207
20208 #[test]
20209 fn bare_analyze_covers_all_user_tables() {
20210 let mut e = Engine::new();
20211 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
20212 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
20213 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
20214 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
20215 let r = e.execute("ANALYZE").unwrap();
20216 match r {
20217 QueryResult::CommandOk {
20218 affected,
20219 modified_catalog,
20220 } => {
20221 assert_eq!(affected, 2);
20222 assert!(modified_catalog);
20223 }
20224 other => panic!("expected CommandOk, got {other:?}"),
20225 }
20226 assert!(e.statistics().get("t1", "id").is_some());
20227 assert!(e.statistics().get("t2", "name").is_some());
20228 }
20229
20230 #[test]
20231 fn select_from_spg_statistic_returns_rows_per_column() {
20232 let mut e = Engine::new();
20233 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
20234 .unwrap();
20235 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
20236 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
20237 e.execute("ANALYZE t").unwrap();
20238 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
20239 let QueryResult::Rows { rows, columns } = r else {
20240 panic!()
20241 };
20242 assert_eq!(columns.len(), 6);
20244 assert_eq!(columns[0].name, "table_name");
20245 assert_eq!(columns[4].name, "histogram_bounds");
20246 assert_eq!(columns[5].name, "cold_row_count");
20247 assert_eq!(rows.len(), 2, "one row per column of t");
20248 match (&rows[0].values[0], &rows[0].values[1]) {
20250 (Value::Text(t), Value::Text(c)) => {
20251 assert_eq!(t, "t");
20252 assert_eq!(c, "id");
20254 }
20255 _ => panic!(),
20256 }
20257 }
20258
20259 #[test]
20260 fn analyze_skips_vector_columns() {
20261 let mut e = Engine::new();
20264 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
20265 .unwrap();
20266 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
20267 e.execute("ANALYZE t").unwrap();
20268 assert!(e.statistics().get("t", "id").is_some());
20269 assert!(e.statistics().get("t", "v").is_none());
20270 }
20271
20272 #[test]
20273 fn statistics_persist_across_envelope_v5_round_trip() {
20274 let mut e = Engine::new();
20275 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20276 for i in 0..20 {
20277 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20278 .unwrap();
20279 }
20280 e.execute("ANALYZE").unwrap();
20281 let snap = e.snapshot();
20282 let e2 = Engine::restore_envelope(&snap).unwrap();
20283 let s = e2.statistics().get("t", "id").unwrap();
20284 assert_eq!(s.n_distinct, 20);
20285 }
20286
20287 #[test]
20290 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
20291 let mut e = Engine::new();
20295 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20296 for i in 0..9 {
20297 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20298 .unwrap();
20299 }
20300 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
20301 e.execute("INSERT INTO t VALUES (9)").unwrap();
20302 let needs = e.tables_needing_analyze();
20303 assert_eq!(needs, alloc::vec!["t".to_string()]);
20304 }
20305
20306 #[test]
20307 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
20308 let mut e = Engine::new();
20314 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20315 for i in 0..1000 {
20316 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20317 .unwrap();
20318 }
20319 e.execute("ANALYZE t").unwrap();
20320 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
20321 for i in 1000..1050 {
20322 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20323 .unwrap();
20324 }
20325 assert!(
20326 e.tables_needing_analyze().is_empty(),
20327 "50 inserts < threshold of ~105"
20328 );
20329 for i in 1050..1200 {
20330 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20331 .unwrap();
20332 }
20333 assert_eq!(
20334 e.tables_needing_analyze(),
20335 alloc::vec!["t".to_string()],
20336 "200 inserts > 0.1 × 1200 threshold"
20337 );
20338 }
20339
20340 #[test]
20341 fn auto_analyze_threshold_resets_after_analyze() {
20342 let mut e = Engine::new();
20343 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
20344 for i in 0..200 {
20345 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
20346 .unwrap();
20347 }
20348 assert!(!e.tables_needing_analyze().is_empty());
20349 e.execute("ANALYZE").unwrap();
20350 assert!(
20351 e.tables_needing_analyze().is_empty(),
20352 "ANALYZE must reset the counter"
20353 );
20354 }
20355
20356 #[test]
20357 fn auto_analyze_threshold_tracks_updates_and_deletes() {
20358 let mut e = Engine::new();
20359 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
20360 .unwrap();
20361 for i in 0..50 {
20362 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
20363 .unwrap();
20364 }
20365 e.execute("ANALYZE t").unwrap();
20366 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
20369 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
20370 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
20371 }
20372
20373 #[test]
20374 fn v4_envelope_loads_with_empty_statistics() {
20375 let mut e = Engine::new();
20379 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
20380 .unwrap();
20381 let catalog = e.catalog.serialize();
20382 let users = crate::users::serialize_users(&e.users);
20383 let pubs = e.publications.serialize();
20384 let subs = e.subscriptions.serialize();
20385 let mut buf = Vec::new();
20386 buf.extend_from_slice(b"SPGENV01");
20387 buf.push(4u8);
20388 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
20389 buf.extend_from_slice(&catalog);
20390 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
20391 buf.extend_from_slice(&users);
20392 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
20393 buf.extend_from_slice(&pubs);
20394 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
20395 buf.extend_from_slice(&subs);
20396 let crc = spg_crypto::crc32::crc32(&buf);
20397 buf.extend_from_slice(&crc.to_le_bytes());
20398 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
20399 assert!(e2.statistics().is_empty());
20400 }
20401
20402 #[test]
20403 fn v1_v2_envelope_loads_with_empty_publications() {
20404 let mut e = Engine::new();
20411 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
20414 .unwrap();
20415
20416 let catalog = e.catalog.serialize();
20418 let users = crate::users::serialize_users(&e.users);
20419 let mut buf = Vec::new();
20420 buf.extend_from_slice(b"SPGENV01");
20421 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
20423 buf.extend_from_slice(&catalog);
20424 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
20425 buf.extend_from_slice(&users);
20426 let crc = spg_crypto::crc32::crc32(&buf);
20427 buf.extend_from_slice(&crc.to_le_bytes());
20428
20429 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
20430 assert!(e2.publications().is_empty());
20431 }
20432}