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.long_strings())
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 build_joined_filtered_rows(
8333 &self,
8334 from: &FromClause,
8335 where_: Option<&Expr>,
8336 cancel: CancelToken<'_>,
8337 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8338 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8339 let primary_alias = from
8340 .primary
8341 .alias
8342 .as_deref()
8343 .unwrap_or(from.primary.name.as_str())
8344 .to_string();
8345 #[allow(clippy::type_complexity)]
8352 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8353 for j in &from.joins {
8354 let a = j
8355 .table
8356 .alias
8357 .as_deref()
8358 .unwrap_or(j.table.name.as_str())
8359 .to_string();
8360 if let Some(inner_box) = &j.table.lateral_subquery {
8361 let schema = self.lateral_probe_schema(inner_box)?;
8366 joined.push(JoinedPeer {
8367 eager_rows: None,
8368 cols: schema,
8369 alias: a,
8370 kind: j.kind,
8371 on: j.on.as_ref(),
8372 lateral: Some(inner_box.as_ref()),
8373 });
8374 } else {
8375 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8376 joined.push(JoinedPeer {
8377 eager_rows: Some(rows),
8378 cols,
8379 alias: a,
8380 kind: j.kind,
8381 on: j.on.as_ref(),
8382 lateral: None,
8383 });
8384 }
8385 }
8386 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8387 for col in &primary_cols {
8388 combined_schema.push(ColumnSchema::new(
8389 alloc::format!("{primary_alias}.{}", col.name),
8390 col.ty,
8391 col.nullable,
8392 ));
8393 }
8394 for peer in &joined {
8395 for col in &peer.cols {
8396 combined_schema.push(ColumnSchema::new(
8397 alloc::format!("{}.{}", peer.alias, col.name),
8398 col.ty,
8399 col.nullable,
8400 ));
8401 }
8402 }
8403 let ctx = EvalContext::new(&combined_schema, None);
8404 let mut working: Vec<Row> = primary_rows;
8405 let mut consumed_cols = primary_cols.len();
8408 for peer in &joined {
8409 let right_arity = peer.cols.len();
8410 let mut next: Vec<Row> = Vec::new();
8411 for left in &working {
8412 let mut left_matched = false;
8413 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8414 Some(inner) => {
8415 let outer_schema = &combined_schema[..consumed_cols];
8419 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8420 alloc::borrow::Cow::Owned(rows)
8421 }
8422 None => {
8423 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8424 alloc::borrow::Cow::Borrowed(r.as_slice())
8425 }
8426 };
8427 for right in per_left_rrows.as_ref() {
8428 let mut combined_vals = left.values.clone();
8429 combined_vals.extend(right.values.iter().cloned());
8430 let combined = Row::new(combined_vals);
8431 let keep = if let Some(on_expr) = peer.on {
8432 let cond =
8435 self.eval_expr_with_correlated(on_expr, &combined, &ctx, cancel, None)?;
8436 matches!(cond, Value::Bool(true))
8437 } else {
8438 true
8439 };
8440 if keep {
8441 next.push(combined);
8442 left_matched = true;
8443 }
8444 }
8445 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8446 let mut combined_vals = left.values.clone();
8447 for _ in 0..right_arity {
8448 combined_vals.push(Value::Null);
8449 }
8450 next.push(Row::new(combined_vals));
8451 }
8452 }
8453 working = next;
8454 consumed_cols += right_arity;
8455 debug_assert!(consumed_cols <= combined_schema.len());
8456 }
8457 let mut filtered: Vec<Row> = Vec::new();
8458 let mut memo = memoize::MemoizeCache::default();
8464 for row in working {
8465 if let Some(where_expr) = where_ {
8466 let cond = self.eval_expr_with_correlated(
8467 where_expr,
8468 &row,
8469 &ctx,
8470 cancel,
8471 Some(&mut memo),
8472 )?;
8473 if !matches!(cond, Value::Bool(true)) {
8474 continue;
8475 }
8476 }
8477 filtered.push(row);
8478 }
8479 Ok((combined_schema, filtered))
8480 }
8481
8482 fn lateral_probe_schema(
8488 &self,
8489 inner: &SelectStatement,
8490 ) -> Result<Vec<ColumnSchema>, EngineError> {
8491 match self.execute_readonly_select_for_lateral_probe(inner) {
8501 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8502 _ => {
8508 let mut out: Vec<ColumnSchema> = Vec::new();
8509 for (i, item) in inner.items.iter().enumerate() {
8510 let name = match item {
8511 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8512 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8513 SelectItem::Wildcard => alloc::format!("col{i}"),
8514 };
8515 out.push(ColumnSchema::new(name, DataType::Text, true));
8516 }
8517 Ok(out)
8518 }
8519 }
8520 }
8521
8522 fn execute_readonly_select_for_lateral_probe(
8528 &self,
8529 inner: &SelectStatement,
8530 ) -> Result<QueryResult, EngineError> {
8531 self.exec_bare_select_cancel(inner, CancelToken::none())
8532 }
8533
8534 fn materialise_lateral_for_outer(
8540 &self,
8541 inner: &SelectStatement,
8542 outer_schema: &[ColumnSchema],
8543 outer_row: &Row,
8544 ) -> Result<Vec<Row>, EngineError> {
8545 let mut substituted = inner.clone();
8546 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8547 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8548 match result {
8549 QueryResult::Rows { rows, .. } => Ok(rows),
8550 _ => Err(EngineError::Unsupported(
8551 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8552 )),
8553 }
8554 }
8555
8556 fn exec_joined_select(
8557 &self,
8558 stmt: &SelectStatement,
8559 from: &FromClause,
8560 cancel: CancelToken<'_>,
8561 ) -> Result<QueryResult, EngineError> {
8562 let (combined_schema, filtered) =
8570 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel)?;
8571 let ctx = EvalContext::new(&combined_schema, None);
8572 if aggregate::uses_aggregate(stmt) {
8575 let refs: Vec<&Row> = filtered.iter().collect();
8576 let agg_correlated = |e: &Expr, r: &Row, c: &EvalContext<'_>| {
8577 self.eval_expr_with_correlated(e, r, c, cancel, None)
8578 .map_err(|err| match err {
8579 EngineError::Eval(ev) => ev,
8580 other => eval::EvalError::TypeMismatch {
8581 detail: alloc::format!("{other}"),
8582 },
8583 })
8584 };
8585 let mut agg =
8586 aggregate::run(stmt, &refs, &combined_schema, None, Some(&agg_correlated))?;
8587 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8588 return Ok(QueryResult::Rows {
8589 columns: agg.columns,
8590 rows: agg.rows,
8591 });
8592 }
8593
8594 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8595 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8596 let mut proj_memo = memoize::MemoizeCache::default();
8597 for row in &filtered {
8598 let mut values = Vec::with_capacity(projection.len());
8599 for p in &projection {
8600 values.push(self.eval_expr_with_correlated(
8603 &p.expr,
8604 row,
8605 &ctx,
8606 cancel,
8607 Some(&mut proj_memo),
8608 )?);
8609 }
8610 let order_keys = if stmt.order_by.is_empty() {
8611 Vec::new()
8612 } else {
8613 build_order_keys(&stmt.order_by, row, &ctx)?
8614 };
8615 tagged.push((order_keys, Row::new(values)));
8616 }
8617 if !stmt.order_by.is_empty() {
8618 let keep = if stmt.distinct {
8619 None
8620 } else {
8621 stmt.limit_literal()
8622 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8623 };
8624 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8625 partial_sort_tagged(&mut tagged, keep, &descs);
8626 }
8627 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8628 if stmt.distinct {
8629 output_rows = dedup_rows(output_rows);
8630 }
8631 apply_offset_and_limit(
8632 &mut output_rows,
8633 stmt.offset_literal(),
8634 stmt.limit_literal(),
8635 );
8636 let columns: Vec<ColumnSchema> = projection
8637 .into_iter()
8638 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8639 .collect();
8640 Ok(QueryResult::Rows {
8641 columns,
8642 rows: output_rows,
8643 })
8644 }
8645}
8646
8647#[derive(Debug, Clone)]
8650struct ProjectedItem {
8651 expr: Expr,
8652 output_name: String,
8653 ty: DataType,
8654 nullable: bool,
8655}
8656
8657fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8663 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8664 for r in rows {
8665 if !out.iter().any(|seen| seen == &r) {
8666 out.push(r);
8667 }
8668 }
8669 out
8670}
8671
8672fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8676 match v {
8677 Value::Null => Ok(f64::INFINITY),
8678 Value::SmallInt(n) => Ok(f64::from(*n)),
8679 Value::Int(n) => Ok(f64::from(*n)),
8680 Value::Date(d) => Ok(f64::from(*d)),
8681 #[allow(clippy::cast_precision_loss)]
8682 Value::Timestamp(t) => Ok(*t as f64),
8683 #[allow(clippy::cast_precision_loss)]
8686 Value::Time(us) => Ok(*us as f64),
8687 Value::Year(y) => Ok(f64::from(*y)),
8691 #[allow(clippy::cast_precision_loss)]
8696 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8697 #[allow(clippy::cast_precision_loss)]
8699 Value::Money(c) => Ok(*c as f64),
8700 Value::Range { .. } => Err(EngineError::Unsupported(
8703 "ORDER BY of a range value is not supported in v7.17.0".into(),
8704 )),
8705 Value::Hstore(_) => Err(EngineError::Unsupported(
8707 "ORDER BY of a hstore value is not supported".into(),
8708 )),
8709 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8711 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8712 ),
8713 #[allow(clippy::cast_precision_loss)]
8714 Value::Numeric { scaled, scale } => {
8715 let mut divisor = 1.0_f64;
8721 for _ in 0..*scale {
8722 divisor *= 10.0;
8723 }
8724 Ok((*scaled as f64) / divisor)
8725 }
8726 #[allow(clippy::cast_precision_loss)]
8727 Value::BigInt(n) => Ok(*n as f64),
8728 Value::Float(x) => Ok(*x),
8729 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8730 Value::Text(s) => {
8731 let mut key: u64 = 0;
8735 for &b in s.as_bytes().iter().take(8) {
8736 key = (key << 8) | u64::from(b);
8737 }
8738 #[allow(clippy::cast_precision_loss)]
8739 Ok(key as f64)
8740 }
8741 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8742 Err(EngineError::Unsupported(
8743 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8744 ))
8745 }
8746 Value::Interval { .. } => Err(EngineError::Unsupported(
8747 "ORDER BY of an INTERVAL is not supported in v2.11 \
8748 (months vs micros has no single canonical ordering)"
8749 .into(),
8750 )),
8751 Value::Json(_) => Err(EngineError::Unsupported(
8752 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8753 )),
8754 _ => Err(EngineError::Unsupported(
8758 "ORDER BY of this value type is not supported".into(),
8759 )),
8760 }
8761}
8762
8763fn try_nsw_knn(
8777 stmt: &SelectStatement,
8778 table: &Table,
8779 schema_cols: &[ColumnSchema],
8780 table_alias: &str,
8781) -> Option<Vec<usize>> {
8782 if stmt.distinct {
8783 return None;
8784 }
8785 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8786 if limit == 0 {
8787 return None;
8788 }
8789 if stmt.order_by.len() != 1 {
8793 return None;
8794 }
8795 let order = &stmt.order_by[0];
8796 if order.desc {
8800 return None;
8801 }
8802 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8803 return None;
8804 };
8805 let metric = match op {
8806 BinOp::L2Distance => spg_storage::NswMetric::L2,
8807 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8808 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8809 _ => return None,
8810 };
8811 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8813 (lhs.as_ref(), rhs.as_ref())
8814 else {
8815 return None;
8816 };
8817 if let Some(q) = &col.qualifier
8818 && q != table_alias
8819 {
8820 return None;
8821 }
8822 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8823 let query = literal_to_vector(literal)?;
8824 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8825 if let Some(where_expr) = &stmt.where_ {
8826 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8830 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8831 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8832 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8833 for i in candidates {
8834 let row = &table.rows()[i];
8835 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8836 if matches!(cond, Value::Bool(true)) {
8837 kept.push(i);
8838 if kept.len() >= limit {
8839 break;
8840 }
8841 }
8842 }
8843 Some(kept)
8844 } else {
8845 Some(spg_storage::nsw_query(
8846 table, &idx.name, &query, limit, metric,
8847 ))
8848 }
8849}
8850
8851const NSW_OVER_FETCH_FLOOR: usize = 32;
8855
8856fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8859 match e {
8860 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8861 Expr::Cast { expr, .. } => literal_to_vector(expr),
8862 _ => None,
8863 }
8864}
8865
8866fn materialise_in_order(
8870 stmt: &SelectStatement,
8871 table: &Table,
8872 schema_cols: &[ColumnSchema],
8873 table_alias: &str,
8874 ordered_rows: &[usize],
8875) -> Result<QueryResult, EngineError> {
8876 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8877 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8878 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8879 for &i in ordered_rows {
8880 let row = &table.rows()[i];
8881 let mut values = Vec::with_capacity(projection.len());
8882 for p in &projection {
8883 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8884 }
8885 output_rows.push(Row::new(values));
8886 }
8887 apply_offset_and_limit(
8888 &mut output_rows,
8889 stmt.offset_literal(),
8890 stmt.limit_literal(),
8891 );
8892 let columns: Vec<ColumnSchema> = projection
8893 .into_iter()
8894 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8895 .collect();
8896 Ok(QueryResult::Rows {
8897 columns,
8898 rows: output_rows,
8899 })
8900}
8901
8902fn try_index_seek_positions(
8915 where_expr: &Expr,
8916 schema_cols: &[ColumnSchema],
8917 table: &Table,
8918 table_alias: &str,
8919) -> Option<Vec<usize>> {
8920 if let Expr::Binary {
8921 lhs,
8922 op: BinOp::And,
8923 rhs,
8924 } = where_expr
8925 {
8926 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8927 return Some(p);
8928 }
8929 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8930 }
8931 let Expr::Binary {
8932 lhs,
8933 op: BinOp::Eq,
8934 rhs,
8935 } = where_expr
8936 else {
8937 return None;
8938 };
8939 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8940 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8941 let idx = table.index_on(col_pos)?;
8942 let key = IndexKey::from_value(&value)?;
8943 let locators = idx.lookup_eq(&key);
8944 let mut out = Vec::with_capacity(locators.len());
8945 for loc in locators {
8946 match *loc {
8947 spg_storage::RowLocator::Hot(i) => out.push(i),
8948 spg_storage::RowLocator::Cold { .. } => return None,
8949 }
8950 }
8951 Some(out)
8952}
8953
8954fn try_index_seek<'a>(
8955 where_expr: &Expr,
8956 schema_cols: &[ColumnSchema],
8957 catalog: &'a Catalog,
8958 table: &'a Table,
8959 table_alias: &str,
8960) -> Option<Vec<Cow<'a, Row>>> {
8961 if let Expr::Binary {
8968 lhs,
8969 op: BinOp::And,
8970 rhs,
8971 } = where_expr
8972 {
8973 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8976 return Some(rows);
8977 }
8978 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8979 }
8980 let Expr::Binary {
8981 lhs,
8982 op: BinOp::Eq,
8983 rhs,
8984 } = where_expr
8985 else {
8986 return None;
8987 };
8988 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8989 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8990 let idx = table.index_on(col_pos)?;
8991 let key = IndexKey::from_value(&value)?;
8992 let locators = idx.lookup_eq(&key);
8993 let table_name = table.schema().name.as_str();
8994 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
9002 for loc in locators {
9003 match *loc {
9004 spg_storage::RowLocator::Hot(i) => {
9005 if let Some(row) = table.rows().get(i) {
9006 out.push(Cow::Borrowed(row));
9007 }
9008 }
9009 spg_storage::RowLocator::Cold { segment_id, .. } => {
9010 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
9011 out.push(Cow::Owned(row));
9012 }
9013 }
9014 }
9015 }
9016 Some(out)
9017}
9018
9019fn try_gin_seek<'a>(
9038 where_expr: &Expr,
9039 schema_cols: &[ColumnSchema],
9040 catalog: &'a Catalog,
9041 table: &'a Table,
9042 table_alias: &str,
9043 ctx: &eval::EvalContext<'_>,
9044) -> Option<Vec<Cow<'a, Row>>> {
9045 if let Expr::Binary {
9046 lhs,
9047 op: BinOp::And,
9048 rhs,
9049 } = where_expr
9050 {
9051 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
9052 return Some(rows);
9053 }
9054 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
9055 }
9056 if let Expr::Binary {
9065 lhs,
9066 op: BinOp::Or,
9067 rhs,
9068 } = where_expr
9069 {
9070 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
9071 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
9072 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
9073 out.extend(left);
9074 out.extend(right);
9075 return Some(out);
9076 }
9077 let Expr::Binary {
9078 lhs,
9079 op: BinOp::TsMatch,
9080 rhs,
9081 } = where_expr
9082 else {
9083 return None;
9084 };
9085 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
9090 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
9091 let idx = table
9098 .indices()
9099 .iter()
9100 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
9101 let candidates = gin_query_candidates(idx, &query)?;
9102 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
9104 for loc in candidates {
9105 match loc {
9106 spg_storage::RowLocator::Hot(i) => {
9107 if let Some(row) = table.rows().get(i) {
9108 out.push(Cow::Borrowed(row));
9109 }
9110 }
9111 spg_storage::RowLocator::Cold { .. } => {}
9118 }
9119 }
9120 Some(out)
9121}
9122
9123fn try_trgm_seek<'a>(
9139 where_expr: &Expr,
9140 schema_cols: &[ColumnSchema],
9141 table: &'a Table,
9142 table_alias: &str,
9143) -> Option<Vec<Cow<'a, Row>>> {
9144 if let Expr::Binary {
9145 lhs,
9146 op: BinOp::And,
9147 rhs,
9148 } = where_expr
9149 {
9150 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9151 return Some(rows);
9152 }
9153 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9154 }
9155 let Expr::Like { expr, pattern, .. } = where_expr else {
9161 return None;
9162 };
9163 let Expr::Column(c) = expr.as_ref() else {
9165 return None;
9166 };
9167 if let Some(q) = &c.qualifier
9168 && q != table_alias
9169 {
9170 return None;
9171 }
9172 let col_pos = schema_cols
9173 .iter()
9174 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9175 let idx = table
9177 .indices()
9178 .iter()
9179 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9180 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9184 return None;
9185 };
9186 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9187 let mut iter = trigrams.iter();
9190 let first = iter.next()?;
9191 let mut acc: Vec<spg_storage::RowLocator> = {
9192 let mut v = idx.gin_trgm_lookup(first).to_vec();
9193 v.sort_by_key(locator_sort_key);
9194 v.dedup_by_key(|l| locator_sort_key(l));
9195 v
9196 };
9197 for tri in iter {
9198 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9199 next.sort_by_key(locator_sort_key);
9200 next.dedup_by_key(|l| locator_sort_key(l));
9201 let mut merged: Vec<spg_storage::RowLocator> =
9203 Vec::with_capacity(acc.len().min(next.len()));
9204 let (mut i, mut j) = (0usize, 0usize);
9205 while i < acc.len() && j < next.len() {
9206 let lk = locator_sort_key(&acc[i]);
9207 let rk = locator_sort_key(&next[j]);
9208 match lk.cmp(&rk) {
9209 core::cmp::Ordering::Less => i += 1,
9210 core::cmp::Ordering::Greater => j += 1,
9211 core::cmp::Ordering::Equal => {
9212 merged.push(acc[i]);
9213 i += 1;
9214 j += 1;
9215 }
9216 }
9217 }
9218 acc = merged;
9219 if acc.is_empty() {
9220 break;
9221 }
9222 }
9223 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9224 for loc in acc {
9225 if let spg_storage::RowLocator::Hot(i) = loc
9226 && let Some(row) = table.rows().get(i)
9227 {
9228 out.push(Cow::Borrowed(row));
9229 }
9230 }
9232 Some(out)
9233}
9234
9235fn resolve_gin_col_query(
9241 col_side: &Expr,
9242 query_side: &Expr,
9243 schema_cols: &[ColumnSchema],
9244 table_alias: &str,
9245 ctx: &eval::EvalContext<'_>,
9246) -> Option<(usize, spg_storage::TsQueryAst)> {
9247 let column = match col_side {
9252 Expr::Column(c) => c,
9253 Expr::FunctionCall { name, args }
9254 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9255 {
9256 if let Expr::Column(c) = args.last().unwrap() {
9260 c
9261 } else {
9262 return None;
9263 }
9264 }
9265 _ => return None,
9266 };
9267 let c = column;
9268 if let Some(q) = &c.qualifier
9269 && q != table_alias
9270 {
9271 return None;
9272 }
9273 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9274 let empty_row = Row::new(Vec::new());
9278 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9279 let Value::TsQuery(q) = v else { return None };
9280 Some((pos, q))
9281}
9282
9283fn gin_query_candidates(
9294 idx: &spg_storage::Index,
9295 query: &spg_storage::TsQueryAst,
9296) -> Option<Vec<spg_storage::RowLocator>> {
9297 use spg_storage::TsQueryAst;
9298 match query {
9299 TsQueryAst::Term { word, .. } => {
9300 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9301 v.sort_by_key(locator_sort_key);
9302 v.dedup_by_key(|l| locator_sort_key(l));
9303 Some(v)
9304 }
9305 TsQueryAst::And(l, r) => {
9306 let mut left = gin_query_candidates(idx, l)?;
9307 let mut right = gin_query_candidates(idx, r)?;
9308 left.sort_by_key(locator_sort_key);
9309 right.sort_by_key(locator_sort_key);
9310 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9312 let (mut i, mut j) = (0usize, 0usize);
9313 while i < left.len() && j < right.len() {
9314 let lk = locator_sort_key(&left[i]);
9315 let rk = locator_sort_key(&right[j]);
9316 match lk.cmp(&rk) {
9317 core::cmp::Ordering::Less => i += 1,
9318 core::cmp::Ordering::Greater => j += 1,
9319 core::cmp::Ordering::Equal => {
9320 out.push(left[i]);
9321 i += 1;
9322 j += 1;
9323 }
9324 }
9325 }
9326 Some(out)
9327 }
9328 TsQueryAst::Or(l, r) => {
9329 let mut out = gin_query_candidates(idx, l)?;
9330 out.extend(gin_query_candidates(idx, r)?);
9331 out.sort_by_key(locator_sort_key);
9332 out.dedup_by_key(|l| locator_sort_key(l));
9333 Some(out)
9334 }
9335 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9340 }
9341}
9342
9343fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9348 match *l {
9349 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9350 spg_storage::RowLocator::Cold {
9351 segment_id,
9352 page_offset,
9353 } => (1, u64::from(segment_id), u64::from(page_offset)),
9354 }
9355}
9356
9357fn try_pk_predicate(
9369 where_expr: &Expr,
9370 schema_cols: &[ColumnSchema],
9371 table_alias: &str,
9372) -> Option<(usize, IndexKey)> {
9373 let Expr::Binary {
9374 lhs,
9375 op: BinOp::Eq,
9376 rhs,
9377 } = where_expr
9378 else {
9379 return None;
9380 };
9381 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9382 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9383 let key = IndexKey::from_value(&value)?;
9384 Some((col_pos, key))
9385}
9386
9387fn resolve_col_literal_pair(
9388 col_side: &Expr,
9389 lit_side: &Expr,
9390 schema_cols: &[ColumnSchema],
9391 table_alias: &str,
9392) -> Option<(usize, Value)> {
9393 let Expr::Column(c) = col_side else {
9394 return None;
9395 };
9396 if let Some(q) = &c.qualifier
9397 && q != table_alias
9398 {
9399 return None;
9400 }
9401 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9402 let Expr::Literal(l) = lit_side else {
9403 return None;
9404 };
9405 let v = match l {
9406 Literal::Integer(n) => {
9407 if let Ok(small) = i32::try_from(*n) {
9408 Value::Int(small)
9409 } else {
9410 Value::BigInt(*n)
9411 }
9412 }
9413 Literal::Float(x) => Value::Float(*x),
9414 Literal::String(s) => Value::Text(s.clone()),
9415 Literal::Bool(b) => Value::Bool(*b),
9416 Literal::Null => Value::Null,
9417 Literal::Vector(_)
9420 | Literal::Interval { .. }
9421 | Literal::TextArray(_)
9422 | Literal::IntArray(_)
9423 | Literal::BigIntArray(_) => return None,
9424 };
9425 Some((pos, v))
9426}
9427
9428fn resolve_projection_column<'a>(
9433 c: &ColumnName,
9434 schema_cols: &'a [ColumnSchema],
9435 table_alias: &str,
9436) -> Result<&'a ColumnSchema, EngineError> {
9437 if let Some(q) = &c.qualifier {
9438 let composite = alloc::format!("{q}.{name}", name = c.name);
9439 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9440 return Ok(s);
9441 }
9442 if q == table_alias
9445 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9446 {
9447 return Ok(s);
9448 }
9449 let prefix = alloc::format!("{q}.");
9453 let qualifier_known =
9454 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9455 if !qualifier_known {
9456 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9457 qualifier: q.clone(),
9458 }));
9459 }
9460 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9461 name: c.name.clone(),
9462 }));
9463 }
9464 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9465 return Ok(s);
9466 }
9467 let suffix = alloc::format!(".{name}", name = c.name);
9468 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9469 let first = matches.next();
9470 let extra = matches.next();
9471 match (first, extra) {
9472 (Some(s), None) => Ok(s),
9473 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9474 detail: alloc::format!("ambiguous column reference: {}", c.name),
9475 })),
9476 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9477 name: c.name.clone(),
9478 })),
9479 }
9480}
9481
9482fn build_projection(
9483 items: &[SelectItem],
9484 schema_cols: &[ColumnSchema],
9485 table_alias: &str,
9486) -> Result<Vec<ProjectedItem>, EngineError> {
9487 let mut out = Vec::new();
9488 for item in items {
9489 match item {
9490 SelectItem::Wildcard => {
9491 for col in schema_cols {
9492 out.push(ProjectedItem {
9493 expr: Expr::Column(ColumnName {
9494 qualifier: None,
9495 name: col.name.clone(),
9496 }),
9497 output_name: col.name.clone(),
9498 ty: col.ty,
9499 nullable: col.nullable,
9500 });
9501 }
9502 }
9503 SelectItem::Expr { expr, alias } => {
9504 if let Expr::Column(c) = expr {
9511 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9512 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9513 out.push(ProjectedItem {
9514 expr: expr.clone(),
9515 output_name,
9516 ty: sch.ty,
9517 nullable: sch.nullable,
9518 });
9519 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9520 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9521 out.push(ProjectedItem {
9522 expr: expr.clone(),
9523 output_name,
9524 ty: shape.ty,
9525 nullable: shape.nullable,
9526 });
9527 } else {
9528 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9529 out.push(ProjectedItem {
9530 expr: expr.clone(),
9531 output_name,
9532 ty: DataType::Text,
9533 nullable: true,
9534 });
9535 }
9536 }
9537 }
9538 }
9539 Ok(out)
9540}
9541
9542fn numeric_from_integer(
9546 n: i128,
9547 precision: u8,
9548 scale: u8,
9549 col_name: &str,
9550) -> Result<Value, EngineError> {
9551 let factor = pow10_i128(scale);
9552 let scaled = n.checked_mul(factor).ok_or_else(|| {
9553 EngineError::Unsupported(alloc::format!(
9554 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9555 ))
9556 })?;
9557 check_precision(scaled, precision, col_name)?;
9558 Ok(Value::Numeric { scaled, scale })
9559}
9560
9561#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9564fn numeric_from_float(
9565 x: f64,
9566 precision: u8,
9567 scale: u8,
9568 col_name: &str,
9569) -> Result<Value, EngineError> {
9570 if !x.is_finite() {
9571 return Err(EngineError::Unsupported(alloc::format!(
9572 "cannot store non-finite float in NUMERIC column `{col_name}`"
9573 )));
9574 }
9575 let mut factor = 1.0_f64;
9576 for _ in 0..scale {
9577 factor *= 10.0;
9578 }
9579 let shifted = x * factor;
9584 let biased = if shifted >= 0.0 {
9585 shifted + 0.5
9586 } else {
9587 shifted - 0.5
9588 };
9589 if !(-1e38..=1e38).contains(&biased) {
9592 return Err(EngineError::Unsupported(alloc::format!(
9593 "value {x} overflows NUMERIC range for column `{col_name}`"
9594 )));
9595 }
9596 let scaled = biased as i128;
9597 check_precision(scaled, precision, col_name)?;
9598 Ok(Value::Numeric { scaled, scale })
9599}
9600
9601fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9608 let s = s.trim();
9609 if s.is_empty() {
9610 return None;
9611 }
9612 let (negative, rest) = match s.as_bytes()[0] {
9613 b'-' => (true, &s[1..]),
9614 b'+' => (false, &s[1..]),
9615 _ => (false, s),
9616 };
9617 if rest.is_empty() {
9618 return None;
9619 }
9620 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9624 return None;
9625 }
9626 let (int_part, frac_part) = match rest.find('.') {
9627 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9628 None => (rest, ""),
9629 };
9630 if int_part.is_empty() && frac_part.is_empty() {
9631 return None;
9632 }
9633 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9634 return None;
9635 }
9636 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9637 return None;
9638 }
9639 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9640 if scale_u32 > u32::from(u8::MAX) {
9641 return None;
9642 }
9643 let scale = scale_u32 as u8;
9644 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9645 if negative {
9646 digits.push('-');
9647 }
9648 digits.push_str(int_part);
9649 digits.push_str(frac_part);
9650 let digits = if digits == "-" {
9652 return None;
9653 } else if digits.is_empty() {
9654 "0"
9655 } else {
9656 digits.as_str()
9657 };
9658 let mantissa: i128 = digits.parse().ok()?;
9659 Some((mantissa, scale))
9660}
9661
9662fn numeric_rescale(
9665 scaled: i128,
9666 src_scale: u8,
9667 precision: u8,
9668 dst_scale: u8,
9669 col_name: &str,
9670) -> Result<Value, EngineError> {
9671 let new_scaled = if dst_scale >= src_scale {
9672 let bump = pow10_i128(dst_scale - src_scale);
9673 scaled.checked_mul(bump).ok_or_else(|| {
9674 EngineError::Unsupported(alloc::format!(
9675 "overflow rescaling NUMERIC for column `{col_name}`"
9676 ))
9677 })?
9678 } else {
9679 let drop = pow10_i128(src_scale - dst_scale);
9680 let half = drop / 2;
9681 if scaled >= 0 {
9682 (scaled + half) / drop
9683 } else {
9684 (scaled - half) / drop
9685 }
9686 };
9687 check_precision(new_scaled, precision, col_name)?;
9688 Ok(Value::Numeric {
9689 scaled: new_scaled,
9690 scale: dst_scale,
9691 })
9692}
9693
9694const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9697 if scale == 0 {
9698 return scaled;
9699 }
9700 let factor = pow10_i128_const(scale);
9701 scaled / factor
9702}
9703
9704fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9708 if precision == 0 {
9709 return Ok(());
9710 }
9711 let limit = pow10_i128(precision);
9712 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9713 return Err(EngineError::Unsupported(alloc::format!(
9714 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9715 )));
9716 }
9717 Ok(())
9718}
9719
9720const fn pow10_i128_const(p: u8) -> i128 {
9721 let mut acc: i128 = 1;
9722 let mut i = 0;
9723 while i < p {
9724 acc *= 10;
9725 i += 1;
9726 }
9727 acc
9728}
9729
9730fn pow10_i128(p: u8) -> i128 {
9731 pow10_i128_const(p)
9732}
9733
9734impl Engine {
9749 #[allow(
9760 clippy::too_many_lines,
9761 clippy::type_complexity,
9762 clippy::needless_range_loop
9763 )] fn exec_select_with_window(
9765 &self,
9766 stmt: &SelectStatement,
9767 cancel: CancelToken<'_>,
9768 ) -> Result<QueryResult, EngineError> {
9769 let from = stmt.from.as_ref().ok_or_else(|| {
9770 EngineError::Unsupported("window functions require a FROM clause".into())
9771 })?;
9772 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9781 let filtered: Vec<Row>;
9782 if from.joins.is_empty() {
9783 let primary = &from.primary;
9784 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9785 StorageError::TableNotFound {
9786 name: primary.name.clone(),
9787 }
9788 })?;
9789 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9790 schema_cols_owned = table.schema().columns.clone();
9791 alias_opt = Some(alias);
9792 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9797 let mut owned: Vec<Row> = Vec::new();
9798 for (i, row) in table.rows().iter().enumerate() {
9799 if i.is_multiple_of(256) {
9800 cancel.check()?;
9801 }
9802 if let Some(w) = &stmt.where_ {
9803 let cond = eval::eval_expr(w, row, &ctx)?;
9804 if !matches!(cond, Value::Bool(true)) {
9805 continue;
9806 }
9807 }
9808 owned.push(row.clone());
9809 }
9810 filtered = owned;
9811 } else {
9812 let (combined_schema, rows) =
9813 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel)?;
9814 schema_cols_owned = combined_schema;
9815 alias_opt = None;
9816 filtered = rows;
9817 }
9818 let schema_cols = &schema_cols_owned;
9819 let ctx = self.ev_ctx(schema_cols, alias_opt);
9820 let alias = alias_opt.unwrap_or("");
9821 let n_rows = filtered.len();
9822 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9826
9827 let mut window_nodes: Vec<Expr> = Vec::new();
9829 for item in &stmt.items {
9830 if let SelectItem::Expr { expr, .. } = item {
9831 collect_window_nodes(expr, &mut window_nodes);
9832 }
9833 }
9834
9835 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9838 for wnode in &window_nodes {
9839 let Expr::WindowFunction {
9840 name,
9841 args,
9842 partition_by,
9843 order_by,
9844 frame,
9845 null_treatment,
9846 } = wnode
9847 else {
9848 unreachable!("collect_window_nodes pushes only WindowFunction");
9849 };
9850 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)> =
9852 Vec::with_capacity(n_rows);
9853 for (i, row) in filtered.iter().enumerate() {
9854 let pkey: Vec<Value> = partition_by
9855 .iter()
9856 .map(|p| eval::eval_expr(p, row, &ctx))
9857 .collect::<Result<_, _>>()?;
9858 let okey: Vec<(Value, bool, Option<bool>)> = order_by
9859 .iter()
9860 .map(|(e, desc, nf)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc, *nf)))
9861 .collect::<Result<_, _>>()?;
9862 indexed.push((pkey, okey, i));
9863 }
9864 indexed.sort_by(|a, b| {
9867 let p_cmp = partition_key_cmp(&a.0, &b.0);
9868 if p_cmp != core::cmp::Ordering::Equal {
9869 return p_cmp;
9870 }
9871 order_key_cmp(&a.1, &b.1)
9872 });
9873 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9875 let mut p_start = 0;
9876 while p_start < indexed.len() {
9877 let mut p_end = p_start + 1;
9878 while p_end < indexed.len()
9879 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9880 == core::cmp::Ordering::Equal
9881 {
9882 p_end += 1;
9883 }
9884 compute_window_partition(
9886 name,
9887 args,
9888 !order_by.is_empty(),
9889 frame.as_ref(),
9890 *null_treatment,
9891 &indexed[p_start..p_end],
9892 &filtered_refs,
9893 &ctx,
9894 &mut out_vals,
9895 )?;
9896 p_start = p_end;
9897 }
9898 win_vals.push(out_vals);
9899 }
9900
9901 let mut ext_cols = schema_cols.clone();
9903 for i in 0..window_nodes.len() {
9904 ext_cols.push(ColumnSchema::new(
9905 alloc::format!("__win_{i}"),
9906 DataType::Text, true,
9908 ));
9909 }
9910 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9912 for i in 0..n_rows {
9913 let mut values = filtered[i].values.clone();
9914 for w in 0..window_nodes.len() {
9915 values.push(win_vals[w][i].clone());
9916 }
9917 ext_rows.push(Row::new(values));
9918 }
9919 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9921 for item in &stmt.items {
9922 let new_item = match item {
9923 SelectItem::Wildcard => SelectItem::Wildcard,
9924 SelectItem::Expr { expr, alias } => {
9925 let mut e = expr.clone();
9926 rewrite_window_to_columns(&mut e, &window_nodes);
9927 SelectItem::Expr {
9928 expr: e,
9929 alias: alias.clone(),
9930 }
9931 }
9932 };
9933 rewritten_items.push(new_item);
9934 }
9935
9936 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9942 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9943 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9944 for (i, row) in ext_rows.iter().enumerate() {
9945 if i.is_multiple_of(256) {
9946 cancel.check()?;
9947 }
9948 let mut values = Vec::with_capacity(projection.len());
9949 for p in &projection {
9950 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9951 }
9952 let order_keys = if stmt.order_by.is_empty() {
9953 Vec::new()
9954 } else {
9955 let mut keys = Vec::with_capacity(stmt.order_by.len());
9956 for o in &stmt.order_by {
9957 let mut e = o.expr.clone();
9958 rewrite_window_to_columns(&mut e, &window_nodes);
9959 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9960 keys.push(value_to_order_key(&key)?);
9961 }
9962 keys
9963 };
9964 tagged.push((order_keys, Row::new(values)));
9965 }
9966 if !stmt.order_by.is_empty() {
9968 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9969 sort_by_keys(&mut tagged, &descs);
9970 }
9971 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9972 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9973 let final_cols: Vec<ColumnSchema> = projection
9974 .into_iter()
9975 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9976 .collect();
9977 Ok(QueryResult::Rows {
9978 columns: final_cols,
9979 rows: out_rows,
9980 })
9981 }
9982
9983 fn exec_select_with_meta_views(
10000 &self,
10001 stmt: &SelectStatement,
10002 cancel: CancelToken<'_>,
10003 ) -> Result<QueryResult, EngineError> {
10004 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
10005 collect_meta_view_names(stmt, &mut needed);
10006 let mut catalog = self.active_catalog().clone();
10007 for view in &needed {
10008 if catalog.get(view).is_some() {
10009 continue;
10010 }
10011 match view.as_str() {
10012 "__spg_info_columns" => {
10013 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
10014 materialise_meta_view(&mut catalog, view, schema, rows)?;
10015 }
10016 "__spg_info_tables" => {
10017 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
10018 materialise_meta_view(&mut catalog, view, schema, rows)?;
10019 }
10020 "__spg_pg_class" => {
10021 let (schema, rows) = synth_pg_class(self.active_catalog());
10022 materialise_meta_view(&mut catalog, view, schema, rows)?;
10023 }
10024 "__spg_pg_attribute" => {
10025 let (schema, rows) = synth_pg_attribute(self.active_catalog());
10026 materialise_meta_view(&mut catalog, view, schema, rows)?;
10027 }
10028 "__spg_pg_type" => {
10031 let (schema, rows) = synth_pg_type(self.active_catalog());
10032 materialise_meta_view(&mut catalog, view, schema, rows)?;
10033 }
10034 "__spg_pg_proc" => {
10037 let (schema, rows) = synth_pg_proc(self.active_catalog());
10038 materialise_meta_view(&mut catalog, view, schema, rows)?;
10039 }
10040 "__spg_pg_trigger" => {
10047 let (schema, rows) = synth_pg_trigger(self.active_catalog());
10048 materialise_meta_view(&mut catalog, view, schema, rows)?;
10049 }
10050 "__spg_pg_namespace" => {
10053 let (schema, rows) = synth_pg_namespace(self.active_catalog());
10054 materialise_meta_view(&mut catalog, view, schema, rows)?;
10055 }
10056 "__spg_pg_indexes" => {
10059 let (schema, rows) = synth_pg_indexes(self.active_catalog());
10060 materialise_meta_view(&mut catalog, view, schema, rows)?;
10061 }
10062 "__spg_pg_index" => {
10065 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
10066 materialise_meta_view(&mut catalog, view, schema, rows)?;
10067 }
10068 "__spg_pg_constraint" => {
10071 let (schema, rows) = synth_pg_constraint(self.active_catalog());
10072 materialise_meta_view(&mut catalog, view, schema, rows)?;
10073 }
10074 "__spg_pg_database" => {
10079 let (schema, rows) = synth_pg_database(self.active_catalog());
10080 materialise_meta_view(&mut catalog, view, schema, rows)?;
10081 }
10082 "__spg_pg_roles" | "__spg_pg_user" => {
10083 let (schema, rows) = synth_pg_roles(self);
10084 materialise_meta_view(&mut catalog, view, schema, rows)?;
10085 }
10086 "__spg_pg_views" => {
10090 let (schema, rows) = synth_pg_views(self.active_catalog());
10091 materialise_meta_view(&mut catalog, view, schema, rows)?;
10092 }
10093 "__spg_pg_matviews" => {
10097 let (schema, _) = synth_pg_views(self.active_catalog());
10098 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
10099 }
10100 "__spg_pg_extension" => {
10103 let (schema, rows) = synth_pg_extension();
10104 materialise_meta_view(&mut catalog, view, schema, rows)?;
10105 }
10106 "__spg_pg_settings" => {
10108 let (schema, rows) = synth_pg_settings(self);
10109 materialise_meta_view(&mut catalog, view, schema, rows)?;
10110 }
10111 "__spg_info_key_column_usage" => {
10113 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
10114 materialise_meta_view(&mut catalog, view, schema, rows)?;
10115 }
10116 "__spg_info_referential_constraints" => {
10118 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
10119 materialise_meta_view(&mut catalog, view, schema, rows)?;
10120 }
10121 "__spg_info_statistics" => {
10123 let (schema, rows) = synth_info_statistics(self.active_catalog());
10124 materialise_meta_view(&mut catalog, view, schema, rows)?;
10125 }
10126 "__spg_info_routines" => {
10128 let (schema, rows) = synth_info_routines();
10129 materialise_meta_view(&mut catalog, view, schema, rows)?;
10130 }
10131 "__spg_mysql_user" => {
10133 let (schema, rows) = synth_mysql_user(self);
10134 materialise_meta_view(&mut catalog, view, schema, rows)?;
10135 }
10136 "__spg_mysql_db" => {
10137 let (schema, rows) = synth_mysql_db();
10138 materialise_meta_view(&mut catalog, view, schema, rows)?;
10139 }
10140 _ => {
10141 return Err(EngineError::Unsupported(alloc::format!(
10142 "meta view {view:?} is not yet materialisable; \
10143 v7.16.2 covers information_schema.columns / .tables \
10144 and pg_catalog.pg_class / pg_attribute; \
10145 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
10146 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
10147 pg_user / pg_views / pg_matviews / pg_settings"
10148 )));
10149 }
10150 }
10151 }
10152 let mut temp = Engine::restore(catalog);
10153 if let Some(c) = self.clock {
10154 temp = temp.with_clock(c);
10155 }
10156 if let Some(f) = self.salt_fn {
10157 temp = temp.with_salt_fn(f);
10158 }
10159 temp.meta_views_materialised = true;
10160 temp.exec_select_cancel(stmt, cancel)
10161 }
10162
10163 fn exec_with_ctes(
10164 &self,
10165 stmt: &SelectStatement,
10166 cancel: CancelToken<'_>,
10167 ) -> Result<QueryResult, EngineError> {
10168 cancel.check()?;
10169 let mut catalog = self.active_catalog().clone();
10170 for cte in &stmt.ctes {
10171 if catalog.get(&cte.name).is_some() {
10172 return Err(EngineError::Unsupported(alloc::format!(
10173 "CTE name {:?} shadows an existing table; rename the CTE",
10174 cte.name
10175 )));
10176 }
10177 let (columns, rows) = if cte.recursive {
10178 self.materialise_recursive_cte(cte, &catalog, cancel)?
10179 } else {
10180 let mut cte_engine = Engine::restore(catalog.clone());
10186 if let Some(c) = self.clock {
10187 cte_engine = cte_engine.with_clock(c);
10188 }
10189 if let Some(f) = self.salt_fn {
10190 cte_engine = cte_engine.with_salt_fn(f);
10191 }
10192 let body_result = cte_engine.exec_select_cancel(&cte.body, cancel)?;
10193 let QueryResult::Rows { columns, rows } = body_result else {
10194 return Err(EngineError::Unsupported(alloc::format!(
10195 "CTE {:?} body did not return rows",
10196 cte.name
10197 )));
10198 };
10199 (columns, rows)
10200 };
10201 let inferred = infer_column_types(&columns, &rows);
10206 let mut columns = inferred;
10207 if !cte.column_overrides.is_empty() {
10209 if cte.column_overrides.len() != columns.len() {
10210 return Err(EngineError::Unsupported(alloc::format!(
10211 "CTE {:?} column list has {} names but body returns {} columns",
10212 cte.name,
10213 cte.column_overrides.len(),
10214 columns.len()
10215 )));
10216 }
10217 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10218 col.name.clone_from(name);
10219 }
10220 }
10221 let schema = TableSchema::new(cte.name.clone(), columns);
10222 catalog.create_table(schema).map_err(EngineError::Storage)?;
10223 let table = catalog
10224 .get_mut(&cte.name)
10225 .expect("just-created CTE table must exist");
10226 for row in rows {
10227 table.insert(row).map_err(EngineError::Storage)?;
10228 }
10229 }
10230 let mut body = stmt.clone();
10233 body.ctes = Vec::new();
10234 let mut temp = Engine::restore(catalog);
10235 if let Some(c) = self.clock {
10236 temp = temp.with_clock(c);
10237 }
10238 if let Some(f) = self.salt_fn {
10239 temp = temp.with_salt_fn(f);
10240 }
10241 temp.exec_select_cancel(&body, cancel)
10242 }
10243
10244 #[allow(clippy::too_many_lines)]
10254 fn materialise_recursive_cte(
10255 &self,
10256 cte: &spg_sql::ast::Cte,
10257 base_catalog: &Catalog,
10258 cancel: CancelToken<'_>,
10259 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10260 const MAX_TOTAL_ROWS: usize = 1_000_000;
10261 const MAX_ITERATIONS: usize = 100_000;
10262 cancel.check()?;
10263 if cte.body.unions.is_empty() {
10264 return Err(EngineError::Unsupported(alloc::format!(
10265 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10266 cte.name
10267 )));
10268 }
10269 let mut anchor = cte.body.clone();
10271 let union_terms = core::mem::take(&mut anchor.unions);
10272 anchor.ctes = Vec::new();
10273 if select_refers_to(&anchor, &cte.name) {
10275 return Err(EngineError::Unsupported(alloc::format!(
10276 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10277 cte.name
10278 )));
10279 }
10280 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10281 let QueryResult::Rows {
10282 columns: anchor_cols,
10283 rows: anchor_rows,
10284 } = anchor_result
10285 else {
10286 return Err(EngineError::Unsupported(alloc::format!(
10287 "WITH RECURSIVE {:?}: anchor did not return rows",
10288 cte.name
10289 )));
10290 };
10291 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10295 if !cte.column_overrides.is_empty() {
10296 if cte.column_overrides.len() != columns.len() {
10297 return Err(EngineError::Unsupported(alloc::format!(
10298 "CTE {:?} column list has {} names but anchor returns {} columns",
10299 cte.name,
10300 cte.column_overrides.len(),
10301 columns.len()
10302 )));
10303 }
10304 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10305 col.name.clone_from(name);
10306 }
10307 }
10308 let mut all_rows: Vec<Row> = anchor_rows.clone();
10309 let mut working_set: Vec<Row> = anchor_rows;
10310 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10311 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10314 if !all_union_all {
10315 for r in &all_rows {
10316 seen.insert(encode_row_key(r));
10317 }
10318 }
10319 for iter in 0..MAX_ITERATIONS {
10320 cancel.check()?;
10321 if working_set.is_empty() {
10322 break;
10323 }
10324 let mut iter_catalog = base_catalog.clone();
10326 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10327 iter_catalog
10328 .create_table(schema)
10329 .map_err(EngineError::Storage)?;
10330 {
10331 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10332 for row in &working_set {
10333 table.insert(row.clone()).map_err(EngineError::Storage)?;
10334 }
10335 }
10336 let mut iter_engine = Engine::restore(iter_catalog);
10337 if let Some(c) = self.clock {
10338 iter_engine = iter_engine.with_clock(c);
10339 }
10340 if let Some(f) = self.salt_fn {
10341 iter_engine = iter_engine.with_salt_fn(f);
10342 }
10343 let mut next_set: Vec<Row> = Vec::new();
10345 for (_, term) in &union_terms {
10346 let mut term = term.clone();
10347 term.ctes = Vec::new();
10348 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10349 let QueryResult::Rows {
10350 columns: rc,
10351 rows: rs,
10352 } = r
10353 else {
10354 return Err(EngineError::Unsupported(alloc::format!(
10355 "WITH RECURSIVE {:?}: recursive term did not return rows",
10356 cte.name
10357 )));
10358 };
10359 if rc.len() != columns.len() {
10360 return Err(EngineError::Unsupported(alloc::format!(
10361 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10362 cte.name,
10363 rc.len(),
10364 columns.len()
10365 )));
10366 }
10367 for row in rs {
10368 if !all_union_all {
10369 let key = encode_row_key(&row);
10370 if !seen.insert(key) {
10371 continue;
10372 }
10373 }
10374 next_set.push(row);
10375 }
10376 }
10377 if next_set.is_empty() {
10378 break;
10379 }
10380 all_rows.extend(next_set.iter().cloned());
10381 working_set = next_set;
10382 if all_rows.len() > MAX_TOTAL_ROWS {
10383 return Err(EngineError::Unsupported(alloc::format!(
10384 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10385 cte.name
10386 )));
10387 }
10388 if iter + 1 == MAX_ITERATIONS {
10389 return Err(EngineError::Unsupported(alloc::format!(
10390 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10391 cte.name
10392 )));
10393 }
10394 }
10395 Ok((columns, all_rows))
10396 }
10397
10398 fn resolve_select_subqueries(
10399 &self,
10400 stmt: &mut SelectStatement,
10401 cancel: CancelToken<'_>,
10402 ) -> Result<(), EngineError> {
10403 for item in &mut stmt.items {
10404 if let SelectItem::Expr { expr, .. } = item {
10405 self.resolve_expr_subqueries(expr, cancel)?;
10406 }
10407 }
10408 if let Some(w) = &mut stmt.where_ {
10409 self.resolve_expr_subqueries(w, cancel)?;
10410 }
10411 if let Some(from) = &mut stmt.from {
10415 for j in &mut from.joins {
10416 if let Some(on) = &mut j.on {
10417 self.resolve_expr_subqueries(on, cancel)?;
10418 }
10419 }
10420 }
10421 if let Some(gs) = &mut stmt.group_by {
10422 for g in gs {
10423 self.resolve_expr_subqueries(g, cancel)?;
10424 }
10425 }
10426 if let Some(h) = &mut stmt.having {
10427 self.resolve_expr_subqueries(h, cancel)?;
10428 }
10429 for o in &mut stmt.order_by {
10430 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10431 }
10432 for (_, peer) in &mut stmt.unions {
10433 self.resolve_select_subqueries(peer, cancel)?;
10434 }
10435 Ok(())
10436 }
10437
10438 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10440 &self,
10441 e: &mut Expr,
10442 cancel: CancelToken<'_>,
10443 ) -> Result<(), EngineError> {
10444 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10446 *e = replacement;
10447 return Ok(());
10448 }
10449 match e {
10450 Expr::AggregateOrdered { call, order_by, .. } => {
10451 self.resolve_expr_subqueries(call, cancel)?;
10452 for o in order_by.iter_mut() {
10453 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10454 }
10455 }
10456 Expr::Binary { lhs, rhs, .. } => {
10457 self.resolve_expr_subqueries(lhs, cancel)?;
10458 self.resolve_expr_subqueries(rhs, cancel)?;
10459 }
10460 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10461 self.resolve_expr_subqueries(expr, cancel)?;
10462 }
10463 Expr::FunctionCall { args, .. } => {
10464 for a in args {
10465 self.resolve_expr_subqueries(a, cancel)?;
10466 }
10467 }
10468 Expr::Like { expr, pattern, .. } => {
10469 self.resolve_expr_subqueries(expr, cancel)?;
10470 self.resolve_expr_subqueries(pattern, cancel)?;
10471 }
10472 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10473 Expr::WindowFunction {
10476 args,
10477 partition_by,
10478 order_by,
10479 ..
10480 } => {
10481 for a in args {
10482 self.resolve_expr_subqueries(a, cancel)?;
10483 }
10484 for p in partition_by {
10485 self.resolve_expr_subqueries(p, cancel)?;
10486 }
10487 for (e, _, _) in order_by {
10488 self.resolve_expr_subqueries(e, cancel)?;
10489 }
10490 }
10491 Expr::ScalarSubquery(_)
10495 | Expr::Exists { .. }
10496 | Expr::InSubquery { .. }
10497 | Expr::Literal(_)
10498 | Expr::Placeholder(_)
10499 | Expr::Column(_) => {}
10500 Expr::Array(items) => {
10502 for elem in items {
10503 self.resolve_expr_subqueries(elem, cancel)?;
10504 }
10505 }
10506 Expr::ArraySubscript { target, index } => {
10507 self.resolve_expr_subqueries(target, cancel)?;
10508 self.resolve_expr_subqueries(index, cancel)?;
10509 }
10510 Expr::AnyAll { expr, array, .. } => {
10511 self.resolve_expr_subqueries(expr, cancel)?;
10512 self.resolve_expr_subqueries(array, cancel)?;
10513 }
10514 Expr::Case {
10515 operand,
10516 branches,
10517 else_branch,
10518 } => {
10519 if let Some(o) = operand {
10520 self.resolve_expr_subqueries(o, cancel)?;
10521 }
10522 for (w, t) in branches {
10523 self.resolve_expr_subqueries(w, cancel)?;
10524 self.resolve_expr_subqueries(t, cancel)?;
10525 }
10526 if let Some(e) = else_branch {
10527 self.resolve_expr_subqueries(e, cancel)?;
10528 }
10529 }
10530 }
10531 Ok(())
10532 }
10533
10534 fn eval_expr_with_correlated(
10542 &self,
10543 expr: &Expr,
10544 row: &Row,
10545 ctx: &EvalContext<'_>,
10546 cancel: CancelToken<'_>,
10547 memo: Option<&mut memoize::MemoizeCache>,
10548 ) -> Result<Value, EngineError> {
10549 if !expr_has_subquery(expr) {
10550 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10551 }
10552 let mut e = expr.clone();
10553 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10554 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10555 }
10556
10557 fn resolve_correlated_in_expr(
10558 &self,
10559 e: &mut Expr,
10560 row: &Row,
10561 ctx: &EvalContext<'_>,
10562 cancel: CancelToken<'_>,
10563 mut memo: Option<&mut memoize::MemoizeCache>,
10564 ) -> Result<(), EngineError> {
10565 match e {
10566 Expr::AggregateOrdered { call, order_by, .. } => {
10567 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
10568 for o in order_by.iter_mut() {
10569 self.resolve_correlated_in_expr(
10570 &mut o.expr,
10571 row,
10572 ctx,
10573 cancel,
10574 memo.as_deref_mut(),
10575 )?;
10576 }
10577 }
10578 Expr::ScalarSubquery(inner) => {
10579 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10584 subquery_repr: alloc::format!("{}", **inner),
10585 outer_values: row.values.clone(),
10586 });
10587 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10588 && let Some(cached) = cache.get(k)
10589 {
10590 *e = value_to_literal_expr(cached)?;
10591 return Ok(());
10592 }
10593 let mut s = (**inner).clone();
10594 substitute_outer_columns(&mut s, row, ctx);
10595 let r = self.exec_select_cancel(&s, cancel)?;
10596 let QueryResult::Rows { rows, .. } = r else {
10597 return Err(EngineError::Unsupported(
10598 "scalar subquery: inner did not return rows".into(),
10599 ));
10600 };
10601 let value = match rows.as_slice() {
10602 [] => Value::Null,
10603 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10604 _ => {
10605 return Err(EngineError::Unsupported(alloc::format!(
10606 "scalar subquery returned {} rows; expected 0 or 1",
10607 rows.len()
10608 )));
10609 }
10610 };
10611 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10612 cache.insert(k, value.clone());
10613 }
10614 *e = value_to_literal_expr(value)?;
10615 }
10616 Expr::Exists { subquery, negated } => {
10617 let mut s = (**subquery).clone();
10618 substitute_outer_columns(&mut s, row, ctx);
10619 let r = self.exec_select_cancel(&s, cancel)?;
10620 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10621 let bit = if *negated { !exists } else { exists };
10622 *e = Expr::Literal(Literal::Bool(bit));
10623 }
10624 Expr::InSubquery {
10625 expr: lhs,
10626 subquery,
10627 negated,
10628 } => {
10629 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10630 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10631 let mut s = (**subquery).clone();
10632 substitute_outer_columns(&mut s, row, ctx);
10633 let r = self.exec_select_cancel(&s, cancel)?;
10634 let QueryResult::Rows { columns, rows, .. } = r else {
10635 return Err(EngineError::Unsupported(
10636 "IN-subquery: inner did not return rows".into(),
10637 ));
10638 };
10639 if columns.len() != 1 {
10640 return Err(EngineError::Unsupported(alloc::format!(
10641 "IN-subquery must project exactly one column; got {}",
10642 columns.len()
10643 )));
10644 }
10645 let mut found = false;
10646 let mut any_null = false;
10647 for r0 in rows {
10648 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10649 if v.is_null() {
10650 any_null = true;
10651 continue;
10652 }
10653 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10654 found = true;
10655 break;
10656 }
10657 }
10658 let bit = if found {
10659 !*negated
10660 } else if any_null {
10661 return Err(EngineError::Unsupported(
10662 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10663 ));
10664 } else {
10665 *negated
10666 };
10667 *e = Expr::Literal(Literal::Bool(bit));
10668 }
10669 Expr::Binary { lhs, rhs, .. } => {
10670 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10671 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10672 }
10673 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10674 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10675 }
10676 Expr::Like { expr, pattern, .. } => {
10677 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10678 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10679 }
10680 Expr::FunctionCall { args, .. } => {
10681 for a in args {
10682 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10683 }
10684 }
10685 Expr::Extract { source, .. } => {
10686 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10687 }
10688 Expr::WindowFunction { .. }
10689 | Expr::Literal(_)
10690 | Expr::Placeholder(_)
10691 | Expr::Column(_) => {}
10692 Expr::Array(items) => {
10694 for elem in items {
10695 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10696 }
10697 }
10698 Expr::ArraySubscript { target, index } => {
10699 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10700 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10701 }
10702 Expr::AnyAll { expr, array, .. } => {
10703 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10704 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10705 }
10706 Expr::Case {
10707 operand,
10708 branches,
10709 else_branch,
10710 } => {
10711 if let Some(o) = operand {
10712 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10713 }
10714 for (w, t) in branches {
10715 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10716 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10717 }
10718 if let Some(e) = else_branch {
10719 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10720 }
10721 }
10722 }
10723 Ok(())
10724 }
10725
10726 fn subquery_replacement(
10727 &self,
10728 e: &Expr,
10729 cancel: CancelToken<'_>,
10730 ) -> Result<Option<Expr>, EngineError> {
10731 match e {
10732 Expr::ScalarSubquery(inner) => {
10733 let mut s = (**inner).clone();
10734 self.resolve_select_subqueries(&mut s, cancel)?;
10737 let r = match self.exec_bare_select_cancel(&s, cancel) {
10738 Ok(r) => r,
10739 Err(e) if is_correlation_error(&e) => return Ok(None),
10740 Err(e) => return Err(e),
10741 };
10742 let QueryResult::Rows { rows, .. } = r else {
10743 return Err(EngineError::Unsupported(
10744 "scalar subquery: inner statement did not return rows".into(),
10745 ));
10746 };
10747 let value = match rows.as_slice() {
10748 [] => Value::Null,
10749 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10750 _ => {
10751 return Err(EngineError::Unsupported(alloc::format!(
10752 "scalar subquery returned {} rows; expected 0 or 1",
10753 rows.len()
10754 )));
10755 }
10756 };
10757 Ok(Some(value_to_literal_expr(value)?))
10758 }
10759 Expr::Exists { subquery, negated } => {
10760 let mut s = (**subquery).clone();
10761 self.resolve_select_subqueries(&mut s, cancel)?;
10762 let r = match self.exec_bare_select_cancel(&s, cancel) {
10763 Ok(r) => r,
10764 Err(e) if is_correlation_error(&e) => return Ok(None),
10765 Err(e) => return Err(e),
10766 };
10767 let exists = match r {
10768 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10769 QueryResult::CommandOk { .. } => false,
10770 };
10771 let bit = if *negated { !exists } else { exists };
10772 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10773 }
10774 Expr::InSubquery {
10775 expr,
10776 subquery,
10777 negated,
10778 } => {
10779 let mut s = (**subquery).clone();
10780 self.resolve_select_subqueries(&mut s, cancel)?;
10781 let r = match self.exec_bare_select_cancel(&s, cancel) {
10782 Ok(r) => r,
10783 Err(e) if is_correlation_error(&e) => return Ok(None),
10784 Err(e) => return Err(e),
10785 };
10786 let QueryResult::Rows { columns, rows, .. } = r else {
10787 return Err(EngineError::Unsupported(
10788 "IN-subquery: inner statement did not return rows".into(),
10789 ));
10790 };
10791 if columns.len() != 1 {
10792 return Err(EngineError::Unsupported(alloc::format!(
10793 "IN-subquery must project exactly one column; got {}",
10794 columns.len()
10795 )));
10796 }
10797 let mut acc: Option<Expr> = None;
10800 for row in rows {
10801 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10802 let lit = value_to_literal_expr(v)?;
10803 let cmp = Expr::Binary {
10804 lhs: expr.clone(),
10805 op: BinOp::Eq,
10806 rhs: Box::new(lit),
10807 };
10808 acc = Some(match acc {
10809 None => cmp,
10810 Some(prev) => Expr::Binary {
10811 lhs: Box::new(prev),
10812 op: BinOp::Or,
10813 rhs: Box::new(cmp),
10814 },
10815 });
10816 }
10817 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10818 let final_expr = if *negated {
10819 Expr::Unary {
10820 op: UnOp::Not,
10821 expr: Box::new(combined),
10822 }
10823 } else {
10824 combined
10825 };
10826 Ok(Some(final_expr))
10827 }
10828 _ => Ok(None),
10829 }
10830 }
10831}
10832
10833fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10845 if let Some(from) = &stmt.from
10846 && from_refers_to(from, target)
10847 {
10848 return true;
10849 }
10850 for (_, peer) in &stmt.unions {
10851 if select_refers_to(peer, target) {
10852 return true;
10853 }
10854 }
10855 for item in &stmt.items {
10856 if let SelectItem::Expr { expr, .. } = item
10857 && expr_refers_to(expr, target)
10858 {
10859 return true;
10860 }
10861 }
10862 if let Some(w) = &stmt.where_
10863 && expr_refers_to(w, target)
10864 {
10865 return true;
10866 }
10867 false
10868}
10869
10870fn from_refers_to(from: &FromClause, target: &str) -> bool {
10871 if from.primary.name.eq_ignore_ascii_case(target) {
10872 return true;
10873 }
10874 from.joins
10875 .iter()
10876 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10877}
10878
10879fn expr_refers_to(e: &Expr, target: &str) -> bool {
10880 match e {
10881 Expr::AggregateOrdered { call, order_by, .. } => {
10882 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
10883 }
10884 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10885 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10886 select_refers_to(subquery, target)
10887 }
10888 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10889 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10890 expr_refers_to(expr, target)
10891 }
10892 Expr::Like { expr, pattern, .. } => {
10893 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10894 }
10895 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10896 Expr::Extract { source, .. } => expr_refers_to(source, target),
10897 Expr::WindowFunction {
10898 args,
10899 partition_by,
10900 order_by,
10901 ..
10902 } => {
10903 args.iter().any(|a| expr_refers_to(a, target))
10904 || partition_by.iter().any(|p| expr_refers_to(p, target))
10905 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
10906 }
10907 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10908 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10909 Expr::ArraySubscript { target: t, index } => {
10910 expr_refers_to(t, target) || expr_refers_to(index, target)
10911 }
10912 Expr::AnyAll { expr, array, .. } => {
10913 expr_refers_to(expr, target) || expr_refers_to(array, target)
10914 }
10915 Expr::Case {
10916 operand,
10917 branches,
10918 else_branch,
10919 } => {
10920 operand
10921 .as_deref()
10922 .is_some_and(|o| expr_refers_to(o, target))
10923 || branches
10924 .iter()
10925 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10926 || else_branch
10927 .as_deref()
10928 .is_some_and(|e| expr_refers_to(e, target))
10929 }
10930 }
10931}
10932
10933fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10944 let s = match ty {
10945 DataType::Int => "integer",
10946 DataType::BigInt => "bigint",
10947 DataType::SmallInt => "smallint",
10948 DataType::Float => "double precision",
10949 DataType::Bool => "boolean",
10950 DataType::Text => "text",
10951 DataType::Varchar(_) => "character varying",
10952 DataType::Date => "date",
10953 DataType::Timestamp => "timestamp without time zone",
10954 DataType::Timestamptz => "timestamp with time zone",
10955 DataType::Json => "jsonb",
10956 DataType::Bytes => "bytea",
10957 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10958 DataType::TsVector => "tsvector",
10959 DataType::TsQuery => "tsquery",
10960 DataType::Vector { .. } => "USER-DEFINED",
10961 _ => "USER-DEFINED",
10964 };
10965 alloc::string::String::from(s)
10966}
10967
10968fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10975 let schema = alloc::vec![
10976 ColumnSchema::new("table_catalog", DataType::Text, false),
10977 ColumnSchema::new("table_schema", DataType::Text, false),
10978 ColumnSchema::new("table_name", DataType::Text, false),
10979 ColumnSchema::new("column_name", DataType::Text, false),
10980 ColumnSchema::new("ordinal_position", DataType::Int, false),
10981 ColumnSchema::new("is_nullable", DataType::Text, false),
10982 ColumnSchema::new("data_type", DataType::Text, false),
10983 ];
10984 let mut rows: Vec<Row> = Vec::new();
10985 for tname in cat.table_names() {
10986 let Some(t) = cat.get(&tname) else { continue };
10987 for (i, col) in t.schema().columns.iter().enumerate() {
10988 #[allow(clippy::cast_possible_wrap)]
10989 let ordinal = (i + 1) as i32;
10990 rows.push(Row::new(alloc::vec![
10991 Value::Text("spg".into()),
10992 Value::Text("public".into()),
10993 Value::Text(tname.clone()),
10994 Value::Text(col.name.clone()),
10995 Value::Int(ordinal),
10996 Value::Text(if col.nullable {
10997 "YES".into()
10998 } else {
10999 "NO".into()
11000 }),
11001 Value::Text(pg_data_type_text(col.ty)),
11002 ]));
11003 }
11004 }
11005 (schema, rows)
11006}
11007
11008fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11010 let schema = alloc::vec![
11011 ColumnSchema::new("table_catalog", DataType::Text, false),
11012 ColumnSchema::new("table_schema", DataType::Text, false),
11013 ColumnSchema::new("table_name", DataType::Text, false),
11014 ColumnSchema::new("table_type", DataType::Text, false),
11015 ];
11016 let mut rows: Vec<Row> = Vec::new();
11017 for tname in cat.table_names() {
11018 rows.push(Row::new(alloc::vec![
11019 Value::Text("spg".into()),
11020 Value::Text("public".into()),
11021 Value::Text(tname.clone()),
11022 Value::Text("BASE TABLE".into()),
11023 ]));
11024 }
11025 (schema, rows)
11026}
11027
11028fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11032 let schema = alloc::vec![
11033 ColumnSchema::new("relname", DataType::Text, false),
11034 ColumnSchema::new("relkind", DataType::Text, false),
11035 ColumnSchema::new("relnamespace", DataType::BigInt, false),
11036 ];
11037 let mut rows: Vec<Row> = Vec::new();
11038 for tname in cat.table_names() {
11039 rows.push(Row::new(alloc::vec![
11040 Value::Text(tname.clone()),
11041 Value::Text("r".into()),
11042 Value::BigInt(2200), ]));
11044 }
11045 (schema, rows)
11046}
11047
11048fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11052 let schema = alloc::vec![
11053 ColumnSchema::new("attrelid", DataType::Text, false),
11054 ColumnSchema::new("attname", DataType::Text, false),
11055 ColumnSchema::new("attnum", DataType::Int, false),
11056 ColumnSchema::new("atttypid", DataType::Text, false),
11057 ColumnSchema::new("attnotnull", DataType::Bool, false),
11058 ];
11059 let mut rows: Vec<Row> = Vec::new();
11060 for tname in cat.table_names() {
11061 let Some(t) = cat.get(&tname) else { continue };
11062 for (i, col) in t.schema().columns.iter().enumerate() {
11063 #[allow(clippy::cast_possible_wrap)]
11064 let ordinal = (i + 1) as i32;
11065 rows.push(Row::new(alloc::vec![
11066 Value::Text(tname.clone()),
11067 Value::Text(col.name.clone()),
11068 Value::Int(ordinal),
11069 Value::Text(pg_data_type_text(col.ty)),
11070 Value::Bool(!col.nullable),
11071 ]));
11072 }
11073 }
11074 (schema, rows)
11075}
11076
11077fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11094 let schema = alloc::vec![
11095 ColumnSchema::new("oid", DataType::BigInt, false),
11096 ColumnSchema::new("typname", DataType::Text, false),
11097 ColumnSchema::new("typlen", DataType::SmallInt, false),
11098 ColumnSchema::new("typtype", DataType::Text, false),
11099 ColumnSchema::new("typcategory", DataType::Text, false),
11100 ColumnSchema::new("typelem", DataType::BigInt, false),
11101 ColumnSchema::new("typarray", DataType::BigInt, false),
11102 ColumnSchema::new("typnamespace", DataType::BigInt, false),
11103 ];
11104 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
11107 (16, "bool", 1, "b", "B", 0, 1000),
11109 (17, "bytea", -1, "b", "U", 0, 1001),
11110 (18, "char", 1, "b", "S", 0, 1002),
11111 (19, "name", 64, "b", "S", 0, 1003),
11112 (20, "int8", 8, "b", "N", 0, 1016),
11113 (21, "int2", 2, "b", "N", 0, 1005),
11114 (23, "int4", 4, "b", "N", 0, 1007),
11115 (24, "regproc", 4, "b", "N", 0, 1008),
11116 (25, "text", -1, "b", "S", 0, 1009),
11117 (26, "oid", 4, "b", "N", 0, 1028),
11118 (114, "json", -1, "b", "U", 0, 199),
11119 (142, "xml", -1, "b", "U", 0, 143),
11120 (700, "float4", 4, "b", "N", 0, 1021),
11121 (701, "float8", 8, "b", "N", 0, 1022),
11122 (650, "cidr", -1, "b", "I", 0, 651),
11123 (869, "inet", -1, "b", "I", 0, 1041),
11124 (829, "macaddr", 6, "b", "U", 0, 1040),
11125 (1042, "bpchar", -1, "b", "S", 0, 1014),
11126 (1043, "varchar", -1, "b", "S", 0, 1015),
11127 (1082, "date", 4, "b", "D", 0, 1182),
11128 (1083, "time", 8, "b", "D", 0, 1183),
11129 (1114, "timestamp", 8, "b", "D", 0, 1115),
11130 (1184, "timestamptz", 8, "b", "D", 0, 1185),
11131 (1186, "interval", 16, "b", "T", 0, 1187),
11132 (1266, "timetz", 12, "b", "D", 0, 1270),
11133 (1700, "numeric", -1, "b", "N", 0, 1231),
11134 (790, "money", 8, "b", "N", 0, 791),
11135 (2950, "uuid", 16, "b", "U", 0, 2951),
11136 (3802, "jsonb", -1, "b", "U", 0, 3807),
11137 (3614, "tsvector", -1, "b", "U", 0, 3643),
11138 (3615, "tsquery", -1, "b", "U", 0, 3645),
11139 (3908, "tstzrange", -1, "r", "R", 0, 3909),
11141 (3910, "tsrange", -1, "r", "R", 0, 3911),
11142 (3904, "int4range", -1, "r", "R", 0, 3905),
11143 (3926, "int8range", -1, "r", "R", 0, 3927),
11144 (3906, "numrange", -1, "r", "R", 0, 3907),
11145 (3912, "daterange", -1, "r", "R", 0, 3913),
11146 ];
11147 let arrays: &[(i64, &str, i64)] = &[
11150 (1000, "_bool", 16),
11151 (1001, "_bytea", 17),
11152 (1002, "_char", 18),
11153 (1003, "_name", 19),
11154 (1016, "_int8", 20),
11155 (1005, "_int2", 21),
11156 (1007, "_int4", 23),
11157 (1008, "_regproc", 24),
11158 (1009, "_text", 25),
11159 (1028, "_oid", 26),
11160 (199, "_json", 114),
11161 (143, "_xml", 142),
11162 (1021, "_float4", 700),
11163 (1022, "_float8", 701),
11164 (651, "_cidr", 650),
11165 (1041, "_inet", 869),
11166 (1040, "_macaddr", 829),
11167 (1014, "_bpchar", 1042),
11168 (1015, "_varchar", 1043),
11169 (1182, "_date", 1082),
11170 (1183, "_time", 1083),
11171 (1115, "_timestamp", 1114),
11172 (1185, "_timestamptz", 1184),
11173 (1187, "_interval", 1186),
11174 (1270, "_timetz", 1266),
11175 (1231, "_numeric", 1700),
11176 (791, "_money", 790),
11177 (2951, "_uuid", 2950),
11178 (3807, "_jsonb", 3802),
11179 (3643, "_tsvector", 3614),
11180 (3645, "_tsquery", 3615),
11181 ];
11182 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
11183 for &(oid, name, len, ty, cat, elem, arr) in scalars {
11184 rows.push(Row::new(alloc::vec![
11185 Value::BigInt(oid),
11186 Value::Text(name.into()),
11187 Value::SmallInt(len),
11188 Value::Text(ty.into()),
11189 Value::Text(cat.into()),
11190 Value::BigInt(elem),
11191 Value::BigInt(arr),
11192 Value::BigInt(2200),
11193 ]));
11194 }
11195 for &(oid, name, elem) in arrays {
11196 rows.push(Row::new(alloc::vec![
11197 Value::BigInt(oid),
11198 Value::Text(name.into()),
11199 Value::SmallInt(-1),
11200 Value::Text("b".into()),
11201 Value::Text("A".into()),
11202 Value::BigInt(elem),
11203 Value::BigInt(0),
11204 Value::BigInt(2200),
11205 ]));
11206 }
11207 (schema, rows)
11208}
11209
11210fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11229 let schema = alloc::vec![
11230 ColumnSchema::new("tgname", DataType::Text, false),
11231 ColumnSchema::new("relname", DataType::Text, false),
11232 ColumnSchema::new("tgenabled", DataType::Text, false),
11233 ColumnSchema::new("timing", DataType::Text, false),
11234 ColumnSchema::new("events", DataType::Text, false),
11235 ColumnSchema::new("function", DataType::Text, false),
11236 ];
11237 let rows: Vec<Row> = cat
11238 .triggers()
11239 .iter()
11240 .map(|t| {
11241 Row::new(alloc::vec![
11242 Value::Text(t.name.clone()),
11243 Value::Text(t.table.clone()),
11244 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
11245 Value::Text(t.timing.clone()),
11246 Value::Text(t.events.join(" OR ")),
11247 Value::Text(t.function.clone()),
11248 ])
11249 })
11250 .collect();
11251 (schema, rows)
11252}
11253
11254fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11255 let schema = alloc::vec![
11256 ColumnSchema::new("oid", DataType::BigInt, false),
11257 ColumnSchema::new("proname", DataType::Text, false),
11258 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11259 ColumnSchema::new("prokind", DataType::Text, false),
11260 ColumnSchema::new("pronargs", DataType::Int, false),
11261 ColumnSchema::new("prorettype", DataType::BigInt, false),
11262 ];
11263 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11266 (1318, "length", "f", 1, 23),
11268 (871, "upper", "f", 1, 25),
11269 (870, "lower", "f", 1, 25),
11270 (936, "substring", "f", 3, 25),
11271 (937, "substring", "f", 2, 25),
11272 (3055, "btrim", "f", 1, 25),
11273 (885, "btrim", "f", 2, 25),
11274 (3056, "ltrim", "f", 1, 25),
11275 (875, "ltrim", "f", 2, 25),
11276 (3057, "rtrim", "f", 1, 25),
11277 (876, "rtrim", "f", 2, 25),
11278 (1397, "abs", "f", 1, 23),
11279 (1396, "abs", "f", 1, 20),
11280 (1606, "round", "f", 1, 1700),
11281 (1707, "round", "f", 2, 1700),
11282 (2308, "ceil", "f", 1, 701),
11283 (2309, "ceiling", "f", 1, 701),
11284 (2310, "floor", "f", 1, 701),
11285 (1376, "sqrt", "f", 1, 701),
11286 (1369, "ln", "f", 1, 701),
11287 (1373, "exp", "f", 1, 701),
11288 (1368, "power", "f", 2, 701),
11289 (2228, "random", "f", 0, 701),
11290 (1299, "now", "f", 0, 1184),
11292 (1274, "current_timestamp", "f", 0, 1184),
11293 (1140, "current_date", "f", 0, 1082),
11294 (2050, "current_time", "f", 0, 1083),
11295 (1158, "date_trunc", "f", 2, 1184),
11296 (1171, "date_part", "f", 2, 701),
11297 (1172, "age", "f", 1, 1186),
11298 (936, "to_char", "f", 2, 25),
11299 (861, "current_database", "f", 0, 19),
11301 (745, "current_user", "f", 0, 19),
11302 (745, "session_user", "f", 0, 19),
11303 (1402, "current_schema", "f", 0, 19),
11304 (3058, "concat", "f", -1, 25),
11306 (3059, "concat_ws", "f", -1, 25),
11307 (3539, "format", "f", -1, 25),
11308 (2877, "pg_typeof", "f", 1, 2206),
11310 (3198, "json_build_object", "f", -1, 114),
11312 (3199, "jsonb_build_object", "f", -1, 3802),
11313 (3271, "json_build_array", "f", -1, 114),
11314 (3272, "jsonb_build_array", "f", -1, 3802),
11315 (3253, "gen_random_uuid", "f", 0, 2950),
11317 (3252, "uuid_generate_v4", "f", 0, 2950),
11318 (2147, "count", "a", 0, 20),
11320 (2803, "count", "a", -1, 20),
11321 (2116, "max", "a", 1, 23),
11322 (2132, "min", "a", 1, 23),
11323 (2108, "sum", "a", 1, 20),
11324 (2100, "avg", "a", 1, 1700),
11325 (2517, "string_agg", "a", 2, 25),
11326 (2747, "array_agg", "a", 1, 1009),
11327 (2517, "bool_and", "a", 1, 16),
11328 (2518, "bool_or", "a", 1, 16),
11329 (2519, "every", "a", 1, 16),
11330 (3100, "row_number", "w", 0, 20),
11332 (3101, "rank", "w", 0, 20),
11333 (3102, "dense_rank", "w", 0, 20),
11334 (3103, "percent_rank", "w", 0, 701),
11335 (3104, "cume_dist", "w", 0, 701),
11336 (3105, "lag", "w", -1, 2283),
11337 (3106, "lead", "w", -1, 2283),
11338 (3107, "first_value", "w", 1, 2283),
11339 (3108, "last_value", "w", 1, 2283),
11340 (3109, "nth_value", "w", 2, 2283),
11341 ];
11342 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11343 for &(oid, name, kind, nargs, rettype) in funcs {
11344 rows.push(Row::new(alloc::vec![
11345 Value::BigInt(oid),
11346 Value::Text(name.into()),
11347 Value::BigInt(11),
11348 Value::Text(kind.into()),
11349 Value::Int(nargs),
11350 Value::BigInt(rettype),
11351 ]));
11352 }
11353 (schema, rows)
11354}
11355
11356fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11362 let schema = alloc::vec![
11363 ColumnSchema::new("user", DataType::Text, false),
11364 ColumnSchema::new("host", DataType::Text, false),
11365 ColumnSchema::new("select_priv", DataType::Text, false),
11366 ];
11367 let mut rows: Vec<Row> = Vec::new();
11368 rows.push(Row::new(alloc::vec![
11369 Value::Text("root".into()),
11370 Value::Text("localhost".into()),
11371 Value::Text("Y".into()),
11372 ]));
11373 for (name, _) in engine.users.iter() {
11374 if name != "root" {
11375 rows.push(Row::new(alloc::vec![
11376 Value::Text(name.to_string()),
11377 Value::Text("%".into()),
11378 Value::Text("Y".into()),
11379 ]));
11380 }
11381 }
11382 (schema, rows)
11383}
11384
11385fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11390 let schema = alloc::vec![
11391 ColumnSchema::new("host", DataType::Text, false),
11392 ColumnSchema::new("db", DataType::Text, false),
11393 ColumnSchema::new("user", DataType::Text, false),
11394 ColumnSchema::new("select_priv", DataType::Text, false),
11395 ];
11396 let rows = alloc::vec![Row::new(alloc::vec![
11397 Value::Text("localhost".into()),
11398 Value::Text("postgres".into()),
11399 Value::Text("root".into()),
11400 Value::Text("Y".into()),
11401 ])];
11402 (schema, rows)
11403}
11404
11405fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11418 let schema = alloc::vec![
11419 ColumnSchema::new("constraint_name", DataType::Text, false),
11420 ColumnSchema::new("table_name", DataType::Text, false),
11421 ColumnSchema::new("column_name", DataType::Text, false),
11422 ColumnSchema::new("ordinal_position", DataType::Int, false),
11423 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11424 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11425 ];
11426 let mut rows: Vec<Row> = Vec::new();
11427 for tname in cat.table_names() {
11428 let Some(t) = cat.get(&tname) else { continue };
11429 let cols = &t.schema().columns;
11430 let col_name_at = |pos: usize| -> String {
11431 cols.get(pos)
11432 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11433 };
11434 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11436 let conname = fk
11437 .name
11438 .clone()
11439 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11440 for (i, (&local, &parent)) in fk
11441 .local_columns
11442 .iter()
11443 .zip(fk.parent_columns.iter())
11444 .enumerate()
11445 {
11446 let parent_name = cat
11447 .get(&fk.parent_table)
11448 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11449 .unwrap_or_else(|| alloc::format!("col{parent}"));
11450 #[allow(clippy::cast_possible_wrap)]
11451 let ordinal = (i + 1) as i32;
11452 rows.push(Row::new(alloc::vec![
11453 Value::Text(conname.clone()),
11454 Value::Text(tname.clone()),
11455 Value::Text(col_name_at(local)),
11456 Value::Int(ordinal),
11457 Value::Text(fk.parent_table.clone()),
11458 Value::Text(parent_name),
11459 ]));
11460 }
11461 }
11462 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11464 let conname = if uc.is_primary_key {
11465 alloc::format!("{}_pkey", tname)
11466 } else {
11467 alloc::format!("{}_uniq{ci}", tname)
11468 };
11469 for (i, &local) in uc.columns.iter().enumerate() {
11470 #[allow(clippy::cast_possible_wrap)]
11471 let ordinal = (i + 1) as i32;
11472 rows.push(Row::new(alloc::vec![
11473 Value::Text(conname.clone()),
11474 Value::Text(tname.clone()),
11475 Value::Text(col_name_at(local)),
11476 Value::Int(ordinal),
11477 Value::Text(String::new()),
11478 Value::Text(String::new()),
11479 ]));
11480 }
11481 }
11482 }
11483 (schema, rows)
11484}
11485
11486fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11489 let schema = alloc::vec![
11490 ColumnSchema::new("constraint_name", DataType::Text, false),
11491 ColumnSchema::new("table_name", DataType::Text, false),
11492 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11493 ColumnSchema::new("update_rule", DataType::Text, false),
11494 ColumnSchema::new("delete_rule", DataType::Text, false),
11495 ];
11496 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11497 match a {
11498 spg_storage::FkAction::Cascade => "CASCADE",
11499 spg_storage::FkAction::SetNull => "SET NULL",
11500 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11501 spg_storage::FkAction::Restrict => "RESTRICT",
11502 spg_storage::FkAction::NoAction => "NO ACTION",
11503 }
11504 }
11505 let mut rows: Vec<Row> = Vec::new();
11506 for tname in cat.table_names() {
11507 let Some(t) = cat.get(&tname) else { continue };
11508 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11509 let conname = fk
11510 .name
11511 .clone()
11512 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11513 rows.push(Row::new(alloc::vec![
11514 Value::Text(conname),
11515 Value::Text(tname.clone()),
11516 Value::Text(fk.parent_table.clone()),
11517 Value::Text(rule_name(fk.on_update).into()),
11518 Value::Text(rule_name(fk.on_delete).into()),
11519 ]));
11520 }
11521 }
11522 (schema, rows)
11523}
11524
11525fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11529 let schema = alloc::vec![
11530 ColumnSchema::new("table_name", DataType::Text, false),
11531 ColumnSchema::new("index_name", DataType::Text, false),
11532 ColumnSchema::new("column_name", DataType::Text, false),
11533 ColumnSchema::new("seq_in_index", DataType::Int, false),
11534 ColumnSchema::new("non_unique", DataType::Int, false),
11535 ColumnSchema::new("index_type", DataType::Text, false),
11536 ];
11537 let mut rows: Vec<Row> = Vec::new();
11538 for tname in cat.table_names() {
11539 let Some(t) = cat.get(&tname) else { continue };
11540 for idx in t.indices() {
11541 let col = t
11542 .schema()
11543 .columns
11544 .get(idx.column_position)
11545 .map_or("?".into(), |c| c.name.clone());
11546 rows.push(Row::new(alloc::vec![
11547 Value::Text(tname.clone()),
11548 Value::Text(idx.name.clone()),
11549 Value::Text(col),
11550 Value::Int(1),
11551 Value::Int(i32::from(!idx.is_unique)),
11552 Value::Text("BTREE".into()),
11553 ]));
11554 }
11555 }
11556 (schema, rows)
11557}
11558
11559fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11563 let schema = alloc::vec![
11564 ColumnSchema::new("routine_name", DataType::Text, false),
11565 ColumnSchema::new("routine_type", DataType::Text, false),
11566 ColumnSchema::new("data_type", DataType::Text, false),
11567 ];
11568 (schema, Vec::new())
11569}
11570
11571fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11586 let schema = alloc::vec![
11587 ColumnSchema::new("conname", DataType::Text, false),
11588 ColumnSchema::new("contype", DataType::Text, false),
11589 ColumnSchema::new("conrelid", DataType::Text, false),
11590 ColumnSchema::new("confrelid", DataType::Text, false),
11591 ColumnSchema::new("conkey", DataType::Text, false),
11592 ColumnSchema::new("confkey", DataType::Text, false),
11593 ];
11594 let mut rows: Vec<Row> = Vec::new();
11595 for tname in cat.table_names() {
11596 let Some(t) = cat.get(&tname) else { continue };
11597 let cols = &t.schema().columns;
11598 let col_name_at = |pos: usize| -> String {
11599 cols.get(pos)
11600 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11601 };
11602 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11604 let kind = if uc.is_primary_key { "p" } else { "u" };
11605 let conname = if uc.is_primary_key {
11606 alloc::format!("{}_pkey", tname)
11607 } else {
11608 alloc::format!("{}_uniq{ci}", tname)
11609 };
11610 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11611 rows.push(Row::new(alloc::vec![
11612 Value::Text(conname),
11613 Value::Text(kind.into()),
11614 Value::Text(tname.clone()),
11615 Value::Text(String::new()),
11616 Value::Text(conkey.join(",")),
11617 Value::Text(String::new()),
11618 ]));
11619 }
11620 for idx in t.indices() {
11625 if !idx.is_unique {
11626 continue;
11627 }
11628 let is_primary = idx.name.ends_with("_pkey");
11629 let conname = idx.name.clone();
11630 let kind = if is_primary { "p" } else { "u" };
11631 let col_name = col_name_at(idx.column_position);
11632 let already = t
11635 .schema()
11636 .uniqueness_constraints
11637 .iter()
11638 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11639 if already {
11640 continue;
11641 }
11642 rows.push(Row::new(alloc::vec![
11643 Value::Text(conname),
11644 Value::Text(kind.into()),
11645 Value::Text(tname.clone()),
11646 Value::Text(String::new()),
11647 Value::Text(col_name),
11648 Value::Text(String::new()),
11649 ]));
11650 }
11651 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11653 let conname = fk
11654 .name
11655 .clone()
11656 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11657 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11658 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11661 fk.parent_columns
11662 .iter()
11663 .map(|&p| {
11664 parent
11665 .schema()
11666 .columns
11667 .get(p)
11668 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11669 })
11670 .collect()
11671 } else {
11672 fk.parent_columns
11673 .iter()
11674 .map(|p| alloc::format!("col{p}"))
11675 .collect()
11676 };
11677 rows.push(Row::new(alloc::vec![
11678 Value::Text(conname),
11679 Value::Text("f".into()),
11680 Value::Text(tname.clone()),
11681 Value::Text(fk.parent_table.clone()),
11682 Value::Text(conkey.join(",")),
11683 Value::Text(confkey.join(",")),
11684 ]));
11685 }
11686 }
11687 (schema, rows)
11688}
11689
11690fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11695 let schema = alloc::vec![
11696 ColumnSchema::new("oid", DataType::BigInt, false),
11697 ColumnSchema::new("datname", DataType::Text, false),
11698 ColumnSchema::new("datdba", DataType::BigInt, false),
11699 ColumnSchema::new("encoding", DataType::Int, false),
11700 ColumnSchema::new("datcollate", DataType::Text, false),
11701 ];
11702 let rows = alloc::vec![Row::new(alloc::vec![
11703 Value::BigInt(16384),
11704 Value::Text("postgres".into()),
11705 Value::BigInt(10),
11706 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11708 ])];
11709 (schema, rows)
11710}
11711
11712fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11717 let schema = alloc::vec![
11718 ColumnSchema::new("oid", DataType::BigInt, false),
11719 ColumnSchema::new("rolname", DataType::Text, false),
11720 ColumnSchema::new("rolsuper", DataType::Bool, false),
11721 ColumnSchema::new("rolinherit", DataType::Bool, false),
11722 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11723 ];
11724 let mut rows: Vec<Row> = Vec::new();
11725 let oid: i64 = 10;
11726 for (i, (name, _)) in engine.users.iter().enumerate() {
11727 rows.push(Row::new(alloc::vec![
11728 Value::BigInt(oid + (i as i64) + 1),
11729 Value::Text(name.to_string()),
11730 Value::Bool(false),
11731 Value::Bool(true),
11732 Value::Bool(true),
11733 ]));
11734 }
11735 if !rows
11738 .iter()
11739 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11740 {
11741 rows.insert(
11742 0,
11743 Row::new(alloc::vec![
11744 Value::BigInt(10),
11745 Value::Text("postgres".into()),
11746 Value::Bool(true),
11747 Value::Bool(true),
11748 Value::Bool(true),
11749 ]),
11750 );
11751 }
11752 (schema, rows)
11753}
11754
11755fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11764 let schema = alloc::vec![
11765 ColumnSchema::new("oid", DataType::BigInt, false),
11766 ColumnSchema::new("extname", DataType::Text, false),
11767 ColumnSchema::new("extversion", DataType::Text, false),
11768 ColumnSchema::new("extnamespace", DataType::Text, false),
11769 ];
11770 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11771 let rows = exts
11772 .iter()
11773 .enumerate()
11774 .map(|(i, (name, ver))| {
11775 Row::new(alloc::vec![
11776 Value::BigInt(16384 + i as i64),
11777 Value::Text((*name).into()),
11778 Value::Text((*ver).into()),
11779 Value::Text("pg_catalog".into()),
11780 ])
11781 })
11782 .collect();
11783 (schema, rows)
11784}
11785
11786fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11787 let schema = alloc::vec![
11788 ColumnSchema::new("schemaname", DataType::Text, false),
11789 ColumnSchema::new("viewname", DataType::Text, false),
11790 ColumnSchema::new("definition", DataType::Text, false),
11791 ];
11792 let mut rows: Vec<Row> = Vec::new();
11793 for (name, def) in cat.views() {
11794 rows.push(Row::new(alloc::vec![
11795 Value::Text("public".into()),
11796 Value::Text(name.clone()),
11797 Value::Text(def.body.clone()),
11798 ]));
11799 }
11800 (schema, rows)
11801}
11802
11803fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11809 let schema = alloc::vec![
11810 ColumnSchema::new("name", DataType::Text, false),
11811 ColumnSchema::new("setting", DataType::Text, false),
11812 ColumnSchema::new("category", DataType::Text, false),
11813 ];
11814 let mut rows: Vec<Row> = Vec::new();
11815 let defaults: &[(&str, &str, &str)] = &[
11817 ("server_version", "16.0 (spg)", "Preset Options"),
11818 ("server_encoding", "UTF8", "Client Connection Defaults"),
11819 ("client_encoding", "UTF8", "Client Connection Defaults"),
11820 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11821 ("TimeZone", "UTC", "Client Connection Defaults"),
11822 ("standard_conforming_strings", "on", "Compatibility"),
11823 ("integer_datetimes", "on", "Compatibility"),
11824 ("max_connections", "100", "Connections and Authentication"),
11825 ];
11826 for &(name, val, cat) in defaults {
11827 rows.push(Row::new(alloc::vec![
11828 Value::Text(name.into()),
11829 Value::Text(val.into()),
11830 Value::Text(cat.into()),
11831 ]));
11832 }
11833 for (k, v) in &engine.session_params {
11835 if !defaults
11836 .iter()
11837 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11838 {
11839 rows.push(Row::new(alloc::vec![
11840 Value::Text(k.clone()),
11841 Value::Text(v.clone()),
11842 Value::Text("Session".into()),
11843 ]));
11844 }
11845 }
11846 (schema, rows)
11847}
11848
11849fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11860 let schema = alloc::vec![
11861 ColumnSchema::new("schemaname", DataType::Text, false),
11862 ColumnSchema::new("tablename", DataType::Text, false),
11863 ColumnSchema::new("indexname", DataType::Text, false),
11864 ColumnSchema::new("indexdef", DataType::Text, false),
11865 ];
11866 let mut rows: Vec<Row> = Vec::new();
11867 for tname in cat.table_names() {
11868 let Some(t) = cat.get(&tname) else { continue };
11869 for idx in t.indices() {
11870 let col_name = t
11871 .schema()
11872 .columns
11873 .get(idx.column_position)
11874 .map_or("?".into(), |c| c.name.clone());
11875 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11876 let indexdef = alloc::format!(
11877 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11878 idx.name,
11879 tname,
11880 col_name
11881 );
11882 rows.push(Row::new(alloc::vec![
11883 Value::Text("public".into()),
11884 Value::Text(tname.clone()),
11885 Value::Text(idx.name.clone()),
11886 Value::Text(indexdef),
11887 ]));
11888 }
11889 }
11890 (schema, rows)
11891}
11892
11893fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11905 let schema = alloc::vec![
11906 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11907 ColumnSchema::new("indrelid", DataType::BigInt, false),
11908 ColumnSchema::new("indnatts", DataType::Int, false),
11909 ColumnSchema::new("indisunique", DataType::Bool, false),
11910 ColumnSchema::new("indisprimary", DataType::Bool, false),
11911 ];
11912 let mut rows: Vec<Row> = Vec::new();
11913 let mut idx_oid: i64 = 100_000;
11914 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11915 let Some(t) = cat.get(tname) else { continue };
11916 for idx in t.indices() {
11917 idx_oid += 1;
11918 #[allow(clippy::cast_possible_wrap)]
11919 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11920 let is_primary = idx.name.ends_with("_pkey");
11923 rows.push(Row::new(alloc::vec![
11924 Value::BigInt(idx_oid),
11925 Value::BigInt((table_idx + 1) as i64),
11926 Value::Int(nattrs),
11927 Value::Bool(idx.is_unique),
11928 Value::Bool(is_primary),
11929 ]));
11930 }
11931 }
11932 (schema, rows)
11933}
11934
11935fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11940 let schema = alloc::vec![
11941 ColumnSchema::new("oid", DataType::BigInt, false),
11942 ColumnSchema::new("nspname", DataType::Text, false),
11943 ColumnSchema::new("nspowner", DataType::BigInt, false),
11944 ];
11945 let rows = alloc::vec![
11946 Row::new(alloc::vec![
11947 Value::BigInt(11),
11948 Value::Text("pg_catalog".into()),
11949 Value::BigInt(10),
11950 ]),
11951 Row::new(alloc::vec![
11952 Value::BigInt(2200),
11953 Value::Text("public".into()),
11954 Value::BigInt(10),
11955 ]),
11956 Row::new(alloc::vec![
11957 Value::BigInt(13000),
11958 Value::Text("information_schema".into()),
11959 Value::BigInt(10),
11960 ]),
11961 ];
11962 (schema, rows)
11963}
11964
11965fn materialise_meta_view(
11968 catalog: &mut Catalog,
11969 name: &str,
11970 columns: Vec<ColumnSchema>,
11971 rows: Vec<Row>,
11972) -> Result<(), EngineError> {
11973 let schema = TableSchema::new(name.to_string(), columns);
11974 catalog.create_table(schema).map_err(EngineError::Storage)?;
11975 let table = catalog
11976 .get_mut(name)
11977 .expect("just-created meta view must exist");
11978 for row in rows {
11979 table.insert(row).map_err(EngineError::Storage)?;
11980 }
11981 Ok(())
11982}
11983
11984fn collect_view_refs(
11997 tref: &spg_sql::ast::TableRef,
11998 cat: &spg_storage::Catalog,
11999 into: &mut Vec<String>,
12000) {
12001 if cat.views().contains_key(&tref.name)
12002 && cat.get(&tref.name).is_none()
12003 && !into.iter().any(|n| n == &tref.name)
12004 {
12005 into.push(tref.name.clone());
12006 }
12007}
12008
12009fn select_references_meta_view(stmt: &SelectStatement) -> bool {
12010 fn is_meta(name: &str) -> bool {
12011 name.starts_with("__spg_info_")
12012 || name.starts_with("__spg_pg_")
12013 || name.starts_with("__spg_mysql_")
12014 }
12015 if let Some(from) = &stmt.from {
12016 if is_meta(&from.primary.name) {
12017 return true;
12018 }
12019 for j in &from.joins {
12020 if is_meta(&j.table.name) {
12021 return true;
12022 }
12023 }
12024 }
12025 for cte in &stmt.ctes {
12026 if select_references_meta_view(&cte.body) {
12027 return true;
12028 }
12029 }
12030 false
12031}
12032
12033fn collect_meta_view_names(
12038 stmt: &SelectStatement,
12039 into: &mut alloc::collections::BTreeSet<String>,
12040) {
12041 fn is_meta(name: &str) -> bool {
12042 name.starts_with("__spg_info_")
12043 || name.starts_with("__spg_pg_")
12044 || name.starts_with("__spg_mysql_")
12045 }
12046 if let Some(from) = &stmt.from {
12047 if is_meta(&from.primary.name) {
12048 into.insert(from.primary.name.clone());
12049 }
12050 for j in &from.joins {
12051 if is_meta(&j.table.name) {
12052 into.insert(j.table.name.clone());
12053 }
12054 }
12055 }
12056 for cte in &stmt.ctes {
12057 collect_meta_view_names(&cte.body, into);
12058 }
12059}
12060
12061fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
12062 let mut out = columns.to_vec();
12063 for (col_idx, col) in out.iter_mut().enumerate() {
12064 if col.ty != DataType::Text {
12065 continue;
12066 }
12067 let mut inferred: Option<DataType> = None;
12068 let mut all_null = true;
12069 for row in rows {
12070 let Some(v) = row.values.get(col_idx) else {
12071 continue;
12072 };
12073 let ty = match v {
12074 Value::Null => continue,
12075 Value::SmallInt(_) => DataType::SmallInt,
12076 Value::Int(_) => DataType::Int,
12077 Value::BigInt(_) => DataType::BigInt,
12078 Value::Float(_) => DataType::Float,
12079 Value::Bool(_) => DataType::Bool,
12080 Value::Vector(_) => DataType::Vector {
12081 dim: 0,
12082 encoding: VecEncoding::F32,
12083 },
12084 _ => DataType::Text,
12085 };
12086 all_null = false;
12087 inferred = Some(match inferred {
12088 None => ty,
12089 Some(prev) if prev == ty => prev,
12090 Some(_) => DataType::Text,
12091 });
12092 }
12093 if let Some(t) = inferred {
12094 col.ty = t;
12095 col.nullable = true;
12096 } else if all_null {
12097 col.nullable = true;
12098 }
12099 }
12100 out
12101}
12102
12103#[allow(clippy::too_many_lines, clippy::format_push_string)]
12108fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
12125 use alloc::collections::BTreeSet;
12126 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
12127 let mut out: Vec<String> = Vec::new();
12128 let cat = engine.active_catalog();
12129 let Some(from) = &stmt.from else {
12133 return out;
12134 };
12135 let mut tables: Vec<String> = Vec::new();
12136 tables.push(from.primary.name.clone());
12137 for j in &from.joins {
12138 tables.push(j.table.name.clone());
12139 }
12140 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
12143 if let Some(w) = &stmt.where_ {
12144 collect_column_refs(w, &mut col_refs);
12145 }
12146 for j in &from.joins {
12147 if let Some(on) = &j.on {
12148 collect_column_refs(on, &mut col_refs);
12149 }
12150 }
12151 for cn in &col_refs {
12152 let owner: Option<String> = if let Some(q) = &cn.qualifier {
12155 tables.iter().find(|t| t == &q).cloned()
12156 } else {
12157 tables.iter().find_map(|t| {
12158 cat.get(t).and_then(|tbl| {
12159 if tbl.schema().column_position(&cn.name).is_some() {
12160 Some(t.clone())
12161 } else {
12162 None
12163 }
12164 })
12165 })
12166 };
12167 let Some(owner) = owner else {
12168 continue;
12169 };
12170 let Some(tbl) = cat.get(&owner) else {
12171 continue;
12172 };
12173 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
12174 continue;
12175 };
12176 let already_indexed = tbl.indices().iter().any(|i| {
12179 matches!(i.kind, spg_storage::IndexKind::BTree(_))
12180 && i.column_position == col_pos
12181 && i.expression.is_none()
12182 && i.partial_predicate.is_none()
12183 });
12184 if already_indexed {
12185 continue;
12186 }
12187 if seen.insert((owner.clone(), cn.name.clone())) {
12188 out.push(alloc::format!(
12189 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
12190 owner,
12191 cn.name,
12192 owner,
12193 cn.name
12194 ));
12195 }
12196 }
12197 out
12198}
12199
12200fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
12203 match expr {
12204 Expr::Column(cn) => out.push(cn.clone()),
12205 Expr::FunctionCall { args, .. } => {
12206 for a in args {
12207 collect_column_refs(a, out);
12208 }
12209 }
12210 Expr::Binary { lhs, rhs, .. } => {
12211 collect_column_refs(lhs, out);
12212 collect_column_refs(rhs, out);
12213 }
12214 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
12215 _ => {}
12216 }
12217}
12218
12219fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
12220 let catalog = engine.active_catalog();
12221 let cold_ids = catalog.cold_segment_ids_global();
12222 let any_cold = !cold_ids.is_empty();
12223 let cold_ids_repr = if any_cold {
12224 let mut s = alloc::string::String::from("[");
12225 for (i, id) in cold_ids.iter().enumerate() {
12226 if i > 0 {
12227 s.push(',');
12228 }
12229 s.push_str(&alloc::format!("{id}"));
12230 }
12231 s.push(']');
12232 s
12233 } else {
12234 alloc::string::String::new()
12235 };
12236 for (idx, line) in lines.iter_mut().enumerate() {
12237 let trimmed = line.trim_start();
12238 let is_top_level = idx == 0;
12239 if is_top_level {
12240 line.push_str(&alloc::format!(" (rows={total_rows})"));
12241 continue;
12242 }
12243 if let Some(rest) = trimmed.strip_prefix("From: ") {
12244 let (name, scan_kind) = match rest.split_once(" [") {
12245 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12246 None => (rest.trim(), ""),
12247 };
12248 let bare = name.split_whitespace().next().unwrap_or(name);
12249 let hot = catalog.get(bare).map(|t| t.rows().len());
12250 let annot = match (hot, scan_kind) {
12255 (Some(h), "full scan") => {
12256 let mut s = alloc::format!(" (hot_rows={h}");
12257 if any_cold {
12258 s.push_str(&alloc::format!(
12259 ", cold_tier=present, cold_segments={cold_ids_repr}"
12260 ));
12261 }
12262 s.push(')');
12263 s
12264 }
12265 (Some(h), "index seek") => {
12266 let mut s = alloc::format!(" (hot_rows≤{h}");
12267 if any_cold {
12268 s.push_str(&alloc::format!(
12269 ", cold_tier=present, cold_segments={cold_ids_repr}"
12270 ));
12271 }
12272 s.push(')');
12273 s
12274 }
12275 _ => " (rows=—)".to_string(),
12276 };
12277 line.push_str(&annot);
12278 continue;
12279 }
12280 line.push_str(" (rows=—)");
12282 }
12283}
12284
12285fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
12286 let pad = " ".repeat(depth);
12287 let top = if !stmt.ctes.is_empty() {
12289 if stmt.ctes.iter().any(|c| c.recursive) {
12290 "CTEScan (WITH RECURSIVE)"
12291 } else {
12292 "CTEScan (WITH)"
12293 }
12294 } else if !stmt.unions.is_empty() {
12295 "UnionScan"
12296 } else if select_has_window(stmt) {
12297 "WindowAgg"
12298 } else if aggregate::uses_aggregate(stmt) {
12299 "Aggregate"
12300 } else if stmt.distinct {
12301 "Distinct"
12302 } else if stmt.from.is_some() {
12303 "TableScan"
12304 } else {
12305 "Result"
12306 };
12307 out.push(alloc::format!("{pad}{top}"));
12308 let child = " ".repeat(depth + 1);
12309 for cte in &stmt.ctes {
12311 let head = if cte.recursive {
12312 alloc::format!("{child}CTE (recursive): {}", cte.name)
12313 } else {
12314 alloc::format!("{child}CTE: {}", cte.name)
12315 };
12316 out.push(head);
12317 explain_select(&cte.body, engine, depth + 2, out);
12318 }
12319 if let Some(from) = &stmt.from {
12321 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12322 if let Some(alias) = &from.primary.alias {
12323 tag.push_str(&alloc::format!(" AS {alias}"));
12324 }
12325 if let Some(w) = &stmt.where_
12328 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12329 {
12330 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12331 let cols = &table.schema().columns;
12332 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12333 tag.push_str(" [index seek]");
12334 } else {
12335 tag.push_str(" [full scan]");
12336 }
12337 } else {
12338 tag.push_str(" [full scan]");
12339 }
12340 out.push(tag);
12341 for j in &from.joins {
12342 let kind = match j.kind {
12343 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12344 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12345 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12346 };
12347 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12348 if let Some(alias) = &j.table.alias {
12349 s.push_str(&alloc::format!(" AS {alias}"));
12350 }
12351 if j.on.is_some() {
12352 s.push_str(" (ON …)");
12353 }
12354 out.push(s);
12355 }
12356 }
12357 if let Some(w) = &stmt.where_ {
12359 let mut s = alloc::format!("{child}Filter: {w}");
12360 if expr_has_subquery(w) {
12361 s.push_str(" [subquery]");
12362 }
12363 out.push(s);
12364 }
12365 if let Some(gs) = &stmt.group_by {
12366 let mut parts = Vec::new();
12367 for g in gs {
12368 parts.push(alloc::format!("{g}"));
12369 }
12370 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12371 }
12372 if let Some(h) = &stmt.having {
12373 out.push(alloc::format!("{child}Having: {h}"));
12374 }
12375 for o in &stmt.order_by {
12376 let dir = if o.desc { "DESC" } else { "ASC" };
12377 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12378 }
12379 if let Some(lim) = stmt.limit {
12380 out.push(alloc::format!("{child}Limit: {lim}"));
12381 }
12382 if let Some(off) = stmt.offset {
12383 out.push(alloc::format!("{child}Offset: {off}"));
12384 }
12385 if stmt
12387 .items
12388 .iter()
12389 .any(|it| matches!(it, SelectItem::Wildcard))
12390 {
12391 out.push(alloc::format!("{child}Project: *"));
12392 } else {
12393 out.push(alloc::format!(
12394 "{child}Project: {} item(s)",
12395 stmt.items.len()
12396 ));
12397 }
12398 for (kind, peer) in &stmt.unions {
12400 let label = match kind {
12401 UnionKind::All => "UNION ALL",
12402 UnionKind::Distinct => "UNION",
12403 };
12404 out.push(alloc::format!("{child}{label}"));
12405 explain_select(peer, engine, depth + 2, out);
12406 }
12407}
12408
12409fn is_correlation_error(e: &EngineError) -> bool {
12414 matches!(
12415 e,
12416 EngineError::Eval(
12417 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12418 )
12419 )
12420}
12421
12422struct JoinedPeer<'a> {
12433 eager_rows: Option<Vec<Row>>,
12434 cols: Vec<ColumnSchema>,
12435 alias: String,
12436 kind: JoinKind,
12437 on: Option<&'a Expr>,
12438 lateral: Option<&'a SelectStatement>,
12439}
12440
12441fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12448 match expr {
12449 Expr::Column(c) => c.name.clone(),
12451 Expr::FunctionCall { name, .. } => name.clone(),
12454 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12456 _ => alloc::format!("column{}", idx + 1),
12458 }
12459}
12460
12461fn substitute_outer_columns_multi(
12468 stmt: &mut SelectStatement,
12469 outer_row: &Row,
12470 outer_schema: &[ColumnSchema],
12471) {
12472 substitute_outer_in_select(stmt, outer_row, outer_schema);
12473}
12474
12475fn substitute_outer_in_select(
12476 stmt: &mut SelectStatement,
12477 outer_row: &Row,
12478 outer_schema: &[ColumnSchema],
12479) {
12480 for item in &mut stmt.items {
12481 if let SelectItem::Expr { expr, .. } = item {
12482 substitute_outer_in_expr(expr, outer_row, outer_schema);
12483 }
12484 }
12485 if let Some(w) = &mut stmt.where_ {
12486 substitute_outer_in_expr(w, outer_row, outer_schema);
12487 }
12488 if let Some(gs) = &mut stmt.group_by {
12489 for g in gs {
12490 substitute_outer_in_expr(g, outer_row, outer_schema);
12491 }
12492 }
12493 if let Some(h) = &mut stmt.having {
12494 substitute_outer_in_expr(h, outer_row, outer_schema);
12495 }
12496 for o in &mut stmt.order_by {
12497 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12498 }
12499 for (_, peer) in &mut stmt.unions {
12500 substitute_outer_in_select(peer, outer_row, outer_schema);
12501 }
12502}
12503
12504fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12505 if let Expr::Column(c) = e
12506 && let Some(qual) = &c.qualifier
12507 {
12508 let composite = alloc::format!("{qual}.{}", c.name);
12509 if let Some(idx) = outer_schema
12510 .iter()
12511 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12512 {
12513 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12514 if let Ok(lit) = value_to_literal_expr(v) {
12515 *e = lit;
12516 return;
12517 }
12518 }
12519 }
12520 match e {
12521 Expr::Binary { lhs, rhs, .. } => {
12522 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12523 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12524 }
12525 Expr::Unary { expr: inner, .. } => {
12526 substitute_outer_in_expr(inner, outer_row, outer_schema);
12527 }
12528 Expr::FunctionCall { args, .. } => {
12529 for a in args {
12530 substitute_outer_in_expr(a, outer_row, outer_schema);
12531 }
12532 }
12533 Expr::Cast { expr: inner, .. } => {
12534 substitute_outer_in_expr(inner, outer_row, outer_schema);
12535 }
12536 Expr::Case {
12537 operand,
12538 branches,
12539 else_branch,
12540 } => {
12541 if let Some(op) = operand {
12542 substitute_outer_in_expr(op, outer_row, outer_schema);
12543 }
12544 for (cond, val) in branches {
12545 substitute_outer_in_expr(cond, outer_row, outer_schema);
12546 substitute_outer_in_expr(val, outer_row, outer_schema);
12547 }
12548 if let Some(e) = else_branch {
12549 substitute_outer_in_expr(e, outer_row, outer_schema);
12550 }
12551 }
12552 _ => {}
12553 }
12554}
12555
12556fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12557 let outer_alias = ctx.table_alias.unwrap_or("");
12564 substitute_in_select(stmt, row, ctx, outer_alias);
12565}
12566
12567fn substitute_in_select(
12568 stmt: &mut SelectStatement,
12569 row: &Row,
12570 ctx: &EvalContext<'_>,
12571 outer_alias: &str,
12572) {
12573 for item in &mut stmt.items {
12574 if let SelectItem::Expr { expr, .. } = item {
12575 substitute_in_expr(expr, row, ctx, outer_alias);
12576 }
12577 }
12578 if let Some(w) = &mut stmt.where_ {
12579 substitute_in_expr(w, row, ctx, outer_alias);
12580 }
12581 if let Some(gs) = &mut stmt.group_by {
12582 for g in gs {
12583 substitute_in_expr(g, row, ctx, outer_alias);
12584 }
12585 }
12586 if let Some(h) = &mut stmt.having {
12587 substitute_in_expr(h, row, ctx, outer_alias);
12588 }
12589 for o in &mut stmt.order_by {
12590 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12591 }
12592 for (_, peer) in &mut stmt.unions {
12593 substitute_in_select(peer, row, ctx, outer_alias);
12594 }
12595}
12596
12597fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12598 if let Expr::Column(c) = e
12604 && c.qualifier.is_none()
12605 && (c.name.starts_with("__grp_") || c.name.starts_with("__agg_"))
12606 && let Some(idx) = ctx.columns.iter().position(|sc| sc.name == c.name)
12607 {
12608 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12609 if let Ok(lit) = value_to_literal_expr(v) {
12610 *e = lit;
12611 return;
12612 }
12613 }
12614 if let Expr::Column(c) = e
12615 && let Some(qual) = &c.qualifier
12616 {
12617 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
12621 ctx.columns
12622 .iter()
12623 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12624 } else {
12625 None
12626 }
12627 .or_else(|| {
12628 let composite = alloc::format!("{qual}.{name}", name = c.name);
12629 ctx.columns
12630 .iter()
12631 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12632 });
12633 if let Some(idx) = idx {
12634 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12635 if let Ok(lit) = value_to_literal_expr(v) {
12636 *e = lit;
12637 return;
12638 }
12639 }
12640 }
12641 match e {
12642 Expr::AggregateOrdered { call, order_by, .. } => {
12643 substitute_in_expr(call, row, ctx, outer_alias);
12644 for o in order_by.iter_mut() {
12645 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12646 }
12647 }
12648 Expr::Binary { lhs, rhs, .. } => {
12649 substitute_in_expr(lhs, row, ctx, outer_alias);
12650 substitute_in_expr(rhs, row, ctx, outer_alias);
12651 }
12652 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12653 substitute_in_expr(expr, row, ctx, outer_alias);
12654 }
12655 Expr::Like { expr, pattern, .. } => {
12656 substitute_in_expr(expr, row, ctx, outer_alias);
12657 substitute_in_expr(pattern, row, ctx, outer_alias);
12658 }
12659 Expr::FunctionCall { args, .. } => {
12660 for a in args {
12661 substitute_in_expr(a, row, ctx, outer_alias);
12662 }
12663 }
12664 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12665 Expr::WindowFunction {
12666 args,
12667 partition_by,
12668 order_by,
12669 ..
12670 } => {
12671 for a in args {
12672 substitute_in_expr(a, row, ctx, outer_alias);
12673 }
12674 for p in partition_by {
12675 substitute_in_expr(p, row, ctx, outer_alias);
12676 }
12677 for (o, _, _) in order_by {
12678 substitute_in_expr(o, row, ctx, outer_alias);
12679 }
12680 }
12681 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12682 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12683 substitute_in_select(subquery, row, ctx, outer_alias);
12684 }
12685 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12686 Expr::Array(items) => {
12687 for elem in items {
12688 substitute_in_expr(elem, row, ctx, outer_alias);
12689 }
12690 }
12691 Expr::ArraySubscript { target, index } => {
12692 substitute_in_expr(target, row, ctx, outer_alias);
12693 substitute_in_expr(index, row, ctx, outer_alias);
12694 }
12695 Expr::AnyAll { expr, array, .. } => {
12696 substitute_in_expr(expr, row, ctx, outer_alias);
12697 substitute_in_expr(array, row, ctx, outer_alias);
12698 }
12699 Expr::Case {
12700 operand,
12701 branches,
12702 else_branch,
12703 } => {
12704 if let Some(o) = operand {
12705 substitute_in_expr(o, row, ctx, outer_alias);
12706 }
12707 for (w, t) in branches {
12708 substitute_in_expr(w, row, ctx, outer_alias);
12709 substitute_in_expr(t, row, ctx, outer_alias);
12710 }
12711 if let Some(e) = else_branch {
12712 substitute_in_expr(e, row, ctx, outer_alias);
12713 }
12714 }
12715 }
12716}
12717
12718fn encode_row_key(row: &Row) -> Vec<u8> {
12722 let mut out = Vec::new();
12723 for v in &row.values {
12724 let s = alloc::format!("{v:?}|");
12725 out.extend_from_slice(s.as_bytes());
12726 }
12727 out
12728}
12729
12730fn select_has_window(stmt: &SelectStatement) -> bool {
12731 for item in &stmt.items {
12732 if let SelectItem::Expr { expr, .. } = item
12733 && expr_has_window(expr)
12734 {
12735 return true;
12736 }
12737 }
12738 false
12739}
12740
12741fn expr_has_window(e: &Expr) -> bool {
12742 match e {
12743 Expr::WindowFunction { .. } => true,
12744 Expr::AggregateOrdered { call, order_by, .. } => {
12745 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
12746 }
12747 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12748 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12749 expr_has_window(expr)
12750 }
12751 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12752 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12753 Expr::Extract { source, .. } => expr_has_window(source),
12754 Expr::ScalarSubquery(_)
12755 | Expr::Exists { .. }
12756 | Expr::InSubquery { .. }
12757 | Expr::Literal(_)
12758 | Expr::Placeholder(_)
12759 | Expr::Column(_) => false,
12760 Expr::Array(items) => items.iter().any(expr_has_window),
12761 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12762 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12763 Expr::Case {
12764 operand,
12765 branches,
12766 else_branch,
12767 } => {
12768 operand.as_deref().is_some_and(expr_has_window)
12769 || branches
12770 .iter()
12771 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12772 || else_branch.as_deref().is_some_and(expr_has_window)
12773 }
12774 }
12775}
12776
12777fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12778 if let Expr::WindowFunction { .. } = e {
12779 if !out.iter().any(|x| x == e) {
12784 out.push(e.clone());
12785 }
12786 return;
12787 }
12788 match e {
12789 Expr::WindowFunction { .. } => unreachable!(),
12791 Expr::Binary { lhs, rhs, .. } => {
12792 collect_window_nodes(lhs, out);
12793 collect_window_nodes(rhs, out);
12794 }
12795 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12796 collect_window_nodes(expr, out);
12797 }
12798 Expr::FunctionCall { args, .. } => {
12799 for a in args {
12800 collect_window_nodes(a, out);
12801 }
12802 }
12803 Expr::Like { expr, pattern, .. } => {
12804 collect_window_nodes(expr, out);
12805 collect_window_nodes(pattern, out);
12806 }
12807 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12808 _ => {}
12809 }
12810}
12811
12812fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12813 if let Expr::WindowFunction { .. } = e
12814 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12815 {
12816 *e = Expr::Column(spg_sql::ast::ColumnName {
12817 qualifier: None,
12818 name: alloc::format!("__win_{idx}"),
12819 });
12820 return;
12821 }
12822 match e {
12823 Expr::Binary { lhs, rhs, .. } => {
12824 rewrite_window_to_columns(lhs, window_nodes);
12825 rewrite_window_to_columns(rhs, window_nodes);
12826 }
12827 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12828 rewrite_window_to_columns(expr, window_nodes);
12829 }
12830 Expr::FunctionCall { args, .. } => {
12831 for a in args {
12832 rewrite_window_to_columns(a, window_nodes);
12833 }
12834 }
12835 Expr::Like { expr, pattern, .. } => {
12836 rewrite_window_to_columns(expr, window_nodes);
12837 rewrite_window_to_columns(pattern, window_nodes);
12838 }
12839 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12840 _ => {}
12841 }
12842}
12843
12844fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12848 for (x, y) in a.iter().zip(b.iter()) {
12849 let c = value_cmp(x, y);
12850 if c != core::cmp::Ordering::Equal {
12851 return c;
12852 }
12853 }
12854 a.len().cmp(&b.len())
12855}
12856
12857fn order_key_cmp(
12858 a: &[(Value, bool, Option<bool>)],
12859 b: &[(Value, bool, Option<bool>)],
12860) -> core::cmp::Ordering {
12861 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
12864 let c = order_by_value_cmp(*desc, *nf, va, vb);
12865 if c != core::cmp::Ordering::Equal {
12866 return c;
12867 }
12868 }
12869 a.len().cmp(&b.len())
12870}
12871
12872const fn value_is_integer(v: &Value) -> bool {
12878 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12879}
12880
12881const fn value_to_i64(v: &Value) -> i64 {
12885 match v {
12886 Value::SmallInt(n) => *n as i64,
12887 Value::Int(n) => *n as i64,
12888 Value::BigInt(n) => *n,
12889 _ => panic!("value_to_i64 called on non-integer Value"),
12890 }
12891}
12892
12893fn generate_series_integers(
12899 start: i64,
12900 stop: i64,
12901 step: i64,
12902 cancel: &CancelToken<'_>,
12903) -> Result<alloc::vec::Vec<Row>, EngineError> {
12904 if step == 0 {
12905 return Err(EngineError::Unsupported(
12906 "generate_series(): step argument cannot be zero".into(),
12907 ));
12908 }
12909 let mut out = alloc::vec::Vec::new();
12910 let mut cur = start;
12911 const MAX_ROWS: usize = 10_000_000;
12915 loop {
12916 cancel.check()?;
12917 if step > 0 && cur > stop {
12918 break;
12919 }
12920 if step < 0 && cur < stop {
12921 break;
12922 }
12923 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12924 if out.len() > MAX_ROWS {
12925 return Err(EngineError::Unsupported(alloc::format!(
12926 "generate_series(): exceeded {MAX_ROWS} rows; \
12927 narrow start/stop or use a larger step"
12928 )));
12929 }
12930 cur = match cur.checked_add(step) {
12931 Some(n) => n,
12932 None => break,
12933 };
12934 }
12935 Ok(out)
12936}
12937
12938fn generate_series_timestamps(
12943 start: i64,
12944 stop: i64,
12945 step: Value,
12946 cancel: &CancelToken<'_>,
12947) -> Result<alloc::vec::Vec<Row>, EngineError> {
12948 let (months, micros) = match &step {
12949 Value::Interval { months, micros } => (*months, *micros),
12950 _ => unreachable!("caller guards step.is_interval"),
12951 };
12952 if months == 0 && micros == 0 {
12953 return Err(EngineError::Unsupported(
12954 "generate_series(): INTERVAL step cannot be zero".into(),
12955 ));
12956 }
12957 let ascending = months > 0 || micros > 0;
12958 let mut out = alloc::vec::Vec::new();
12959 let mut cur = Value::Timestamp(start);
12960 const MAX_ROWS: usize = 10_000_000;
12961 loop {
12962 cancel.check()?;
12963 let cur_t = match cur {
12964 Value::Timestamp(t) => t,
12965 _ => unreachable!("loop invariant: cur is Timestamp"),
12966 };
12967 if ascending && cur_t > stop {
12968 break;
12969 }
12970 if !ascending && cur_t < stop {
12971 break;
12972 }
12973 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12974 if out.len() > MAX_ROWS {
12975 return Err(EngineError::Unsupported(alloc::format!(
12976 "generate_series(): exceeded {MAX_ROWS} rows; \
12977 narrow start/stop or use a larger step"
12978 )));
12979 }
12980 let next = eval::apply_binary_interval(
12981 spg_sql::ast::BinOp::Add,
12982 &cur,
12983 &Value::Interval { months, micros },
12984 )
12985 .map_err(EngineError::Eval)?;
12986 cur = match next {
12987 Some(v) => v,
12988 None => break,
12989 };
12990 }
12991 Ok(out)
12992}
12993
12994#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
13000 desc: bool,
13001 nulls_first: Option<bool>,
13002 a: &Value,
13003 b: &Value,
13004) -> core::cmp::Ordering {
13005 use core::cmp::Ordering;
13006 let nf = nulls_first.unwrap_or(desc);
13007 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
13008 (true, true) => Ordering::Equal,
13009 (true, false) => {
13010 if nf {
13011 Ordering::Less
13012 } else {
13013 Ordering::Greater
13014 }
13015 }
13016 (false, true) => {
13017 if nf {
13018 Ordering::Greater
13019 } else {
13020 Ordering::Less
13021 }
13022 }
13023 (false, false) => {
13024 let c = value_cmp(a, b);
13025 if desc { c.reverse() } else { c }
13026 }
13027 }
13028}
13029
13030fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
13031 use core::cmp::Ordering;
13032 match (a, b) {
13033 (Value::Null, Value::Null) => Ordering::Equal,
13034 (Value::Null, _) => Ordering::Less,
13035 (_, Value::Null) => Ordering::Greater,
13036 (Value::Int(x), Value::Int(y)) => x.cmp(y),
13037 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
13038 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
13039 (Value::Text(x), Value::Text(y)) => x.cmp(y),
13040 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
13041 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
13042 (Value::Date(x), Value::Date(y)) => x.cmp(y),
13043 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
13044 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
13047 }
13048}
13049
13050#[allow(
13056 clippy::too_many_arguments,
13057 clippy::cast_possible_truncation,
13058 clippy::cast_possible_wrap,
13059 clippy::cast_precision_loss,
13060 clippy::cast_sign_loss,
13061 clippy::doc_markdown,
13062 clippy::too_many_lines,
13063 clippy::type_complexity,
13064 clippy::match_same_arms
13065)]
13066fn compute_window_partition(
13067 name: &str,
13068 args: &[Expr],
13069 ordered: bool,
13070 frame: Option<&WindowFrame>,
13071 null_treatment: spg_sql::ast::NullTreatment,
13072 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13073 filtered_rows: &[&Row],
13074 ctx: &EvalContext<'_>,
13075 out_vals: &mut [Value],
13076) -> Result<(), EngineError> {
13077 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
13078 let lower = name.to_ascii_lowercase();
13079 match lower.as_str() {
13080 "row_number" => {
13081 for (rank, (_, _, idx)) in slice.iter().enumerate() {
13082 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
13083 }
13084 Ok(())
13085 }
13086 "rank" => {
13087 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13088 let mut current_rank: i64 = 1;
13089 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13090 if let Some(p) = prev_key
13091 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13092 {
13093 current_rank = (i + 1) as i64;
13094 }
13095 if prev_key.is_none() {
13096 current_rank = 1;
13097 }
13098 out_vals[*idx] = Value::BigInt(current_rank);
13099 prev_key = Some(okey.as_slice());
13100 }
13101 Ok(())
13102 }
13103 "dense_rank" => {
13104 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13105 let mut current_rank: i64 = 0;
13106 for (_, okey, idx) in slice {
13107 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
13108 current_rank += 1;
13109 }
13110 out_vals[*idx] = Value::BigInt(current_rank);
13111 prev_key = Some(okey.as_slice());
13112 }
13113 Ok(())
13114 }
13115 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
13116 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
13119 slice.iter().map(|_| Value::Null).collect()
13120 } else {
13121 slice
13122 .iter()
13123 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13124 .collect::<Result<_, _>>()
13125 .map_err(EngineError::Eval)?
13126 };
13127 let eff = effective_frame(frame, ordered)?;
13131 #[allow(clippy::needless_range_loop)]
13132 for i in 0..slice.len() {
13133 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13134 let mut sum: f64 = 0.0;
13135 let mut count: i64 = 0;
13136 let mut min_v: Option<f64> = None;
13137 let mut max_v: Option<f64> = None;
13138 let mut row_count: i64 = 0;
13139 if lo <= hi {
13140 for j in lo..=hi {
13141 let v = &arg_values[j];
13142 match lower.as_str() {
13143 "count_star" => row_count += 1,
13144 "count" => {
13145 if !v.is_null() {
13146 count += 1;
13147 }
13148 }
13149 _ => {
13150 if let Some(x) = value_to_f64(v) {
13151 sum += x;
13152 count += 1;
13153 min_v = Some(min_v.map_or(x, |m| m.min(x)));
13154 max_v = Some(max_v.map_or(x, |m| m.max(x)));
13155 }
13156 }
13157 }
13158 }
13159 }
13160 let value = match lower.as_str() {
13161 "count_star" => Value::BigInt(row_count),
13162 "count" => Value::BigInt(count),
13163 "sum" => Value::Float(sum),
13164 "avg" => {
13165 if count == 0 {
13166 Value::Null
13167 } else {
13168 Value::Float(sum / count as f64)
13169 }
13170 }
13171 "min" => min_v.map_or(Value::Null, Value::Float),
13172 "max" => max_v.map_or(Value::Null, Value::Float),
13173 _ => unreachable!(),
13174 };
13175 let (_, _, idx) = &slice[i];
13176 out_vals[*idx] = value;
13177 }
13178 Ok(())
13179 }
13180 "lag" | "lead" => {
13181 if args.is_empty() {
13184 return Err(EngineError::Unsupported(alloc::format!(
13185 "{lower}() requires at least one argument"
13186 )));
13187 }
13188 let offset: i64 = if args.len() >= 2 {
13189 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13190 .map_err(EngineError::Eval)?;
13191 match v {
13192 Value::SmallInt(n) => i64::from(n),
13193 Value::Int(n) => i64::from(n),
13194 Value::BigInt(n) => n,
13195 _ => {
13196 return Err(EngineError::Unsupported(alloc::format!(
13197 "{lower}() offset must be integer"
13198 )));
13199 }
13200 }
13201 } else {
13202 1
13203 };
13204 let default: Value = if args.len() >= 3 {
13205 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
13206 .map_err(EngineError::Eval)?
13207 } else {
13208 Value::Null
13209 };
13210 let values: Vec<Value> = slice
13211 .iter()
13212 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13213 .collect::<Result<_, _>>()
13214 .map_err(EngineError::Eval)?;
13215 let n = slice.len();
13216 for (i, (_, _, idx)) in slice.iter().enumerate() {
13217 let signed_offset = if lower == "lag" { -offset } else { offset };
13218 let v = if ignore_nulls {
13219 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
13223 let needed: i64 = signed_offset.abs();
13224 if needed == 0 {
13225 values[i].clone()
13226 } else {
13227 let mut j: i64 = i as i64;
13228 let mut hits: i64 = 0;
13229 let mut found: Option<Value> = None;
13230 loop {
13231 j += step;
13232 if j < 0 || j >= n as i64 {
13233 break;
13234 }
13235 #[allow(clippy::cast_sign_loss)]
13236 let v = &values[j as usize];
13237 if !v.is_null() {
13238 hits += 1;
13239 if hits == needed {
13240 found = Some(v.clone());
13241 break;
13242 }
13243 }
13244 }
13245 found.unwrap_or_else(|| default.clone())
13246 }
13247 } else {
13248 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
13249 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
13250 default.clone()
13251 } else {
13252 #[allow(clippy::cast_sign_loss)]
13253 {
13254 values[target_signed as usize].clone()
13255 }
13256 }
13257 };
13258 out_vals[*idx] = v;
13259 }
13260 Ok(())
13261 }
13262 "first_value" | "last_value" | "nth_value" => {
13263 if args.is_empty() {
13264 return Err(EngineError::Unsupported(alloc::format!(
13265 "{lower}() requires at least one argument"
13266 )));
13267 }
13268 let values: Vec<Value> = slice
13269 .iter()
13270 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13271 .collect::<Result<_, _>>()
13272 .map_err(EngineError::Eval)?;
13273 let nth: usize = if lower == "nth_value" {
13274 if args.len() < 2 {
13275 return Err(EngineError::Unsupported(
13276 "nth_value() requires (expr, n)".into(),
13277 ));
13278 }
13279 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13280 .map_err(EngineError::Eval)?;
13281 let raw = match v {
13282 Value::SmallInt(n) => i64::from(n),
13283 Value::Int(n) => i64::from(n),
13284 Value::BigInt(n) => n,
13285 _ => {
13286 return Err(EngineError::Unsupported(
13287 "nth_value() n must be integer".into(),
13288 ));
13289 }
13290 };
13291 if raw < 1 {
13292 return Err(EngineError::Unsupported(
13293 "nth_value() n must be >= 1".into(),
13294 ));
13295 }
13296 #[allow(clippy::cast_sign_loss)]
13297 {
13298 raw as usize
13299 }
13300 } else {
13301 0
13302 };
13303 let eff = effective_frame(frame, ordered)?;
13304 for i in 0..slice.len() {
13305 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13306 let (_, _, idx) = &slice[i];
13307 let v = if lo > hi {
13308 Value::Null
13309 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
13310 if lower == "first_value" {
13313 (lo..=hi)
13314 .find_map(|j| {
13315 let v = &values[j];
13316 (!v.is_null()).then(|| v.clone())
13317 })
13318 .unwrap_or(Value::Null)
13319 } else {
13320 (lo..=hi)
13321 .rev()
13322 .find_map(|j| {
13323 let v = &values[j];
13324 (!v.is_null()).then(|| v.clone())
13325 })
13326 .unwrap_or(Value::Null)
13327 }
13328 } else {
13329 match lower.as_str() {
13330 "first_value" => values[lo].clone(),
13331 "last_value" => values[hi].clone(),
13332 "nth_value" => {
13333 let pos = lo + nth - 1;
13334 if pos > hi {
13335 Value::Null
13336 } else {
13337 values[pos].clone()
13338 }
13339 }
13340 _ => unreachable!(),
13341 }
13342 };
13343 out_vals[*idx] = v;
13344 }
13345 Ok(())
13346 }
13347 "ntile" => {
13348 if args.is_empty() {
13349 return Err(EngineError::Unsupported(
13350 "ntile(n) requires an integer argument".into(),
13351 ));
13352 }
13353 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
13354 .map_err(EngineError::Eval)?;
13355 let bucket_count: i64 = match v {
13356 Value::SmallInt(n) => i64::from(n),
13357 Value::Int(n) => i64::from(n),
13358 Value::BigInt(n) => n,
13359 _ => {
13360 return Err(EngineError::Unsupported(
13361 "ntile() argument must be integer".into(),
13362 ));
13363 }
13364 };
13365 if bucket_count < 1 {
13366 return Err(EngineError::Unsupported(
13367 "ntile() argument must be >= 1".into(),
13368 ));
13369 }
13370 #[allow(clippy::cast_sign_loss)]
13371 let buckets = bucket_count as usize;
13372 let n = slice.len();
13373 let base = n / buckets;
13376 let extras = n % buckets;
13377 let mut bucket: usize = 1;
13378 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
13379 let mut buckets_with_extra_remaining = extras;
13380 for (_, _, idx) in slice {
13381 if remaining_in_bucket == 0 {
13382 bucket += 1;
13383 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
13384 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
13385 base + 1
13386 } else {
13387 base
13388 };
13389 if remaining_in_bucket == 0 {
13392 remaining_in_bucket = 1;
13393 }
13394 }
13395 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13396 remaining_in_bucket -= 1;
13397 }
13398 Ok(())
13399 }
13400 "percent_rank" => {
13401 let n = slice.len();
13404 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13405 let mut current_rank: i64 = 1;
13406 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13407 if let Some(p) = prev_key
13408 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13409 {
13410 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13411 }
13412 if prev_key.is_none() {
13413 current_rank = 1;
13414 }
13415 #[allow(clippy::cast_precision_loss)]
13416 let pr = if n <= 1 {
13417 0.0
13418 } else {
13419 (current_rank - 1) as f64 / (n - 1) as f64
13420 };
13421 out_vals[*idx] = Value::Float(pr);
13422 prev_key = Some(okey.as_slice());
13423 }
13424 Ok(())
13425 }
13426 "cume_dist" => {
13427 let n = slice.len();
13429 for i in 0..slice.len() {
13431 let peer_end = peer_group_end(slice, i);
13432 #[allow(clippy::cast_precision_loss)]
13433 let cd = (peer_end + 1) as f64 / n as f64;
13434 let (_, _, idx) = &slice[i];
13435 out_vals[*idx] = Value::Float(cd);
13436 }
13437 Ok(())
13438 }
13439 other => Err(EngineError::Unsupported(alloc::format!(
13440 "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)"
13441 ))),
13442 }
13443}
13444
13445fn effective_frame(
13452 frame: Option<&WindowFrame>,
13453 ordered: bool,
13454) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13455 match frame {
13456 None => {
13457 if ordered {
13458 Ok((
13459 FrameKind::Range,
13460 FrameBound::UnboundedPreceding,
13461 FrameBound::CurrentRow,
13462 ))
13463 } else {
13464 Ok((
13465 FrameKind::Rows,
13466 FrameBound::UnboundedPreceding,
13467 FrameBound::UnboundedFollowing,
13468 ))
13469 }
13470 }
13471 Some(fr) => {
13472 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13473 if matches!(fr.start, FrameBound::UnboundedFollowing)
13475 || matches!(end, FrameBound::UnboundedPreceding)
13476 {
13477 return Err(EngineError::Unsupported(alloc::format!(
13478 "invalid frame: start={:?} end={:?}",
13479 fr.start,
13480 end
13481 )));
13482 }
13483 if fr.kind == FrameKind::Range
13488 && (matches!(
13489 fr.start,
13490 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13491 ) || matches!(
13492 end,
13493 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13494 ))
13495 {
13496 return Err(EngineError::Unsupported(
13497 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13498 ));
13499 }
13500 Ok((fr.kind, fr.start.clone(), end))
13501 }
13502 }
13503}
13504
13505#[allow(clippy::type_complexity)]
13509fn frame_bounds_for_row(
13510 eff: &(FrameKind, FrameBound, FrameBound),
13511 i: usize,
13512 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13513) -> (usize, usize) {
13514 let (kind, start, end) = eff;
13515 let n = slice.len();
13516 let last = n.saturating_sub(1);
13517 let (mut lo, mut hi) = match kind {
13518 FrameKind::Rows => {
13519 let lo = match start {
13520 FrameBound::UnboundedPreceding => 0,
13521 FrameBound::OffsetPreceding(k) => {
13522 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13523 i.saturating_sub(k)
13524 }
13525 FrameBound::CurrentRow => i,
13526 FrameBound::OffsetFollowing(k) => {
13527 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13528 i.saturating_add(k).min(last)
13529 }
13530 FrameBound::UnboundedFollowing => last,
13531 };
13532 let hi = match end {
13533 FrameBound::UnboundedPreceding => 0,
13534 FrameBound::OffsetPreceding(k) => {
13535 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13536 i.saturating_sub(k)
13537 }
13538 FrameBound::CurrentRow => i,
13539 FrameBound::OffsetFollowing(k) => {
13540 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13541 i.saturating_add(k).min(last)
13542 }
13543 FrameBound::UnboundedFollowing => last,
13544 };
13545 (lo, hi)
13546 }
13547 FrameKind::Range => {
13548 let lo = match start {
13554 FrameBound::UnboundedPreceding => 0,
13555 FrameBound::CurrentRow => peer_group_start(slice, i),
13556 FrameBound::UnboundedFollowing => last,
13557 _ => unreachable!("offset bounds rejected for RANGE"),
13558 };
13559 let hi = match end {
13560 FrameBound::UnboundedPreceding => 0,
13561 FrameBound::CurrentRow => peer_group_end(slice, i),
13562 FrameBound::UnboundedFollowing => last,
13563 _ => unreachable!("offset bounds rejected for RANGE"),
13564 };
13565 (lo, hi)
13566 }
13567 };
13568 if hi >= n {
13569 hi = last;
13570 }
13571 if lo >= n {
13572 lo = last;
13573 }
13574 (lo, hi)
13575}
13576
13577#[allow(clippy::type_complexity)]
13581fn peer_group_start(
13582 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13583 i: usize,
13584) -> usize {
13585 let key = &slice[i].1;
13586 let mut j = i;
13587 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13588 j -= 1;
13589 }
13590 j
13591}
13592
13593#[allow(clippy::type_complexity)]
13596fn peer_group_end(
13597 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13598 i: usize,
13599) -> usize {
13600 let key = &slice[i].1;
13601 let mut j = i;
13602 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13603 j += 1;
13604 }
13605 j
13606}
13607
13608fn value_to_f64(v: &Value) -> Option<f64> {
13609 match v {
13610 Value::SmallInt(n) => Some(f64::from(*n)),
13611 Value::Int(n) => Some(f64::from(*n)),
13612 #[allow(clippy::cast_precision_loss)]
13613 Value::BigInt(n) => Some(*n as f64),
13614 Value::Float(x) => Some(*x),
13615 _ => None,
13616 }
13617}
13618
13619fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13623 let mut any = false;
13624 for item in &stmt.items {
13625 if let SelectItem::Expr { expr, .. } = item {
13626 any = any || expr_has_subquery(expr);
13627 }
13628 }
13629 if let Some(w) = &stmt.where_ {
13630 any = any || expr_has_subquery(w);
13631 }
13632 if let Some(h) = &stmt.having {
13633 any = any || expr_has_subquery(h);
13634 }
13635 for o in &stmt.order_by {
13636 any = any || expr_has_subquery(&o.expr);
13637 }
13638 for (_, peer) in &stmt.unions {
13639 any = any || expr_tree_has_subquery(peer);
13640 }
13641 any
13642}
13643
13644pub(crate) fn expr_has_subquery(e: &Expr) -> bool {
13645 match e {
13646 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13647 Expr::AggregateOrdered { call, order_by, .. } => {
13648 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
13649 }
13650 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13651 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13652 expr_has_subquery(expr)
13653 }
13654 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13655 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13656 Expr::Extract { source, .. } => expr_has_subquery(source),
13657 Expr::WindowFunction {
13658 args,
13659 partition_by,
13660 order_by,
13661 ..
13662 } => {
13663 args.iter().any(expr_has_subquery)
13664 || partition_by.iter().any(expr_has_subquery)
13665 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
13666 }
13667 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13668 Expr::Array(items) => items.iter().any(expr_has_subquery),
13669 Expr::ArraySubscript { target, index } => {
13670 expr_has_subquery(target) || expr_has_subquery(index)
13671 }
13672 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13673 Expr::Case {
13674 operand,
13675 branches,
13676 else_branch,
13677 } => {
13678 operand.as_deref().is_some_and(expr_has_subquery)
13679 || branches
13680 .iter()
13681 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13682 || else_branch.as_deref().is_some_and(expr_has_subquery)
13683 }
13684 }
13685}
13686
13687fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13694 let lit = match v {
13695 Value::Null => Literal::Null,
13696 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13697 Value::Int(n) => Literal::Integer(i64::from(n)),
13698 Value::BigInt(n) => Literal::Integer(n),
13699 Value::Float(x) => Literal::Float(x),
13700 Value::Text(s) | Value::Json(s) => Literal::String(s),
13701 Value::Bool(b) => Literal::Bool(b),
13702 other => {
13703 return Err(EngineError::Unsupported(alloc::format!(
13704 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13705 other.data_type()
13706 )));
13707 }
13708 };
13709 Ok(Expr::Literal(lit))
13710}
13711
13712fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13718 let lit = match v {
13719 Value::Null => Literal::Null,
13720 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13721 Value::Int(n) => Literal::Integer(i64::from(n)),
13722 Value::BigInt(n) => Literal::Integer(n),
13723 Value::Float(x) => Literal::Float(x),
13724 Value::Text(s) | Value::Json(s) => Literal::String(s),
13725 Value::Bool(b) => Literal::Bool(b),
13726 Value::Vector(xs) => Literal::Vector(xs),
13727 Value::Date(days) => {
13731 let micros = (i64::from(days)) * 86_400_000_000;
13732 Literal::String(format_timestamp_micros_as_date(micros))
13733 }
13734 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13735 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13736 other => {
13737 return Err(EngineError::Unsupported(alloc::format!(
13738 "INSERT … SELECT cannot materialise value of type {:?}; \
13739 add an explicit CAST in the inner SELECT",
13740 other.data_type()
13741 )));
13742 }
13743 };
13744 Ok(Expr::Literal(lit))
13745}
13746
13747fn format_timestamp_micros(us: i64) -> String {
13748 let days = us.div_euclid(86_400_000_000);
13750 let intra_day = us.rem_euclid(86_400_000_000);
13751 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13752 let secs = intra_day / 1_000_000;
13753 let us_rem = intra_day % 1_000_000;
13754 let h = (secs / 3600) % 24;
13755 let m = (secs / 60) % 60;
13756 let s = secs % 60;
13757 if us_rem == 0 {
13758 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13759 } else {
13760 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13761 }
13762}
13763
13764fn format_timestamp_micros_as_date(us: i64) -> String {
13765 let days = us.div_euclid(86_400_000_000);
13768 let jdn = days + 2_440_588;
13770 let (y, mo, d) = jdn_to_ymd(jdn);
13771 alloc::format!("{y:04}-{mo:02}-{d:02}")
13772}
13773
13774fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13775 let l = jdn + 68569;
13777 let n = (4 * l) / 146_097;
13778 let l = l - (146_097 * n + 3) / 4;
13779 let i = (4000 * (l + 1)) / 1_461_001;
13780 let l = l - (1461 * i) / 4 + 31;
13781 let j = (80 * l) / 2447;
13782 let day = (l - (2447 * j) / 80) as u32;
13783 let l = j / 11;
13784 let month = (j + 2 - 12 * l) as u32;
13785 let year = 100 * (n - 49) + i + l;
13786 (year, month, day)
13787}
13788
13789fn format_numeric(scaled: i128, scale: u8) -> String {
13790 if scale == 0 {
13791 return alloc::format!("{scaled}");
13792 }
13793 let abs = scaled.unsigned_abs();
13794 let divisor = 10u128.pow(u32::from(scale));
13795 let whole = abs / divisor;
13796 let frac = abs % divisor;
13797 let sign = if scaled < 0 { "-" } else { "" };
13798 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13799}
13800
13801fn rewrite_column_in_source(
13825 src: &str,
13826 old: &str,
13827 new: &str,
13828) -> Result<alloc::string::String, EngineError> {
13829 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13830 EngineError::Unsupported(alloc::format!(
13831 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13832 failed to parse for rewrite ({e})"
13833 ))
13834 })?;
13835 rewrite_column_in_expr(&mut expr, old, new);
13836 Ok(alloc::format!("{expr}"))
13837}
13838
13839fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13847 match e {
13848 Expr::AggregateOrdered { call, order_by, .. } => {
13849 rewrite_column_in_expr(call, old, new);
13850 for o in order_by.iter_mut() {
13851 rewrite_column_in_expr(&mut o.expr, old, new);
13852 }
13853 }
13854 Expr::Column(c) => {
13855 if c.name.eq_ignore_ascii_case(old) {
13856 c.name = new.to_string();
13857 }
13858 }
13859 Expr::Binary { lhs, rhs, .. } => {
13860 rewrite_column_in_expr(lhs, old, new);
13861 rewrite_column_in_expr(rhs, old, new);
13862 }
13863 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13864 rewrite_column_in_expr(expr, old, new);
13865 }
13866 Expr::FunctionCall { args, .. } => {
13867 for a in args {
13868 rewrite_column_in_expr(a, old, new);
13869 }
13870 }
13871 Expr::Like { expr, pattern, .. } => {
13872 rewrite_column_in_expr(expr, old, new);
13873 rewrite_column_in_expr(pattern, old, new);
13874 }
13875 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13876 Expr::WindowFunction {
13877 args,
13878 partition_by,
13879 order_by,
13880 ..
13881 } => {
13882 for a in args {
13883 rewrite_column_in_expr(a, old, new);
13884 }
13885 for p in partition_by {
13886 rewrite_column_in_expr(p, old, new);
13887 }
13888 for (o, _, _) in order_by {
13889 rewrite_column_in_expr(o, old, new);
13890 }
13891 }
13892 Expr::Array(items) => {
13893 for elem in items {
13894 rewrite_column_in_expr(elem, old, new);
13895 }
13896 }
13897 Expr::ArraySubscript { target, index } => {
13898 rewrite_column_in_expr(target, old, new);
13899 rewrite_column_in_expr(index, old, new);
13900 }
13901 Expr::AnyAll { expr, array, .. } => {
13902 rewrite_column_in_expr(expr, old, new);
13903 rewrite_column_in_expr(array, old, new);
13904 }
13905 Expr::Case {
13906 operand,
13907 branches,
13908 else_branch,
13909 } => {
13910 if let Some(o) = operand {
13911 rewrite_column_in_expr(o, old, new);
13912 }
13913 for (w, t) in branches {
13914 rewrite_column_in_expr(w, old, new);
13915 rewrite_column_in_expr(t, old, new);
13916 }
13917 if let Some(e) = else_branch {
13918 rewrite_column_in_expr(e, old, new);
13919 }
13920 }
13921 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13925 Expr::Literal(_) | Expr::Placeholder(_) => {}
13926 }
13927}
13928
13929pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13937 match stmt {
13938 Statement::Select(s) => substitute_select(s, params)?,
13939 Statement::Insert(ins) => {
13940 for row in &mut ins.rows {
13941 for e in row {
13942 substitute_expr(e, params)?;
13943 }
13944 }
13945 if let Some(clause) = &mut ins.on_conflict
13949 && let spg_sql::ast::OnConflictAction::Update {
13950 assignments,
13951 where_,
13952 } = &mut clause.action
13953 {
13954 for (_, e) in assignments.iter_mut() {
13955 substitute_expr(e, params)?;
13956 }
13957 if let Some(w) = where_ {
13958 substitute_expr(w, params)?;
13959 }
13960 }
13961 }
13962 Statement::Update(u) => {
13963 for (_, e) in &mut u.assignments {
13964 substitute_expr(e, params)?;
13965 }
13966 if let Some(w) = &mut u.where_ {
13967 substitute_expr(w, params)?;
13968 }
13969 }
13970 Statement::Delete(d) => {
13971 if let Some(w) = &mut d.where_ {
13972 substitute_expr(w, params)?;
13973 }
13974 }
13975 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13976 _ => {}
13979 }
13980 Ok(())
13981}
13982
13983pub(crate) fn walk_select_exprs_mut(
13993 s: &mut SelectStatement,
13994 f: &mut impl FnMut(&mut Expr) -> Result<(), EngineError>,
13995) -> Result<(), EngineError> {
13996 for cte in &mut s.ctes {
13997 walk_select_exprs_mut(&mut cte.body, f)?;
13998 }
13999 for item in &mut s.items {
14000 if let SelectItem::Expr { expr, .. } = item {
14001 f(expr)?;
14002 }
14003 }
14004 if let Some(from) = &mut s.from {
14005 if let Some(sub) = &mut from.primary.lateral_subquery {
14006 walk_select_exprs_mut(sub, f)?;
14007 }
14008 for j in &mut from.joins {
14009 if let Some(sub) = &mut j.table.lateral_subquery {
14010 walk_select_exprs_mut(sub, f)?;
14011 }
14012 if let Some(on) = &mut j.on {
14013 f(on)?;
14014 }
14015 }
14016 }
14017 if let Some(w) = &mut s.where_ {
14018 f(w)?;
14019 }
14020 if let Some(gs) = &mut s.group_by {
14021 for g in gs {
14022 f(g)?;
14023 }
14024 }
14025 if let Some(h) = &mut s.having {
14026 f(h)?;
14027 }
14028 for o in &mut s.order_by {
14029 f(&mut o.expr)?;
14030 }
14031 for (_, peer) in &mut s.unions {
14032 walk_select_exprs_mut(peer, f)?;
14033 }
14034 Ok(())
14035}
14036
14037fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
14038 walk_select_exprs_mut(s, &mut |e| substitute_expr(e, params))?;
14039 for cte in &mut s.ctes {
14044 resolve_limit_offset_placeholders(&mut cte.body, params)?;
14045 }
14046 for (_, peer) in &mut s.unions {
14047 resolve_limit_offset_placeholders(peer, params)?;
14048 }
14049 if let Some(le) = s.limit {
14054 s.limit = Some(resolve_limit_placeholder(le, params)?);
14055 }
14056 if let Some(le) = s.offset {
14057 s.offset = Some(resolve_limit_placeholder(le, params)?);
14058 }
14059 Ok(())
14060}
14061
14062fn resolve_limit_offset_placeholders(
14065 s: &mut SelectStatement,
14066 params: &[Value],
14067) -> Result<(), EngineError> {
14068 if let Some(le) = s.limit {
14069 s.limit = Some(resolve_limit_placeholder(le, params)?);
14070 }
14071 if let Some(le) = s.offset {
14072 s.offset = Some(resolve_limit_placeholder(le, params)?);
14073 }
14074 for cte in &mut s.ctes {
14075 resolve_limit_offset_placeholders(&mut cte.body, params)?;
14076 }
14077 for (_, peer) in &mut s.unions {
14078 resolve_limit_offset_placeholders(peer, params)?;
14079 }
14080 Ok(())
14081}
14082
14083fn resolve_limit_placeholder(
14084 le: spg_sql::ast::LimitExpr,
14085 params: &[Value],
14086) -> Result<spg_sql::ast::LimitExpr, EngineError> {
14087 use spg_sql::ast::LimitExpr;
14088 match le {
14089 LimitExpr::Literal(_) => Ok(le),
14090 LimitExpr::Placeholder(n) => {
14091 let idx = usize::from(n).saturating_sub(1);
14092 let v = params.get(idx).ok_or_else(|| {
14093 EngineError::Eval(EvalError::PlaceholderOutOfRange {
14094 n,
14095 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
14096 })
14097 })?;
14098 let int = match v {
14099 Value::SmallInt(x) => Some(i64::from(*x)),
14100 Value::Int(x) => Some(i64::from(*x)),
14101 Value::BigInt(x) => Some(*x),
14102 _ => None,
14103 }
14104 .ok_or_else(|| {
14105 EngineError::Unsupported(alloc::format!(
14106 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
14107 ))
14108 })?;
14109 if int < 0 {
14110 return Err(EngineError::Unsupported(alloc::format!(
14111 "LIMIT/OFFSET ${n} bound to negative value {int}"
14112 )));
14113 }
14114 let bounded = u32::try_from(int).map_err(|_| {
14115 EngineError::Unsupported(alloc::format!(
14116 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
14117 ))
14118 })?;
14119 Ok(LimitExpr::Literal(bounded))
14120 }
14121 }
14122}
14123
14124fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
14125 if let Expr::Placeholder(n) = e {
14126 let idx = usize::from(*n).saturating_sub(1);
14127 let v = params.get(idx).ok_or_else(|| {
14128 EngineError::Eval(EvalError::PlaceholderOutOfRange {
14129 n: *n,
14130 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
14131 })
14132 })?;
14133 *e = Expr::Literal(value_to_literal(v.clone()));
14134 return Ok(());
14135 }
14136 match e {
14137 Expr::AggregateOrdered { call, order_by, .. } => {
14138 substitute_expr(call, params)?;
14139 for o in order_by.iter_mut() {
14140 substitute_expr(&mut o.expr, params)?;
14141 }
14142 }
14143 Expr::Binary { lhs, rhs, .. } => {
14144 substitute_expr(lhs, params)?;
14145 substitute_expr(rhs, params)?;
14146 }
14147 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14148 substitute_expr(expr, params)?;
14149 }
14150 Expr::FunctionCall { args, .. } => {
14151 for a in args {
14152 substitute_expr(a, params)?;
14153 }
14154 }
14155 Expr::Like { expr, pattern, .. } => {
14156 substitute_expr(expr, params)?;
14157 substitute_expr(pattern, params)?;
14158 }
14159 Expr::Extract { source, .. } => substitute_expr(source, params)?,
14160 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
14161 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
14162 Expr::InSubquery { expr, subquery, .. } => {
14163 substitute_expr(expr, params)?;
14164 substitute_select(subquery, params)?;
14165 }
14166 Expr::WindowFunction {
14167 args,
14168 partition_by,
14169 order_by,
14170 ..
14171 } => {
14172 for a in args {
14173 substitute_expr(a, params)?;
14174 }
14175 for p in partition_by {
14176 substitute_expr(p, params)?;
14177 }
14178 for (e, _, _) in order_by {
14179 substitute_expr(e, params)?;
14180 }
14181 }
14182 Expr::Literal(_) | Expr::Column(_) => {}
14183 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
14185 Expr::Array(items) => {
14186 for elem in items {
14187 substitute_expr(elem, params)?;
14188 }
14189 }
14190 Expr::ArraySubscript { target, index } => {
14191 substitute_expr(target, params)?;
14192 substitute_expr(index, params)?;
14193 }
14194 Expr::AnyAll { expr, array, .. } => {
14195 substitute_expr(expr, params)?;
14196 substitute_expr(array, params)?;
14197 }
14198 Expr::Case {
14199 operand,
14200 branches,
14201 else_branch,
14202 } => {
14203 if let Some(o) = operand {
14204 substitute_expr(o, params)?;
14205 }
14206 for (w, t) in branches {
14207 substitute_expr(w, params)?;
14208 substitute_expr(t, params)?;
14209 }
14210 if let Some(e) = else_branch {
14211 substitute_expr(e, params)?;
14212 }
14213 }
14214 }
14215 Ok(())
14216}
14217
14218fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
14236 use core::cmp::Ordering;
14237 match (a, b) {
14238 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
14239 (Value::Int(a), Value::Int(b)) => a.cmp(b),
14240 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
14241 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
14242 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
14243 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14244 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
14245 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14246 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
14247 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14248 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
14249 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
14250 (Value::Date(a), Value::Date(b)) => a.cmp(b),
14251 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
14252 (Value::SmallInt(n), Value::Float(x)) => {
14254 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14255 }
14256 (Value::Float(x), Value::SmallInt(n)) => {
14257 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14258 }
14259 (Value::Int(n), Value::Float(x)) => {
14260 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14261 }
14262 (Value::Float(x), Value::Int(n)) => {
14263 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14264 }
14265 (Value::BigInt(n), Value::Float(x)) => {
14266 #[allow(clippy::cast_precision_loss)]
14267 let nf = *n as f64;
14268 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
14269 }
14270 (Value::Float(x), Value::BigInt(n)) => {
14271 #[allow(clippy::cast_precision_loss)]
14272 let nf = *n as f64;
14273 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
14274 }
14275 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
14278 }
14279}
14280
14281fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
14288 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
14289 out.push('[');
14290 for (i, b) in bounds.iter().enumerate() {
14291 if i > 0 {
14292 out.push_str(", ");
14293 }
14294 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
14295 if needs_quote {
14296 out.push('"');
14297 for ch in b.chars() {
14298 if ch == '"' || ch == '\\' {
14299 out.push('\\');
14300 }
14301 out.push(ch);
14302 }
14303 out.push('"');
14304 } else {
14305 out.push_str(b);
14306 }
14307 }
14308 out.push(']');
14309 out
14310}
14311
14312pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
14322 match v {
14323 Value::Null => "NULL".to_string(),
14324 Value::SmallInt(n) => alloc::format!("{n}"),
14325 Value::Int(n) => alloc::format!("{n}"),
14326 Value::BigInt(n) => alloc::format!("{n}"),
14327 Value::Float(x) => alloc::format!("{x:?}"),
14328 Value::Text(s) | Value::Json(s) => s.clone(),
14329 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
14330 Value::Date(d) => eval::format_date(*d),
14331 Value::Timestamp(t) => eval::format_timestamp(*t),
14332 Value::Time(us) => eval::format_time(*us),
14334 Value::Year(y) => alloc::format!("{y:04}"),
14336 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
14338 Value::Money(c) => eval::format_money(*c),
14340 v @ Value::Range { .. } => format_range_str(v),
14342 Value::Hstore(pairs) => format_hstore_str(pairs),
14344 Value::IntArray2D(rows) => format_int_2d_text(rows),
14346 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
14347 Value::TextArray2D(rows) => format_text_2d_text(rows),
14348 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
14349 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
14350 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
14351 alloc::format!("{v:?}")
14355 }
14356 _ => alloc::format!("{v:?}"),
14360 }
14361}
14362
14363const fn is_internal_table_name(_name: &str) -> bool {
14370 false
14371}
14372
14373fn value_to_literal(v: Value) -> Literal {
14374 match v {
14375 Value::Null => Literal::Null,
14376 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14377 Value::Int(n) => Literal::Integer(i64::from(n)),
14378 Value::BigInt(n) => Literal::Integer(n),
14379 Value::Float(x) => Literal::Float(x),
14380 Value::Text(s) | Value::Json(s) => Literal::String(s),
14381 Value::Bool(b) => Literal::Bool(b),
14382 Value::Vector(v) => Literal::Vector(v),
14383 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
14384 Value::Date(d) => Literal::String(eval::format_date(d)),
14385 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14386 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14392 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14397 Value::TextArray(items) => Literal::TextArray(items),
14402 Value::IntArray(items) => Literal::IntArray(items),
14403 Value::BigIntArray(items) => Literal::BigIntArray(items),
14404 Value::Interval { months, micros } => Literal::Interval {
14405 months,
14406 micros,
14407 text: eval::format_interval(months, micros),
14408 },
14409 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14412 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14413 v => Literal::String(alloc::format!("{v:?}")),
14417 }
14418}
14419
14420fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14421 let Some(now) = now_micros else {
14422 return;
14423 };
14424 match stmt {
14425 Statement::Select(s) => rewrite_select_clock(s, now),
14426 Statement::Insert(ins) => {
14427 for row in &mut ins.rows {
14428 for e in row {
14429 rewrite_expr_clock(e, now);
14430 }
14431 }
14432 if let Some(clause) = &mut ins.on_conflict
14436 && let spg_sql::ast::OnConflictAction::Update {
14437 assignments,
14438 where_,
14439 } = &mut clause.action
14440 {
14441 for (_, e) in assignments.iter_mut() {
14442 rewrite_expr_clock(e, now);
14443 }
14444 if let Some(w) = where_ {
14445 rewrite_expr_clock(w, now);
14446 }
14447 }
14448 }
14449 Statement::Update(u) => {
14453 for (_, e) in &mut u.assignments {
14454 rewrite_expr_clock(e, now);
14455 }
14456 if let Some(w) = &mut u.where_ {
14457 rewrite_expr_clock(w, now);
14458 }
14459 }
14460 Statement::Delete(d) => {
14461 if let Some(w) = &mut d.where_ {
14462 rewrite_expr_clock(w, now);
14463 }
14464 }
14465 _ => {}
14466 }
14467}
14468
14469fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14470 let _ = walk_select_exprs_mut(s, &mut |e| {
14475 rewrite_expr_clock(e, now);
14476 Ok(())
14477 });
14478}
14479
14480fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14488 if let Some(replacement) = clock_replacement_for(e, now) {
14492 *e = replacement;
14493 return;
14494 }
14495 match e {
14496 Expr::AggregateOrdered { call, order_by, .. } => {
14497 rewrite_expr_clock(call, now);
14498 for o in order_by.iter_mut() {
14499 rewrite_expr_clock(&mut o.expr, now);
14500 }
14501 }
14502 Expr::Binary { lhs, rhs, .. } => {
14503 rewrite_expr_clock(lhs, now);
14504 rewrite_expr_clock(rhs, now);
14505 }
14506 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14507 rewrite_expr_clock(expr, now);
14508 }
14509 Expr::FunctionCall { args, .. } => {
14510 for a in args {
14511 rewrite_expr_clock(a, now);
14512 }
14513 }
14514 Expr::Like { expr, pattern, .. } => {
14515 rewrite_expr_clock(expr, now);
14516 rewrite_expr_clock(pattern, now);
14517 }
14518 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14519 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14523 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14524 Expr::InSubquery { expr, subquery, .. } => {
14525 rewrite_expr_clock(expr, now);
14526 rewrite_select_clock(subquery, now);
14527 }
14528 Expr::WindowFunction {
14531 args,
14532 partition_by,
14533 order_by,
14534 ..
14535 } => {
14536 for a in args {
14537 rewrite_expr_clock(a, now);
14538 }
14539 for p in partition_by {
14540 rewrite_expr_clock(p, now);
14541 }
14542 for (e, _, _) in order_by {
14543 rewrite_expr_clock(e, now);
14544 }
14545 }
14546 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14547 Expr::Array(items) => {
14548 for elem in items {
14549 rewrite_expr_clock(elem, now);
14550 }
14551 }
14552 Expr::ArraySubscript { target, index } => {
14553 rewrite_expr_clock(target, now);
14554 rewrite_expr_clock(index, now);
14555 }
14556 Expr::AnyAll { expr, array, .. } => {
14557 rewrite_expr_clock(expr, now);
14558 rewrite_expr_clock(array, now);
14559 }
14560 Expr::Case {
14561 operand,
14562 branches,
14563 else_branch,
14564 } => {
14565 if let Some(o) = operand {
14566 rewrite_expr_clock(o, now);
14567 }
14568 for (w, t) in branches {
14569 rewrite_expr_clock(w, now);
14570 rewrite_expr_clock(t, now);
14571 }
14572 if let Some(e) = else_branch {
14573 rewrite_expr_clock(e, now);
14574 }
14575 }
14576 }
14577}
14578
14579fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14586 let (kind, name) = match e {
14587 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14588 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14589 _ => return None,
14590 };
14591 enum ClockShape {
14599 Timestamp,
14600 Date,
14601 UnixSeconds,
14602 }
14603 let shape = match name.len() {
14604 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14605 Some(ClockShape::Timestamp)
14606 }
14607 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14608 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14609 Some(ClockShape::UnixSeconds)
14610 }
14611 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14612 _ => None,
14613 };
14614 let shape = shape?;
14615 let payload = match shape {
14616 ClockShape::Timestamp => now,
14617 ClockShape::Date => now.div_euclid(86_400_000_000),
14618 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14619 };
14620 let target = match shape {
14621 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14622 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14623 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14624 };
14625 Some(Expr::Cast {
14626 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14627 target,
14628 })
14629}
14630
14631#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14632enum ClockSite {
14633 Fn,
14634 BareIdent,
14635}
14636
14637fn expand_group_by_all(s: &mut SelectStatement) {
14648 if !s.group_by_all {
14649 for (_, peer) in &mut s.unions {
14650 expand_group_by_all(peer);
14651 }
14652 return;
14653 }
14654 let mut groups: Vec<Expr> = Vec::new();
14655 for item in &s.items {
14656 if let SelectItem::Expr { expr, .. } = item
14657 && !aggregate::contains_aggregate(expr)
14658 {
14659 groups.push(expr.clone());
14660 }
14661 }
14662 s.group_by = Some(groups);
14663 s.group_by_all = false;
14664 for (_, peer) in &mut s.unions {
14665 expand_group_by_all(peer);
14666 }
14667}
14668
14669fn resolve_order_by_position(s: &mut SelectStatement) {
14670 for order in &mut s.order_by {
14675 match &order.expr {
14676 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14677 if let Ok(idx_one_based) = usize::try_from(*n) {
14678 let idx = idx_one_based - 1;
14679 if idx < s.items.len()
14680 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14681 {
14682 order.expr = expr.clone();
14683 }
14684 }
14685 }
14686 Expr::Column(c) if c.qualifier.is_none() => {
14687 for item in &s.items {
14689 if let SelectItem::Expr {
14690 expr,
14691 alias: Some(a),
14692 } = item
14693 && a == &c.name
14694 {
14695 order.expr = expr.clone();
14696 break;
14697 }
14698 }
14699 }
14700 _ => {}
14701 }
14702 }
14703 for (_, peer) in &mut s.unions {
14704 resolve_order_by_position(peer);
14705 }
14706}
14707
14708fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14721 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14722 match keep {
14723 Some(k) if k < tagged.len() && k > 0 => {
14724 let pivot = k - 1;
14725 tagged.select_nth_unstable_by(pivot, cmp);
14726 tagged[..k].sort_by(cmp);
14727 tagged.truncate(k);
14728 }
14729 _ => {
14730 tagged.sort_by(cmp);
14731 }
14732 }
14733}
14734
14735fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14736 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14737}
14738
14739fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14743 use core::cmp::Ordering;
14744 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14745 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14746 let ord = if descs.get(i).copied().unwrap_or(false) {
14747 ord.reverse()
14748 } else {
14749 ord
14750 };
14751 if ord != Ordering::Equal {
14752 return ord;
14753 }
14754 }
14755 Ordering::Equal
14756}
14757
14758fn build_order_keys(
14761 order_by: &[OrderBy],
14762 row: &Row,
14763 ctx: &EvalContext,
14764) -> Result<Vec<f64>, EngineError> {
14765 let mut keys = Vec::with_capacity(order_by.len());
14766 for o in order_by {
14767 let v = eval::eval_expr(&o.expr, row, ctx)?;
14768 if matches!(v, Value::Null) {
14775 let nf = o.nulls_first.unwrap_or(o.desc);
14776 keys.push(if nf == o.desc {
14777 f64::INFINITY
14778 } else {
14779 f64::NEG_INFINITY
14780 });
14781 } else {
14782 keys.push(value_to_order_key(&v)?);
14783 }
14784 }
14785 Ok(keys)
14786}
14787
14788fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14792 if let Some(off) = offset {
14793 let off = off as usize;
14794 if off >= rows.len() {
14795 rows.clear();
14796 } else {
14797 rows.drain(..off);
14798 }
14799 }
14800 if let Some(n) = limit {
14801 rows.truncate(n as usize);
14802 }
14803}
14804
14805fn apply_offset_and_limit_tagged(
14816 tagged: &mut Vec<(Vec<f64>, Row)>,
14817 offset: Option<u32>,
14818 limit: Option<u32>,
14819 with_ties: bool,
14820) {
14821 if let Some(off) = offset {
14822 let off = off as usize;
14823 if off >= tagged.len() {
14824 tagged.clear();
14825 } else {
14826 tagged.drain(..off);
14827 }
14828 }
14829 if let Some(n) = limit {
14830 let n = n as usize;
14831 if with_ties && n > 0 && n < tagged.len() {
14832 let cutoff_key = tagged[n - 1].0.clone();
14833 let mut end = n;
14834 while end < tagged.len() && tagged[end].0 == cutoff_key {
14835 end += 1;
14836 }
14837 tagged.truncate(end);
14838 } else {
14839 tagged.truncate(n);
14840 }
14841 }
14842}
14843
14844fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14850 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14851 return Err(EngineError::Unsupported(alloc::string::String::from(
14852 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14853 )));
14854 }
14855 Ok(())
14856}
14857
14858fn resolve_foreign_key(
14872 local_table_name: &str,
14873 local_cols: &[ColumnSchema],
14874 fk: spg_sql::ast::ForeignKeyConstraint,
14875 catalog: &Catalog,
14876) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14877 let mut local_columns = Vec::with_capacity(fk.columns.len());
14879 for name in &fk.columns {
14880 let pos = local_cols
14881 .iter()
14882 .position(|c| c.name == *name)
14883 .ok_or_else(|| {
14884 EngineError::Unsupported(alloc::format!(
14885 "FOREIGN KEY references unknown local column {name:?}"
14886 ))
14887 })?;
14888 local_columns.push(pos);
14889 }
14890 let is_self_ref = fk.parent_table == local_table_name;
14894 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14895 (local_cols, local_table_name)
14896 } else {
14897 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14898 EngineError::Storage(StorageError::TableNotFound {
14899 name: fk.parent_table.clone(),
14900 })
14901 })?;
14902 (
14903 parent_table.schema().columns.as_slice(),
14904 fk.parent_table.as_str(),
14905 )
14906 };
14907 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14912 if fk.columns.len() != 1 {
14913 return Err(EngineError::Unsupported(
14914 "composite FOREIGN KEY without explicit parent column list is not supported \
14915 — list the parent columns explicitly"
14916 .into(),
14917 ));
14918 }
14919 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14921 .ok_or_else(|| {
14922 EngineError::Unsupported(alloc::format!(
14923 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14924 to default the FOREIGN KEY against"
14925 ))
14926 })?;
14927 alloc::vec![pos]
14928 } else {
14929 let mut out = Vec::with_capacity(fk.parent_columns.len());
14930 for name in &fk.parent_columns {
14931 let pos = parent_cols_for_lookup
14932 .iter()
14933 .position(|c| c.name == *name)
14934 .ok_or_else(|| {
14935 EngineError::Unsupported(alloc::format!(
14936 "FOREIGN KEY references unknown parent column \
14937 {name:?} on table {parent_table_str:?}"
14938 ))
14939 })?;
14940 out.push(pos);
14941 }
14942 out
14943 };
14944 if parent_columns.len() != local_columns.len() {
14945 return Err(EngineError::Unsupported(alloc::format!(
14946 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14947 local_columns.len(),
14948 parent_columns.len()
14949 )));
14950 }
14951 if !is_self_ref {
14961 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14962 let primary_parent_col = parent_columns[0];
14963 let has_btree = parent_table
14964 .schema()
14965 .columns
14966 .get(primary_parent_col)
14967 .is_some()
14968 && parent_table.indices().iter().any(|idx| {
14969 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14970 && idx.column_position == primary_parent_col
14971 && idx.partial_predicate.is_none()
14972 });
14973 if !has_btree {
14974 return Err(EngineError::Unsupported(alloc::format!(
14975 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14976 index — create one with `CREATE INDEX ... ON {} ({})` first",
14977 parent_table_str,
14978 parent_table_str,
14979 parent_table.schema().columns[primary_parent_col].name,
14980 )));
14981 }
14982 }
14983 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14984 let on_update = fk_action_sql_to_storage(fk.on_update);
14985 Ok(spg_storage::ForeignKeyConstraint {
14986 name: fk.name,
14987 local_columns,
14988 parent_table: fk.parent_table,
14989 parent_columns,
14990 on_delete,
14991 on_update,
14992 })
14993}
14994
14995fn pick_pk_index_column(
15001 catalog: &Catalog,
15002 parent_name: &str,
15003 is_self_ref: bool,
15004 local_cols: &[ColumnSchema],
15005) -> Option<usize> {
15006 if is_self_ref {
15007 let _ = local_cols;
15011 return Some(0);
15012 }
15013 let parent = catalog.get(parent_name)?;
15014 parent.indices().iter().find_map(|idx| {
15015 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15016 && idx.partial_predicate.is_none()
15017 && idx.included_columns.is_empty()
15018 && idx.expression.is_none()
15019 {
15020 Some(idx.column_position)
15021 } else {
15022 None
15023 }
15024 })
15025}
15026
15027fn resolve_on_conflict_columns(
15034 catalog: &Catalog,
15035 table_name: &str,
15036 target: &[String],
15037) -> Result<Vec<usize>, EngineError> {
15038 let table = catalog.get(table_name).ok_or_else(|| {
15039 EngineError::Storage(StorageError::TableNotFound {
15040 name: table_name.into(),
15041 })
15042 })?;
15043 if target.is_empty() {
15044 if let Some(uc) = table.schema().uniqueness_constraints.first() {
15054 return Ok(uc.columns.clone());
15055 }
15056 let pos = table
15057 .indices()
15058 .iter()
15059 .find_map(|idx| {
15060 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15061 && idx.partial_predicate.is_none()
15062 && idx.included_columns.is_empty()
15063 && idx.expression.is_none()
15064 {
15065 Some(idx.column_position)
15066 } else {
15067 None
15068 }
15069 })
15070 .ok_or_else(|| {
15071 EngineError::Unsupported(alloc::format!(
15072 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
15073 ))
15074 })?;
15075 return Ok(alloc::vec![pos]);
15076 }
15077 let mut out = Vec::with_capacity(target.len());
15078 for name in target {
15079 let pos = table
15080 .schema()
15081 .columns
15082 .iter()
15083 .position(|c| c.name == *name)
15084 .ok_or_else(|| {
15085 EngineError::Unsupported(alloc::format!(
15086 "ON CONFLICT target column {name:?} not found on {table_name:?}"
15087 ))
15088 })?;
15089 out.push(pos);
15090 }
15091 Ok(out)
15092}
15093
15094fn on_conflict_key_exists(
15097 catalog: &Catalog,
15098 table_name: &str,
15099 column_pos: usize,
15100 key: &Value,
15101) -> bool {
15102 let Some(table) = catalog.get(table_name) else {
15103 return false;
15104 };
15105 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
15106 return false;
15107 };
15108 table.indices().iter().any(|idx| {
15109 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15110 && idx.column_position == column_pos
15111 && idx.partial_predicate.is_none()
15112 && !idx.lookup_eq(&idx_key).is_empty()
15113 })
15114}
15115
15116fn lookup_row_position_by_keys(
15122 catalog: &Catalog,
15123 table_name: &str,
15124 column_positions: &[usize],
15125 key: &[&Value],
15126) -> Option<usize> {
15127 let table = catalog.get(table_name)?;
15128 table.rows().iter().position(|r| {
15129 column_positions
15130 .iter()
15131 .enumerate()
15132 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15133 })
15134}
15135
15136fn on_conflict_keys_exist(
15141 catalog: &Catalog,
15142 table_name: &str,
15143 column_positions: &[usize],
15144 key: &[&Value],
15145) -> bool {
15146 if column_positions.len() == 1 {
15147 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
15148 }
15149 let Some(table) = catalog.get(table_name) else {
15150 return false;
15151 };
15152 table.rows().iter().any(|r| {
15153 column_positions
15154 .iter()
15155 .enumerate()
15156 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15157 })
15158}
15159
15160fn apply_on_conflict_assignments(
15173 catalog: &Catalog,
15174 table_name: &str,
15175 target_pos: usize,
15176 incoming: &[Value],
15177 assignments: &[(String, Expr)],
15178 where_: Option<&Expr>,
15179) -> Result<Option<Vec<Value>>, EngineError> {
15180 let table = catalog.get(table_name).ok_or_else(|| {
15181 EngineError::Storage(StorageError::TableNotFound {
15182 name: table_name.into(),
15183 })
15184 })?;
15185 let schema_cols = table.schema().columns.clone();
15186 let existing = table
15187 .rows()
15188 .get(target_pos)
15189 .ok_or_else(|| {
15190 EngineError::Unsupported(alloc::format!(
15191 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
15192 ))
15193 })?
15194 .clone();
15195 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
15196 if let Some(w) = where_ {
15198 let pred = w.clone();
15199 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
15200 let v = eval::eval_expr(&pred, &existing, &ctx)?;
15201 if !matches!(v, Value::Bool(true)) {
15202 return Ok(None);
15203 }
15204 }
15205 let mut new_values = existing.values.clone();
15206 for (col_name, expr) in assignments {
15207 let target_idx = schema_cols
15208 .iter()
15209 .position(|c| c.name == *col_name)
15210 .ok_or_else(|| {
15211 EngineError::Eval(EvalError::ColumnNotFound {
15212 name: col_name.clone(),
15213 })
15214 })?;
15215 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
15216 let v = eval::eval_expr(&sub, &existing, &ctx)?;
15217 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
15218 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
15219 new_values[target_idx] = coerced;
15220 }
15221 Ok(Some(new_values))
15222}
15223
15224fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
15229 use spg_sql::ast::ColumnName;
15230 match expr {
15231 Expr::Column(ColumnName { qualifier, name })
15232 if qualifier
15233 .as_deref()
15234 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
15235 {
15236 let pos = schema_cols.iter().position(|c| c.name == name);
15237 match pos {
15238 Some(p) => {
15239 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
15240 value_to_literal_expr(v)
15241 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
15242 }
15243 None => Expr::Column(ColumnName { qualifier, name }),
15244 }
15245 }
15246 Expr::Binary { op, lhs, rhs } => Expr::Binary {
15247 op,
15248 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
15249 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
15250 },
15251 Expr::Unary { op, expr } => Expr::Unary {
15252 op,
15253 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
15254 },
15255 Expr::FunctionCall { name, args } => Expr::FunctionCall {
15256 name,
15257 args: args
15258 .into_iter()
15259 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
15260 .collect(),
15261 },
15262 other => other,
15263 }
15264}
15265
15266fn enforce_uniqueness_inserts(
15289 catalog: &Catalog,
15290 child_table: &str,
15291 constraints: &[spg_storage::UniquenessConstraint],
15292 rows: &[Vec<Value>],
15293) -> Result<(), EngineError> {
15294 if constraints.is_empty() {
15295 return Ok(());
15296 }
15297 let table = catalog.get(child_table).ok_or_else(|| {
15298 EngineError::Storage(StorageError::TableNotFound {
15299 name: child_table.into(),
15300 })
15301 })?;
15302 let schema = table.schema();
15303 for uc in constraints {
15304 for (batch_idx, row_values) in rows.iter().enumerate() {
15305 let key: Vec<Value> = uc
15314 .columns
15315 .iter()
15316 .map(|&i| collated_key_cell(&row_values[i], i, schema))
15317 .collect();
15318 let has_null = key.iter().any(|v| matches!(v, Value::Null));
15319 if has_null && !uc.nulls_not_distinct {
15324 continue;
15325 }
15326 let collides_in_table = table.rows().iter().any(|prow| {
15328 uc.columns.iter().enumerate().all(|(i, &p)| {
15329 prow.values
15330 .get(p)
15331 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15332 })
15333 });
15334 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
15336 uc.columns.iter().enumerate().all(|(i, &p)| {
15337 earlier
15338 .get(p)
15339 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15340 })
15341 });
15342 if collides_in_table || collides_in_batch {
15343 let kind = if uc.is_primary_key {
15344 "PRIMARY KEY"
15345 } else {
15346 "UNIQUE"
15347 };
15348 let col_names: Vec<String> = uc
15349 .columns
15350 .iter()
15351 .map(|&i| table.schema().columns[i].name.clone())
15352 .collect();
15353 return Err(EngineError::Unsupported(alloc::format!(
15354 "{kind} violation on {child_table:?} columns {col_names:?}: \
15355 row #{batch_idx} duplicates an existing key"
15356 )));
15357 }
15358 }
15359 }
15360 Ok(())
15361}
15362
15363fn collated_key_cell(
15370 v: &spg_storage::Value,
15371 column_position: usize,
15372 schema: &spg_storage::TableSchema,
15373) -> spg_storage::Value {
15374 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
15375 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
15376 spg_storage::Value::Text(s.to_ascii_lowercase())
15377 }
15378 _ => v.clone(),
15379 }
15380}
15381
15382fn predicate_truthy(v: &spg_storage::Value) -> bool {
15390 use spg_storage::Value as V;
15391 match v {
15392 V::Bool(b) => *b,
15393 V::Int(n) => *n != 0,
15394 V::BigInt(n) => *n != 0,
15395 V::SmallInt(n) => *n != 0,
15396 _ => false,
15397 }
15398}
15399
15400fn check_existing_unique_violation(
15405 idx: &spg_storage::Index,
15406 schema: &spg_storage::TableSchema,
15407 rows: &[spg_storage::Row],
15408) -> Result<(), EngineError> {
15409 let predicate_expr = match idx.partial_predicate.as_deref() {
15410 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15411 EngineError::Unsupported(alloc::format!(
15412 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15413 ))
15414 })?),
15415 None => None,
15416 };
15417 let ctx = eval::EvalContext::new(&schema.columns, None);
15418 let key_positions = unique_key_positions(idx);
15419 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15420 for row in rows {
15421 if let Some(expr) = &predicate_expr {
15422 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15423 EngineError::Unsupported(alloc::format!(
15424 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15425 ))
15426 })?;
15427 if !predicate_truthy(&v) {
15428 continue;
15429 }
15430 }
15431 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15432 .iter()
15433 .map(|&p| {
15434 let v = row
15435 .values
15436 .get(p)
15437 .cloned()
15438 .unwrap_or(spg_storage::Value::Null);
15439 collated_key_cell(&v, p, schema)
15440 })
15441 .collect();
15442 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15443 continue;
15444 }
15445 if seen.iter().any(|other| *other == key) {
15446 return Err(EngineError::Unsupported(alloc::format!(
15447 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15448 idx.name
15449 )));
15450 }
15451 seen.push(key);
15452 }
15453 Ok(())
15454}
15455
15456fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15460 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15461 out.push(idx.column_position);
15462 out.extend_from_slice(&idx.extra_column_positions);
15463 out
15464}
15465
15466fn enforce_unique_index_inserts(
15474 catalog: &Catalog,
15475 table_name: &str,
15476 rows: &[alloc::vec::Vec<spg_storage::Value>],
15477) -> Result<(), EngineError> {
15478 let table = catalog.get(table_name).ok_or_else(|| {
15479 EngineError::Storage(StorageError::TableNotFound {
15480 name: table_name.into(),
15481 })
15482 })?;
15483 let schema = table.schema();
15484 let ctx = eval::EvalContext::new(&schema.columns, None);
15485 for idx in table.indices() {
15486 if !idx.is_unique {
15487 continue;
15488 }
15489 let predicate_expr = match idx.partial_predicate.as_deref() {
15491 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15492 EngineError::Unsupported(alloc::format!(
15493 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15494 idx.name
15495 ))
15496 })?),
15497 None => None,
15498 };
15499 let key_positions = unique_key_positions(idx);
15500 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15501 key_positions
15505 .iter()
15506 .map(|&p| {
15507 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15508 collated_key_cell(&v, p, schema)
15509 })
15510 .collect()
15511 };
15512 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15516 let Some(expr) = &predicate_expr else {
15517 return Ok(true);
15518 };
15519 let tmp_row = spg_storage::Row {
15520 values: values.to_vec(),
15521 };
15522 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15523 EngineError::Unsupported(alloc::format!(
15524 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15525 idx.name
15526 ))
15527 })?;
15528 Ok(predicate_truthy(&v))
15529 };
15530 for (batch_idx, row_values) in rows.iter().enumerate() {
15531 if !participates(row_values)? {
15532 continue;
15533 }
15534 let key = key_of(row_values);
15535 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15536 continue;
15537 }
15538 for prow in table.rows() {
15540 if !participates(&prow.values)? {
15541 continue;
15542 }
15543 if key_of(&prow.values) == key {
15544 return Err(EngineError::Unsupported(alloc::format!(
15545 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15546 row #{batch_idx} duplicates an existing key",
15547 idx.name
15548 )));
15549 }
15550 }
15551 for earlier in &rows[..batch_idx] {
15553 if !participates(earlier)? {
15554 continue;
15555 }
15556 if key_of(earlier) == key {
15557 return Err(EngineError::Unsupported(alloc::format!(
15558 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15559 row #{batch_idx} duplicates an earlier row in the same batch",
15560 idx.name
15561 )));
15562 }
15563 }
15564 }
15565 }
15566 Ok(())
15567}
15568
15569fn any_column_changed(
15577 filter_cols: &[String],
15578 schema_cols: &[ColumnSchema],
15579 old_row: &Row,
15580 new_row: &Row,
15581) -> bool {
15582 for col_name in filter_cols {
15583 let Some(pos) = schema_cols
15584 .iter()
15585 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15586 else {
15587 continue;
15588 };
15589 let old_v = old_row.values.get(pos);
15590 let new_v = new_row.values.get(pos);
15591 if old_v != new_v {
15592 return true;
15593 }
15594 }
15595 false
15596}
15597
15598fn enforce_check_constraints(
15603 catalog: &Catalog,
15604 table_name: &str,
15605 rows: &[alloc::vec::Vec<spg_storage::Value>],
15606) -> Result<(), EngineError> {
15607 let table = catalog.get(table_name).ok_or_else(|| {
15608 EngineError::Storage(StorageError::TableNotFound {
15609 name: table_name.into(),
15610 })
15611 })?;
15612 let schema = table.schema();
15613 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15617 alloc::vec::Vec::new();
15618 for (idx, col) in schema.columns.iter().enumerate() {
15619 let Some(dname) = &col.user_domain_type else {
15620 continue;
15621 };
15622 let Some(dom) = catalog.domain_types().get(dname) else {
15623 continue;
15624 };
15625 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15626 alloc::vec::Vec::with_capacity(dom.checks.len());
15627 for src in &dom.checks {
15628 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15629 EngineError::Unsupported(alloc::format!(
15630 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15631 col.name
15632 ))
15633 })?;
15634 parsed_for_col.push(expr);
15635 }
15636 if !parsed_for_col.is_empty() {
15637 domain_checks_per_col.push((idx, parsed_for_col));
15638 }
15639 }
15640 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15641 return Ok(());
15642 }
15643 let ctx = eval::EvalContext::new(&schema.columns, None);
15644 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15645 for (i, src) in schema.checks.iter().enumerate() {
15646 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15647 EngineError::Unsupported(alloc::format!(
15648 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15649 ))
15650 })?;
15651 parsed.push((i, expr));
15652 }
15653 for (batch_idx, row_values) in rows.iter().enumerate() {
15654 let tmp_row = spg_storage::Row {
15655 values: row_values.clone(),
15656 };
15657 for (i, expr) in &parsed {
15658 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15659 EngineError::Unsupported(alloc::format!(
15660 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15661 ))
15662 })?;
15663 if matches!(v, spg_storage::Value::Bool(false)) {
15665 return Err(EngineError::Unsupported(alloc::format!(
15666 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15667 schema.checks[*i]
15668 )));
15669 }
15670 }
15671 for (col_idx, checks) in &domain_checks_per_col {
15677 let cell = row_values
15678 .get(*col_idx)
15679 .cloned()
15680 .unwrap_or(spg_storage::Value::Null);
15681 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15682 "value",
15683 schema.columns[*col_idx].ty,
15684 schema.columns[*col_idx].nullable,
15685 )];
15686 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15687 let synth_row = spg_storage::Row {
15688 values: alloc::vec![cell],
15689 };
15690 for (ci, expr) in checks.iter().enumerate() {
15691 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15692 EngineError::Unsupported(alloc::format!(
15693 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15694 schema.columns[*col_idx].name
15695 ))
15696 })?;
15697 if matches!(v, spg_storage::Value::Bool(false)) {
15698 return Err(EngineError::Unsupported(alloc::format!(
15699 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15700 schema.columns[*col_idx].name
15701 )));
15702 }
15703 }
15704 }
15705 }
15706 Ok(())
15707}
15708
15709fn enforce_fk_inserts(
15710 catalog: &Catalog,
15711 child_table: &str,
15712 fks: &[spg_storage::ForeignKeyConstraint],
15713 rows: &[Vec<Value>],
15714) -> Result<(), EngineError> {
15715 for fk in fks {
15716 let parent_is_self = fk.parent_table == child_table;
15717 let parent = if parent_is_self {
15718 catalog.get(child_table).ok_or_else(|| {
15721 EngineError::Storage(StorageError::TableNotFound {
15722 name: child_table.into(),
15723 })
15724 })?
15725 } else {
15726 catalog.get(&fk.parent_table).ok_or_else(|| {
15727 EngineError::Storage(StorageError::TableNotFound {
15728 name: fk.parent_table.clone(),
15729 })
15730 })?
15731 };
15732 for (batch_idx, row_values) in rows.iter().enumerate() {
15733 if fk.local_columns.len() == 1 {
15737 let v = &row_values[fk.local_columns[0]];
15738 if matches!(v, Value::Null) {
15739 continue;
15740 }
15741 let parent_col = fk.parent_columns[0];
15742 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15743 EngineError::Unsupported(alloc::format!(
15744 "FOREIGN KEY column value of type {:?} is not index-eligible",
15745 v.data_type()
15746 ))
15747 })?;
15748 let present_committed = parent.indices().iter().any(|idx| {
15749 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15750 && idx.column_position == parent_col
15751 && idx.partial_predicate.is_none()
15752 && !idx.lookup_eq(&key).is_empty()
15753 });
15754 let present_in_batch = parent_is_self
15758 && rows[..batch_idx]
15759 .iter()
15760 .any(|earlier| earlier.get(parent_col) == Some(v));
15761 if !(present_committed || present_in_batch) {
15762 return Err(EngineError::Unsupported(alloc::format!(
15763 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15764 fk.parent_table,
15765 parent
15766 .schema()
15767 .columns
15768 .get(parent_col)
15769 .map_or("?", |c| c.name.as_str()),
15770 v,
15771 )));
15772 }
15773 } else {
15774 if fk
15778 .local_columns
15779 .iter()
15780 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15781 {
15782 continue;
15783 }
15784 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15785 let parent_match_committed = parent.rows().iter().any(|prow| {
15786 fk.parent_columns
15787 .iter()
15788 .enumerate()
15789 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15790 });
15791 let parent_match_in_batch = parent_is_self
15792 && rows[..batch_idx].iter().any(|earlier| {
15793 fk.parent_columns
15794 .iter()
15795 .enumerate()
15796 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15797 });
15798 if !(parent_match_committed || parent_match_in_batch) {
15799 return Err(EngineError::Unsupported(alloc::format!(
15800 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15801 fk.parent_table,
15802 )));
15803 }
15804 }
15805 }
15806 }
15807 Ok(())
15808}
15809
15810#[derive(Debug, Clone)]
15814struct FkChildStep {
15815 child_table: String,
15816 action: FkChildAction,
15817}
15818
15819#[derive(Debug, Clone)]
15820enum FkChildAction {
15821 Delete { positions: Vec<usize> },
15823 SetNull {
15827 positions: Vec<usize>,
15828 columns: Vec<usize>,
15829 },
15830 SetDefault {
15834 positions: Vec<usize>,
15835 columns: Vec<usize>,
15836 defaults: Vec<Value>,
15837 },
15838}
15839
15840fn plan_fk_parent_deletions(
15856 catalog: &Catalog,
15857 parent_table_name: &str,
15858 to_delete_positions: &[usize],
15859 to_delete_rows: &[Vec<Value>],
15860) -> Result<Vec<FkChildStep>, EngineError> {
15861 use alloc::collections::{BTreeMap, BTreeSet};
15862 if to_delete_rows.is_empty() {
15863 return Ok(Vec::new());
15864 }
15865 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15866 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15868 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15869 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15870 for &p in to_delete_positions {
15871 visited.insert((parent_table_name.to_string(), p));
15872 }
15873 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15874 .iter()
15875 .map(|r| (parent_table_name.to_string(), r.clone()))
15876 .collect();
15877 while let Some((cur_parent, parent_row)) = work.pop() {
15878 for child_name in catalog.table_names() {
15879 let child = catalog
15880 .get(&child_name)
15881 .expect("table_names → catalog.get round-trip is total");
15882 for fk in &child.schema().foreign_keys {
15883 if fk.parent_table != cur_parent {
15884 continue;
15885 }
15886 let parent_key: Vec<&Value> = fk
15887 .parent_columns
15888 .iter()
15889 .map(|&pi| &parent_row[pi])
15890 .collect();
15891 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15892 continue;
15893 }
15894 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15895 if child_name == cur_parent
15896 && visited.contains(&(child_name.clone(), child_row_idx))
15897 {
15898 continue;
15899 }
15900 let matches_key = fk
15901 .local_columns
15902 .iter()
15903 .enumerate()
15904 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15905 if !matches_key {
15906 continue;
15907 }
15908 match fk.on_delete {
15909 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15910 return Err(EngineError::Unsupported(alloc::format!(
15911 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15912 restricted by FK from {child_name:?}.{:?}",
15913 fk.local_columns,
15914 )));
15915 }
15916 spg_storage::FkAction::Cascade => {
15917 if visited.insert((child_name.clone(), child_row_idx)) {
15918 delete_plan
15919 .entry(child_name.clone())
15920 .or_default()
15921 .insert(child_row_idx);
15922 work.push((child_name.clone(), child_row.values.clone()));
15923 }
15924 }
15925 spg_storage::FkAction::SetNull => {
15926 for &li in &fk.local_columns {
15928 let col = child.schema().columns.get(li).ok_or_else(|| {
15929 EngineError::Unsupported(alloc::format!(
15930 "FK local column {li} missing in {child_name:?}"
15931 ))
15932 })?;
15933 if !col.nullable {
15934 return Err(EngineError::Unsupported(alloc::format!(
15935 "FOREIGN KEY ON DELETE SET NULL: column \
15936 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15937 col.name,
15938 )));
15939 }
15940 }
15941 let entry = setnull_plan.entry(child_name.clone()).or_default();
15942 for &li in &fk.local_columns {
15943 entry.insert((child_row_idx, li));
15944 }
15945 }
15946 spg_storage::FkAction::SetDefault => {
15947 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15949 for &li in &fk.local_columns {
15950 let col = child.schema().columns.get(li).ok_or_else(|| {
15951 EngineError::Unsupported(alloc::format!(
15952 "FK local column {li} missing in {child_name:?}"
15953 ))
15954 })?;
15955 let default = col.default.clone().ok_or_else(|| {
15956 EngineError::Unsupported(alloc::format!(
15957 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15958 {child_name:?}.{:?} has no DEFAULT declared",
15959 col.name,
15960 ))
15961 })?;
15962 entry.insert((child_row_idx, li), default);
15963 }
15964 }
15965 }
15966 }
15967 }
15968 }
15969 }
15970 let mut steps: Vec<FkChildStep> = Vec::new();
15978 for (child_table, entries) in setnull_plan {
15979 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15980 steps.push(FkChildStep {
15981 child_table,
15982 action: FkChildAction::SetNull { positions, columns },
15983 });
15984 }
15985 for (child_table, entries) in setdefault_plan {
15986 let mut positions = Vec::with_capacity(entries.len());
15987 let mut columns = Vec::with_capacity(entries.len());
15988 let mut defaults = Vec::with_capacity(entries.len());
15989 for ((p, c), v) in entries {
15990 positions.push(p);
15991 columns.push(c);
15992 defaults.push(v);
15993 }
15994 steps.push(FkChildStep {
15995 child_table,
15996 action: FkChildAction::SetDefault {
15997 positions,
15998 columns,
15999 defaults,
16000 },
16001 });
16002 }
16003 for (child_table, positions) in delete_plan {
16004 steps.push(FkChildStep {
16005 child_table,
16006 action: FkChildAction::Delete {
16007 positions: positions.into_iter().collect(),
16008 },
16009 });
16010 }
16011 Ok(steps)
16012}
16013
16014fn plan_fk_parent_updates(
16031 catalog: &Catalog,
16032 parent_table_name: &str,
16033 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
16034) -> Result<Vec<FkChildStep>, EngineError> {
16035 use alloc::collections::BTreeMap;
16036 if plan_with_old.is_empty() {
16037 return Ok(Vec::new());
16038 }
16039 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
16044 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
16045 BTreeMap::new();
16046 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
16047 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
16049
16050 for child_name in catalog.table_names() {
16051 let child = catalog
16052 .get(&child_name)
16053 .expect("table_names → catalog.get total");
16054 for fk in &child.schema().foreign_keys {
16055 if fk.parent_table != parent_table_name {
16056 continue;
16057 }
16058 for (_pos, old_row, new_row) in plan_with_old {
16059 let key_changed = fk
16061 .parent_columns
16062 .iter()
16063 .any(|&pi| old_row.get(pi) != new_row.get(pi));
16064 if !key_changed {
16065 continue;
16066 }
16067 let old_key: Vec<&Value> =
16069 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
16070 if old_key.iter().any(|v| matches!(v, Value::Null)) {
16071 continue;
16073 }
16074 let new_key: Vec<&Value> =
16075 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
16076 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
16077 if child_name == parent_table_name
16080 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
16081 {
16082 continue;
16083 }
16084 let matches_key = fk
16085 .local_columns
16086 .iter()
16087 .enumerate()
16088 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
16089 if !matches_key {
16090 continue;
16091 }
16092 match fk.on_update {
16093 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
16094 return Err(EngineError::Unsupported(alloc::format!(
16095 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
16096 restricted by FK from {child_name:?}.{:?}",
16097 fk.local_columns,
16098 )));
16099 }
16100 spg_storage::FkAction::Cascade => {
16101 let entry = cascade_plan.entry(child_name.clone()).or_default();
16103 for (i, &li) in fk.local_columns.iter().enumerate() {
16104 entry.insert((child_row_idx, li), new_key[i].clone());
16105 }
16106 }
16107 spg_storage::FkAction::SetNull => {
16108 for &li in &fk.local_columns {
16109 let col = child.schema().columns.get(li).ok_or_else(|| {
16110 EngineError::Unsupported(alloc::format!(
16111 "FK local column {li} missing in {child_name:?}"
16112 ))
16113 })?;
16114 if !col.nullable {
16115 return Err(EngineError::Unsupported(alloc::format!(
16116 "FOREIGN KEY ON UPDATE SET NULL: column \
16117 {child_name:?}.{:?} is NOT NULL",
16118 col.name,
16119 )));
16120 }
16121 }
16122 let entry = setnull_plan.entry(child_name.clone()).or_default();
16123 for &li in &fk.local_columns {
16124 entry.insert((child_row_idx, li));
16125 }
16126 }
16127 spg_storage::FkAction::SetDefault => {
16128 let entry = setdefault_plan.entry(child_name.clone()).or_default();
16129 for &li in &fk.local_columns {
16130 let col = child.schema().columns.get(li).ok_or_else(|| {
16131 EngineError::Unsupported(alloc::format!(
16132 "FK local column {li} missing in {child_name:?}"
16133 ))
16134 })?;
16135 let default = col.default.clone().ok_or_else(|| {
16136 EngineError::Unsupported(alloc::format!(
16137 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
16138 {child_name:?}.{:?} has no DEFAULT",
16139 col.name,
16140 ))
16141 })?;
16142 entry.insert((child_row_idx, li), default);
16143 }
16144 }
16145 }
16146 }
16147 }
16148 }
16149 }
16150 let mut steps: Vec<FkChildStep> = Vec::new();
16153 for (child_table, entries) in cascade_plan {
16154 let mut positions = Vec::with_capacity(entries.len());
16155 let mut columns = Vec::with_capacity(entries.len());
16156 let mut defaults = Vec::with_capacity(entries.len());
16157 for ((p, c), v) in entries {
16158 positions.push(p);
16159 columns.push(c);
16160 defaults.push(v);
16161 }
16162 steps.push(FkChildStep {
16167 child_table,
16168 action: FkChildAction::SetDefault {
16169 positions,
16170 columns,
16171 defaults,
16172 },
16173 });
16174 }
16175 for (child_table, entries) in setnull_plan {
16176 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16177 steps.push(FkChildStep {
16178 child_table,
16179 action: FkChildAction::SetNull { positions, columns },
16180 });
16181 }
16182 for (child_table, entries) in setdefault_plan {
16183 let mut positions = Vec::with_capacity(entries.len());
16184 let mut columns = Vec::with_capacity(entries.len());
16185 let mut defaults = Vec::with_capacity(entries.len());
16186 for ((p, c), v) in entries {
16187 positions.push(p);
16188 columns.push(c);
16189 defaults.push(v);
16190 }
16191 steps.push(FkChildStep {
16192 child_table,
16193 action: FkChildAction::SetDefault {
16194 positions,
16195 columns,
16196 defaults,
16197 },
16198 });
16199 }
16200 let _ = delete_plan; Ok(steps)
16202}
16203
16204fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
16208 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
16209 EngineError::Storage(StorageError::TableNotFound {
16210 name: step.child_table.clone(),
16211 })
16212 })?;
16213 match &step.action {
16214 FkChildAction::Delete { positions } => {
16215 let _ = child.delete_rows(positions);
16216 }
16217 FkChildAction::SetNull { positions, columns } => {
16218 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
16219 }
16220 FkChildAction::SetDefault {
16221 positions,
16222 columns,
16223 defaults,
16224 } => {
16225 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
16226 }
16227 }
16228 Ok(())
16229}
16230
16231fn apply_per_cell_writes(
16237 child: &mut spg_storage::Table,
16238 positions: &[usize],
16239 columns: &[usize],
16240 mut value_for: impl FnMut(usize) -> Value,
16241) -> Result<(), EngineError> {
16242 use alloc::collections::BTreeMap;
16243 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
16244 for i in 0..positions.len() {
16245 by_row
16246 .entry(positions[i])
16247 .or_default()
16248 .push((columns[i], value_for(i)));
16249 }
16250 for (pos, mutations) in by_row {
16251 let mut new_values = child.rows()[pos].values.clone();
16252 for (col, v) in mutations {
16253 if let Some(slot) = new_values.get_mut(col) {
16254 *slot = v;
16255 }
16256 }
16257 child
16258 .update_row(pos, new_values)
16259 .map_err(EngineError::Storage)?;
16260 }
16261 Ok(())
16262}
16263
16264fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
16265 match a {
16266 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
16267 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
16268 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
16269 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
16270 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
16271 }
16272}
16273
16274fn resolve_column_default_free(
16280 col: &ColumnSchema,
16281 clock_fn: Option<ClockFn>,
16282) -> Result<Value, EngineError> {
16283 if let Some(rt) = &col.runtime_default {
16284 return eval_runtime_default_free(rt, col.ty, clock_fn);
16285 }
16286 Ok(col.default.clone().unwrap_or(Value::Null))
16287}
16288
16289fn eval_runtime_default_free(
16290 rt: &str,
16291 ty: DataType,
16292 clock_fn: Option<ClockFn>,
16293) -> Result<Value, EngineError> {
16294 let s = rt.trim().to_ascii_lowercase();
16295 let with_no_parens = s.trim_end_matches("()");
16301 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
16302 if with_no_parens.ends_with(')') {
16303 &with_no_parens[..open_idx]
16304 } else {
16305 with_no_parens
16306 }
16307 } else {
16308 with_no_parens
16309 };
16310 let now_us = match clock_fn {
16311 Some(f) => f(),
16312 None => 0,
16313 };
16314 let v = match canonical {
16315 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
16316 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
16317 "current_time" | "localtime" => Value::Timestamp(now_us),
16318 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
16324 other => {
16325 return Err(EngineError::Unsupported(alloc::format!(
16326 "runtime DEFAULT expression {other:?} not supported \
16327 (v7.17.0 whitelist: now() / current_timestamp / \
16328 current_date / current_time / localtimestamp / \
16329 localtime / gen_random_uuid() / \
16330 uuid_generate_v4())"
16331 )));
16332 }
16333 };
16334 coerce_value(v, ty, "DEFAULT", 0)
16335}
16336
16337fn is_runtime_default_expr(expr: &Expr) -> bool {
16343 match expr {
16344 Expr::FunctionCall { .. } => true,
16345 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
16346 _ => false,
16347 }
16348}
16349
16350fn canonicalize_set_value(
16363 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16364 col_idx: usize,
16365 col_name: &str,
16366 value: Value,
16367) -> Result<Value, EngineError> {
16368 let Some(variants) = lookup.get(&col_idx) else {
16369 return Ok(value);
16370 };
16371 match value {
16372 Value::Null => Ok(Value::Null),
16373 Value::Text(s) => {
16374 if s.is_empty() {
16375 return Ok(Value::Text(alloc::string::String::new()));
16376 }
16377 let mut present = alloc::vec![false; variants.len()];
16380 for raw in s.split(',') {
16381 let tok = raw.trim();
16382 if tok.is_empty() {
16383 continue;
16384 }
16385 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
16386 EngineError::Unsupported(alloc::format!(
16387 "column {col_name:?}: invalid SET token {tok:?}; \
16388 allowed: {variants:?}"
16389 ))
16390 })?;
16391 present[idx] = true;
16392 }
16393 let mut out = alloc::string::String::new();
16395 let mut first = true;
16396 for (i, keep) in present.iter().enumerate() {
16397 if !keep {
16398 continue;
16399 }
16400 if !first {
16401 out.push(',');
16402 }
16403 first = false;
16404 out.push_str(&variants[i]);
16405 }
16406 Ok(Value::Text(out))
16407 }
16408 other => Err(EngineError::Unsupported(alloc::format!(
16409 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16410 other.data_type()
16411 ))),
16412 }
16413}
16414
16415fn enforce_enum_label(
16416 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16417 col_idx: usize,
16418 col_name: &str,
16419 value: &Value,
16420) -> Result<(), EngineError> {
16421 if let Some(labels) = lookup.get(&col_idx) {
16422 match value {
16423 Value::Null => Ok(()),
16424 Value::Text(s) => {
16425 if labels.iter().any(|l| l == s) {
16426 Ok(())
16427 } else {
16428 Err(EngineError::Unsupported(alloc::format!(
16429 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16430 )))
16431 }
16432 }
16433 other => Err(EngineError::Unsupported(alloc::format!(
16434 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16435 other.data_type()
16436 ))),
16437 }
16438 } else {
16439 Ok(())
16440 }
16441}
16442
16443fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16444 let ty = column_type_to_data_type(c.ty);
16445 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16446 if let Some(name) = c.user_type_ref {
16453 schema.user_enum_type = Some(name);
16454 }
16455 if let Some(expr) = c.on_update_runtime {
16458 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16459 }
16460 schema.collation = match c.collation {
16464 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16465 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16466 };
16467 schema.is_unsigned = c.is_unsigned;
16470 schema.inline_enum_variants = c.inline_enum_variants;
16474 schema.inline_set_variants = c.inline_set_variants;
16478 if let Some(default_expr) = c.default {
16479 if is_runtime_default_expr(&default_expr) {
16485 let display = alloc::format!("{default_expr}");
16486 schema = schema.with_runtime_default(display);
16487 } else {
16488 let raw = literal_expr_to_value(default_expr)?;
16489 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16490 schema = schema.with_default(coerced);
16491 }
16492 }
16493 if c.auto_increment {
16494 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16496 return Err(EngineError::Unsupported(alloc::format!(
16497 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16498 )));
16499 }
16500 schema = schema.with_auto_increment();
16501 }
16502 Ok(schema)
16503}
16504
16505fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16510 let s = s.trim();
16511 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16512 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16514 if cleaned.len() % 2 != 0 {
16515 return Err("odd-length hex literal");
16516 }
16517 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16518 let cleaned_bytes = cleaned.as_bytes();
16519 for i in (0..cleaned_bytes.len()).step_by(2) {
16520 let hi = hex_nibble(cleaned_bytes[i])?;
16521 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16522 out.push((hi << 4) | lo);
16523 }
16524 return Ok(out);
16525 }
16526 let bytes = s.as_bytes();
16529 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16530 let mut i = 0;
16531 while i < bytes.len() {
16532 let b = bytes[i];
16533 if b == b'\\' && i + 1 < bytes.len() {
16534 let n = bytes[i + 1];
16535 if n == b'\\' {
16536 out.push(b'\\');
16537 i += 2;
16538 continue;
16539 }
16540 if n.is_ascii_digit()
16541 && i + 3 < bytes.len()
16542 && bytes[i + 2].is_ascii_digit()
16543 && bytes[i + 3].is_ascii_digit()
16544 {
16545 let oct = |x: u8| (x - b'0') as u32;
16546 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16547 if v <= 0xFF {
16548 out.push(v as u8);
16549 i += 4;
16550 continue;
16551 }
16552 }
16553 }
16554 out.push(b);
16555 i += 1;
16556 }
16557 Ok(out)
16558}
16559
16560fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16561 match b {
16562 b'0'..=b'9' => Ok(b - b'0'),
16563 b'a'..=b'f' => Ok(b - b'a' + 10),
16564 b'A'..=b'F' => Ok(b - b'A' + 10),
16565 _ => Err("invalid hex digit"),
16566 }
16567}
16568
16569fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16583 let mut has_text = false;
16584 let mut has_bigint = false;
16585 let mut has_int = false;
16586 for v in &items {
16587 match v {
16588 Value::Null => {}
16589 Value::Text(_) | Value::Json(_) => has_text = true,
16590 Value::BigInt(_) => has_bigint = true,
16591 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16592 _ => has_text = true,
16593 }
16594 }
16595 if has_text || (!has_bigint && !has_int) {
16596 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16597 .into_iter()
16598 .map(|v| match v {
16599 Value::Null => None,
16600 Value::Text(s) | Value::Json(s) => Some(s),
16601 other => Some(alloc::format!("{other:?}")),
16602 })
16603 .collect();
16604 return Value::TextArray(out);
16605 }
16606 if has_bigint {
16607 let out: alloc::vec::Vec<Option<i64>> = items
16608 .into_iter()
16609 .map(|v| match v {
16610 Value::Null => None,
16611 Value::Int(n) => Some(i64::from(n)),
16612 Value::SmallInt(n) => Some(i64::from(n)),
16613 Value::BigInt(n) => Some(n),
16614 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16615 })
16616 .collect();
16617 return Value::BigIntArray(out);
16618 }
16619 let out: alloc::vec::Vec<Option<i32>> = items
16620 .into_iter()
16621 .map(|v| match v {
16622 Value::Null => None,
16623 Value::Int(n) => Some(n),
16624 Value::SmallInt(n) => Some(i32::from(n)),
16625 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16626 })
16627 .collect();
16628 Value::IntArray(out)
16629}
16630
16631fn decode_text_array_literal(
16632 s: &str,
16633) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16634 let trimmed = s.trim();
16635 let inner = trimmed
16636 .strip_prefix('{')
16637 .and_then(|x| x.strip_suffix('}'))
16638 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16639 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16640 if inner.trim().is_empty() {
16641 return Ok(out);
16642 }
16643 let bytes = inner.as_bytes();
16644 let mut i = 0;
16645 while i <= bytes.len() {
16646 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16648 i += 1;
16649 }
16650 if i < bytes.len() && bytes[i] == b'"' {
16652 i += 1; let mut buf = alloc::string::String::new();
16654 while i < bytes.len() && bytes[i] != b'"' {
16655 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16656 buf.push(bytes[i + 1] as char);
16657 i += 2;
16658 } else {
16659 buf.push(bytes[i] as char);
16660 i += 1;
16661 }
16662 }
16663 if i >= bytes.len() {
16664 return Err("unterminated quoted element");
16665 }
16666 i += 1; out.push(Some(buf));
16668 } else {
16669 let start = i;
16671 while i < bytes.len() && bytes[i] != b',' {
16672 i += 1;
16673 }
16674 let raw = inner[start..i].trim();
16675 if raw.eq_ignore_ascii_case("NULL") {
16676 out.push(None);
16677 } else {
16678 out.push(Some(alloc::string::ToString::to_string(raw)));
16679 }
16680 }
16681 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16683 i += 1;
16684 }
16685 if i >= bytes.len() {
16686 break;
16687 }
16688 if bytes[i] != b',' {
16689 return Err("expected ',' between TEXT[] elements");
16690 }
16691 i += 1;
16692 }
16693 Ok(out)
16694}
16695
16696fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16701 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16702 out.push('{');
16703 for (i, item) in items.iter().enumerate() {
16704 if i > 0 {
16705 out.push(',');
16706 }
16707 match item {
16708 None => out.push_str("NULL"),
16709 Some(s) => {
16710 let needs_quote = s.is_empty()
16711 || s.eq_ignore_ascii_case("NULL")
16712 || s.chars()
16713 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16714 if needs_quote {
16715 out.push('"');
16716 for c in s.chars() {
16717 if c == '"' || c == '\\' {
16718 out.push('\\');
16719 }
16720 out.push(c);
16721 }
16722 out.push('"');
16723 } else {
16724 out.push_str(s);
16725 }
16726 }
16727 }
16728 }
16729 out.push('}');
16730 out
16731}
16732
16733fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16737 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16738 out.push_str("\\x");
16739 for byte in b {
16740 let hi = byte >> 4;
16741 let lo = byte & 0x0F;
16742 out.push(hex_digit(hi));
16743 out.push(hex_digit(lo));
16744 }
16745 out
16746}
16747
16748const fn hex_digit(n: u8) -> char {
16749 match n {
16750 0..=9 => (b'0' + n) as char,
16751 10..=15 => (b'a' + n - 10) as char,
16752 _ => '?',
16753 }
16754}
16755
16756fn parse_hstore_str(
16768 s: &str,
16769) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16770 let bytes = s.as_bytes();
16771 let mut i = 0;
16772 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16773 let skip_ws = |bytes: &[u8], i: &mut usize| {
16774 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16775 *i += 1;
16776 }
16777 };
16778 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16779 if *i >= bytes.len() {
16780 return None;
16781 }
16782 if bytes[*i] == b'"' {
16783 *i += 1;
16784 let mut out = alloc::string::String::new();
16785 while *i < bytes.len() {
16786 match bytes[*i] {
16787 b'"' => {
16788 *i += 1;
16789 return Some(out);
16790 }
16791 b'\\' if *i + 1 < bytes.len() => {
16792 out.push(bytes[*i + 1] as char);
16793 *i += 2;
16794 }
16795 c => {
16796 out.push(c as char);
16797 *i += 1;
16798 }
16799 }
16800 }
16801 None
16802 } else {
16803 let start = *i;
16804 while *i < bytes.len()
16805 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16806 {
16807 *i += 1;
16808 }
16809 if *i == start {
16810 return None;
16811 }
16812 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16813 }
16814 };
16815 skip_ws(bytes, &mut i);
16816 while i < bytes.len() {
16817 let key = parse_token(bytes, &mut i)?;
16818 skip_ws(bytes, &mut i);
16819 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16820 return None;
16821 }
16822 i += 2;
16823 skip_ws(bytes, &mut i);
16824 let val_token = if i + 4 <= bytes.len()
16826 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16827 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16828 {
16829 i += 4;
16830 None
16831 } else {
16832 Some(parse_token(bytes, &mut i)?)
16833 };
16834 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16836 out[pos] = (key, val_token);
16837 } else {
16838 out.push((key, val_token));
16839 }
16840 skip_ws(bytes, &mut i);
16841 if i >= bytes.len() {
16842 break;
16843 }
16844 if bytes[i] == b',' {
16845 i += 1;
16846 skip_ws(bytes, &mut i);
16847 continue;
16848 }
16849 return None;
16850 }
16851 Some(out)
16852}
16853
16854fn format_hstore_str(
16858 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16859) -> alloc::string::String {
16860 let mut out = alloc::string::String::new();
16861 for (i, (k, v)) in pairs.iter().enumerate() {
16862 if i > 0 {
16863 out.push_str(", ");
16864 }
16865 out.push('"');
16866 out.push_str(k);
16867 out.push_str("\"=>");
16868 match v {
16869 None => out.push_str("NULL"),
16870 Some(val) => {
16871 out.push('"');
16872 out.push_str(val);
16873 out.push('"');
16874 }
16875 }
16876 }
16877 out
16878}
16879
16880pub fn format_hstore_text(
16883 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16884) -> alloc::string::String {
16885 format_hstore_str(pairs)
16886}
16887
16888fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16893 let s = s.trim();
16894 let outer = s
16895 .strip_prefix('{')
16896 .and_then(|x| x.strip_suffix('}'))
16897 .ok_or("missing outer '{...}' braces")?;
16898 let trimmed = outer.trim();
16899 if trimmed.is_empty() {
16900 return Ok(Vec::new());
16901 }
16902 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16903 let mut i = 0;
16904 let bytes = trimmed.as_bytes();
16905 while i < bytes.len() {
16906 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16907 i += 1;
16908 }
16909 if i >= bytes.len() {
16910 break;
16911 }
16912 if bytes[i] != b'{' {
16913 return Err("expected '{' opening a row");
16914 }
16915 i += 1;
16916 let row_start = i;
16917 let mut depth = 1;
16918 while i < bytes.len() && depth > 0 {
16919 match bytes[i] {
16920 b'{' => depth += 1,
16921 b'}' => depth -= 1,
16922 _ => {}
16923 }
16924 if depth > 0 {
16925 i += 1;
16926 }
16927 }
16928 if depth != 0 {
16929 return Err("unbalanced '{...}' in row");
16930 }
16931 let row_text = &trimmed[row_start..i];
16932 i += 1;
16933 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16934 Vec::new()
16935 } else {
16936 row_text.split(',').map(|t| t.trim().to_string()).collect()
16937 };
16938 rows.push(cells);
16939 }
16940 if let Some(first) = rows.first() {
16941 let cols = first.len();
16942 for r in &rows {
16943 if r.len() != cols {
16944 return Err("ragged 2D array (rows have different column counts)");
16945 }
16946 }
16947 }
16948 Ok(rows)
16949}
16950
16951fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16952 let raw = split_2d_literal(s)?;
16953 raw.into_iter()
16954 .map(|row| {
16955 row.into_iter()
16956 .map(|cell| {
16957 if cell.eq_ignore_ascii_case("NULL") {
16958 Ok(None)
16959 } else {
16960 cell.parse::<i32>()
16961 .map(Some)
16962 .map_err(|_| "invalid int element")
16963 }
16964 })
16965 .collect()
16966 })
16967 .collect()
16968}
16969
16970fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16971 let raw = split_2d_literal(s)?;
16972 raw.into_iter()
16973 .map(|row| {
16974 row.into_iter()
16975 .map(|cell| {
16976 if cell.eq_ignore_ascii_case("NULL") {
16977 Ok(None)
16978 } else {
16979 cell.parse::<i64>()
16980 .map(Some)
16981 .map_err(|_| "invalid bigint element")
16982 }
16983 })
16984 .collect()
16985 })
16986 .collect()
16987}
16988
16989fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16990 let raw = split_2d_literal(s)?;
16991 Ok(raw
16992 .into_iter()
16993 .map(|row| {
16994 row.into_iter()
16995 .map(|cell| {
16996 if cell.eq_ignore_ascii_case("NULL") {
16997 None
16998 } else {
16999 Some(cell.trim_matches('"').to_string())
17000 }
17001 })
17002 .collect()
17003 })
17004 .collect())
17005}
17006
17007fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
17008 let mut out = alloc::string::String::from("{");
17009 for (i, row) in rows.iter().enumerate() {
17010 if i > 0 {
17011 out.push(',');
17012 }
17013 out.push('{');
17014 for (j, cell) in row.iter().enumerate() {
17015 if j > 0 {
17016 out.push(',');
17017 }
17018 match cell {
17019 None => out.push_str("NULL"),
17020 Some(n) => out.push_str(&alloc::format!("{n}")),
17021 }
17022 }
17023 out.push('}');
17024 }
17025 out.push('}');
17026 out
17027}
17028
17029fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
17030 let mut out = alloc::string::String::from("{");
17031 for (i, row) in rows.iter().enumerate() {
17032 if i > 0 {
17033 out.push(',');
17034 }
17035 out.push('{');
17036 for (j, cell) in row.iter().enumerate() {
17037 if j > 0 {
17038 out.push(',');
17039 }
17040 match cell {
17041 None => out.push_str("NULL"),
17042 Some(n) => out.push_str(&alloc::format!("{n}")),
17043 }
17044 }
17045 out.push('}');
17046 }
17047 out.push('}');
17048 out
17049}
17050
17051fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
17052 let mut out = alloc::string::String::from("{");
17053 for (i, row) in rows.iter().enumerate() {
17054 if i > 0 {
17055 out.push(',');
17056 }
17057 out.push('{');
17058 for (j, cell) in row.iter().enumerate() {
17059 if j > 0 {
17060 out.push(',');
17061 }
17062 match cell {
17063 None => out.push_str("NULL"),
17064 Some(s) => out.push_str(s),
17065 }
17066 }
17067 out.push('}');
17068 }
17069 out.push('}');
17070 out
17071}
17072
17073pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
17076 format_int_2d_text(rows)
17077}
17078pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
17079 format_bigint_2d_text(rows)
17080}
17081pub fn format_text_2d_text_pub(
17082 rows: &[Vec<Option<alloc::string::String>>],
17083) -> alloc::string::String {
17084 format_text_2d_text(rows)
17085}
17086
17087fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17092 let s = s.trim();
17093 if s.eq_ignore_ascii_case("empty") {
17094 return Some(Value::Range {
17095 kind,
17096 lower: None,
17097 upper: None,
17098 lower_inc: false,
17099 upper_inc: false,
17100 empty: true,
17101 });
17102 }
17103 let bytes = s.as_bytes();
17104 if bytes.len() < 3 {
17105 return None;
17106 }
17107 let lower_inc = match bytes[0] {
17108 b'[' => true,
17109 b'(' => false,
17110 _ => return None,
17111 };
17112 let upper_inc = match bytes[bytes.len() - 1] {
17113 b']' => true,
17114 b')' => false,
17115 _ => return None,
17116 };
17117 let inner = &s[1..s.len() - 1];
17118 let (lo_text, up_text) = inner.split_once(',')?;
17119 let lower = if lo_text.is_empty() {
17120 None
17121 } else {
17122 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
17123 };
17124 let upper = if up_text.is_empty() {
17125 None
17126 } else {
17127 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
17128 };
17129 Some(Value::Range {
17130 kind,
17131 lower,
17132 upper,
17133 lower_inc,
17134 upper_inc,
17135 empty: false,
17136 })
17137}
17138
17139fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17142 let text = text.trim().trim_matches('"');
17143 use spg_storage::RangeKind as K;
17144 match kind {
17145 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
17146 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
17147 K::Num => {
17148 let dot = text.find('.');
17151 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
17152 let digits: alloc::string::String = text
17153 .chars()
17154 .filter(|c| *c == '-' || c.is_ascii_digit())
17155 .collect();
17156 let scaled: i128 = digits.parse().ok()?;
17157 Some(Value::Numeric { scaled, scale })
17158 }
17159 K::Ts | K::TsTz => {
17160 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
17165 }
17166 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
17167 }
17168}
17169
17170pub fn format_range_text(v: &Value) -> alloc::string::String {
17174 format_range_str(v)
17175}
17176
17177fn format_range_str(v: &Value) -> alloc::string::String {
17178 let Value::Range {
17179 lower,
17180 upper,
17181 lower_inc,
17182 upper_inc,
17183 empty,
17184 ..
17185 } = v
17186 else {
17187 return alloc::string::String::new();
17188 };
17189 if *empty {
17190 return "empty".into();
17191 }
17192 let mut out = alloc::string::String::new();
17193 out.push(if *lower_inc { '[' } else { '(' });
17194 if let Some(l) = lower {
17195 out.push_str(&format_range_element(l));
17196 }
17197 out.push(',');
17198 if let Some(u) = upper {
17199 out.push_str(&format_range_element(u));
17200 }
17201 out.push(if *upper_inc { ']' } else { ')' });
17202 out
17203}
17204
17205fn format_range_element(v: &Value) -> alloc::string::String {
17206 match v {
17207 Value::Int(n) => alloc::format!("{n}"),
17208 Value::BigInt(n) => alloc::format!("{n}"),
17209 Value::Date(d) => crate::eval::format_date(*d),
17210 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
17211 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
17212 other => alloc::format!("{other:?}"),
17213 }
17214}
17215
17216fn parse_money_str(s: &str) -> Option<i64> {
17227 let s = s.trim();
17228 let (neg, rest) = match s.strip_prefix('-') {
17229 Some(r) => (true, r.trim_start()),
17230 None => (false, s),
17231 };
17232 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
17233 let (int_part, frac_part) = match rest.split_once('.') {
17234 Some((i, f)) => (i, Some(f)),
17235 None => (rest, None),
17236 };
17237 if int_part.is_empty() {
17238 return None;
17239 }
17240 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
17242 for b in int_part.bytes() {
17243 match b {
17244 b',' => {}
17245 b'0'..=b'9' => int_digits.push(b as char),
17246 _ => return None,
17247 }
17248 }
17249 if int_digits.is_empty() {
17250 return None;
17251 }
17252 let dollars: i64 = int_digits.parse().ok()?;
17253 let cents: i64 = match frac_part {
17254 None => 0,
17255 Some(f) => {
17256 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
17257 return None;
17258 }
17259 let padded = if f.len() == 1 {
17260 alloc::format!("{f}0")
17261 } else {
17262 f.to_string()
17263 };
17264 padded.parse().ok()?
17265 }
17266 };
17267 let total = dollars.checked_mul(100)?.checked_add(cents)?;
17268 Some(if neg { -total } else { total })
17269}
17270
17271fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
17282 let s = s.trim();
17283 let bytes = s.as_bytes();
17287 let sign_pos = bytes
17288 .iter()
17289 .enumerate()
17290 .rev()
17291 .find(|&(_, &b)| b == b'+' || b == b'-')
17292 .map(|(i, _)| i)?;
17293 if sign_pos == 0 {
17294 return None; }
17296 let time_part = &s[..sign_pos];
17297 let offset_part = &s[sign_pos..];
17298 let us = parse_time_str(time_part)?;
17299 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
17300 let offset_body = &offset_part[1..];
17301 let (hh_str, mm_str) = match offset_body.split_once(':') {
17302 Some((h, m)) => (h, m),
17303 None => (offset_body, "0"),
17304 };
17305 let hh: i32 = hh_str.parse().ok()?;
17306 let mm: i32 = mm_str.parse().ok()?;
17307 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
17308 return None;
17309 }
17310 let total = sign * (hh * 3600 + mm * 60);
17311 if total.abs() > 50_400 {
17312 return None;
17313 }
17314 Some((us, total))
17315}
17316
17317fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
17322 if n == 0 || (1901..=2155).contains(&n) {
17323 return Ok(Value::Year(n as u16));
17326 }
17327 Err(EngineError::Eval(EvalError::TypeMismatch {
17328 detail: alloc::format!(
17329 "year value out of range: {n} (column `{col_name}`; \
17330 MySQL accepts 0 or 1901..=2155)"
17331 ),
17332 }))
17333}
17334
17335fn parse_time_str(s: &str) -> Option<i64> {
17347 let s = s.trim();
17348 let (hms, frac) = match s.split_once('.') {
17349 Some((h, f)) => (h, Some(f)),
17350 None => (s, None),
17351 };
17352 let mut parts = hms.split(':');
17353 let hh: u32 = parts.next()?.parse().ok()?;
17354 let mm: u32 = parts.next()?.parse().ok()?;
17355 let ss: u32 = parts.next()?.parse().ok()?;
17356 if parts.next().is_some() {
17357 return None;
17358 }
17359 if hh > 23 || mm > 59 || ss > 59 {
17360 return None;
17361 }
17362 let frac_us: i64 = match frac {
17363 None => 0,
17364 Some(f) => {
17365 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
17366 return None;
17367 }
17368 let mut padded = alloc::string::String::with_capacity(6);
17370 padded.push_str(f);
17371 while padded.len() < 6 {
17372 padded.push('0');
17373 }
17374 padded.parse().ok()?
17375 }
17376 };
17377 Some(
17378 i64::from(hh) * 3_600_000_000
17379 + i64::from(mm) * 60_000_000
17380 + i64::from(ss) * 1_000_000
17381 + frac_us,
17382 )
17383}
17384
17385const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
17386 match t {
17387 ColumnTypeName::SmallInt => DataType::SmallInt,
17388 ColumnTypeName::Int => DataType::Int,
17389 ColumnTypeName::BigInt => DataType::BigInt,
17390 ColumnTypeName::Float => DataType::Float,
17391 ColumnTypeName::Text => DataType::Text,
17392 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17393 ColumnTypeName::Char(n) => DataType::Char(n),
17394 ColumnTypeName::Bool => DataType::Bool,
17395 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17396 dim,
17397 encoding: match encoding {
17398 SqlVecEncoding::F32 => VecEncoding::F32,
17399 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17400 SqlVecEncoding::F16 => VecEncoding::F16,
17401 },
17402 },
17403 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17404 ColumnTypeName::Date => DataType::Date,
17405 ColumnTypeName::Timestamp => DataType::Timestamp,
17406 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17407 ColumnTypeName::Json => DataType::Json,
17408 ColumnTypeName::Jsonb => DataType::Jsonb,
17409 ColumnTypeName::Bytes => DataType::Bytes,
17410 ColumnTypeName::TextArray => DataType::TextArray,
17411 ColumnTypeName::IntArray => DataType::IntArray,
17412 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17413 ColumnTypeName::TsVector => DataType::TsVector,
17414 ColumnTypeName::TsQuery => DataType::TsQuery,
17415 ColumnTypeName::Uuid => DataType::Uuid,
17416 ColumnTypeName::Time => DataType::Time,
17417 ColumnTypeName::Year => DataType::Year,
17418 ColumnTypeName::TimeTz => DataType::TimeTz,
17419 ColumnTypeName::Money => DataType::Money,
17420 ColumnTypeName::Range(k) => DataType::Range(match k {
17421 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17422 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17423 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17424 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17425 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17426 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17427 }),
17428 ColumnTypeName::Hstore => DataType::Hstore,
17429 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17430 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17431 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17432 }
17433}
17434
17435fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17439 match expr {
17440 Expr::Literal(l) => Ok(literal_to_value(l)),
17441 Expr::Cast { expr, target } => {
17442 let inner_value = literal_expr_to_value(*expr)?;
17443 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17444 }
17445 Expr::Unary {
17446 op: UnOp::Neg,
17447 expr,
17448 } => match *expr {
17449 Expr::Literal(Literal::Integer(n)) => {
17450 let neg = n.checked_neg().ok_or_else(|| {
17453 EngineError::Unsupported("integer literal overflow on negation".into())
17454 })?;
17455 Ok(int_value_for(neg))
17456 }
17457 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17458 other => Err(EngineError::Unsupported(alloc::format!(
17459 "unary minus over non-literal expression: {other:?}"
17460 ))),
17461 },
17462 Expr::Array(items) => {
17470 let mut materialised: alloc::vec::Vec<Value> =
17471 alloc::vec::Vec::with_capacity(items.len());
17472 for elem in items {
17473 materialised.push(literal_expr_to_value(elem)?);
17474 }
17475 Ok(array_literal_widen(materialised))
17476 }
17477 other => {
17490 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17491 let ctx = EvalContext::new(&empty_schema, None);
17492 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17493 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17494 }
17495 }
17496}
17497
17498fn literal_to_value(l: Literal) -> Value {
17499 match l {
17500 Literal::Integer(n) => int_value_for(n),
17501 Literal::Float(x) => Value::Float(x),
17502 Literal::String(s) => Value::Text(s),
17503 Literal::Bool(b) => Value::Bool(b),
17504 Literal::Null => Value::Null,
17505 Literal::Vector(v) => Value::Vector(v),
17506 Literal::TextArray(items) => Value::TextArray(items),
17507 Literal::IntArray(items) => Value::IntArray(items),
17508 Literal::BigIntArray(items) => Value::BigIntArray(items),
17509 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17510 }
17511}
17512
17513fn int_value_for(n: i64) -> Value {
17517 if let Ok(small) = i32::try_from(n) {
17518 Value::Int(small)
17519 } else {
17520 Value::BigInt(n)
17521 }
17522}
17523
17524#[allow(clippy::too_many_lines)]
17530fn check_unsigned_range(
17535 v: &Value,
17536 schema: &ColumnSchema,
17537 position: usize,
17538) -> Result<(), EngineError> {
17539 if !schema.is_unsigned {
17540 return Ok(());
17541 }
17542 let n = match v {
17543 Value::SmallInt(x) => i64::from(*x),
17544 Value::Int(x) => i64::from(*x),
17545 Value::BigInt(x) => *x,
17546 _ => return Ok(()), };
17548 if n < 0 {
17549 return Err(EngineError::Unsupported(alloc::format!(
17550 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17551 schema.name
17552 )));
17553 }
17554 Ok(())
17555}
17556
17557fn coerce_value(
17558 v: Value,
17559 expected: DataType,
17560 col_name: &str,
17561 position: usize,
17562) -> Result<Value, EngineError> {
17563 if v.is_null() {
17564 return Ok(Value::Null);
17565 }
17566 let actual = v.data_type().expect("non-null");
17567 if actual == expected {
17568 return Ok(v);
17569 }
17570 let coerced = match (v, expected) {
17571 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17572 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17573 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17574 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17575 i128::from(n),
17576 precision,
17577 scale,
17578 col_name,
17579 )?),
17580 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17581 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17582 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17583 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17584 i128::from(n),
17585 precision,
17586 scale,
17587 col_name,
17588 )?),
17589 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17590 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17591 #[allow(clippy::cast_precision_loss)]
17592 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17593 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17594 i128::from(n),
17595 precision,
17596 scale,
17597 col_name,
17598 )?),
17599 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17600 Some(numeric_from_float(x, precision, scale, col_name)?)
17601 }
17602 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17613 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17614 return Err(EngineError::Eval(EvalError::TypeMismatch {
17615 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17616 }));
17617 };
17618 Some(numeric_rescale(
17619 mantissa, src_scale, precision, scale, col_name,
17620 )?)
17621 }
17622 (Value::Text(s), DataType::Date) => {
17624 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17625 EngineError::Eval(EvalError::TypeMismatch {
17626 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17627 })
17628 })?;
17629 Some(Value::Date(d))
17630 }
17631 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17638 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17639 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17640 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17641 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17642 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17643 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17644 _ => None,
17645 },
17646 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17655 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17656 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17657 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17661 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17662 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17670 (Value::Text(s), DataType::Bytes) => {
17677 let bytes = decode_bytea_literal(&s).map_err(|e| {
17678 EngineError::Eval(EvalError::TypeMismatch {
17679 detail: alloc::format!(
17680 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17681 ),
17682 })
17683 })?;
17684 Some(Value::Bytes(bytes))
17685 }
17686 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17690 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17698 Some(b) => Some(Value::Uuid(b)),
17699 None => {
17700 return Err(EngineError::Eval(EvalError::TypeMismatch {
17701 detail: alloc::format!(
17702 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17703 ),
17704 }));
17705 }
17706 },
17707 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17712 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17718 Some(us) => Some(Value::Time(us)),
17719 None => {
17720 return Err(EngineError::Eval(EvalError::TypeMismatch {
17721 detail: alloc::format!(
17722 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17723 ),
17724 }));
17725 }
17726 },
17727 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17729 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17734 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17735 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17736 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17740 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17741 Err(_) => {
17742 return Err(EngineError::Eval(EvalError::TypeMismatch {
17743 detail: alloc::format!(
17744 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17745 ),
17746 }));
17747 }
17748 },
17749 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17751 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17755 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17756 None => {
17757 return Err(EngineError::Eval(EvalError::TypeMismatch {
17758 detail: alloc::format!(
17759 "invalid input syntax for type time with time zone: \
17760 {s:?} (column `{col_name}`)"
17761 ),
17762 }));
17763 }
17764 },
17765 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17767 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17768 }
17769 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17773 Some(c) => Some(Value::Money(c)),
17774 None => {
17775 return Err(EngineError::Eval(EvalError::TypeMismatch {
17776 detail: alloc::format!(
17777 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17778 ),
17779 }));
17780 }
17781 },
17782 (Value::SmallInt(n), DataType::Money) => {
17786 Some(Value::Money(i64::from(n).saturating_mul(100)))
17787 }
17788 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17789 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17790 (Value::Float(x), DataType::Money) => {
17791 let scaled = x * 100.0;
17794 let cents = if scaled >= 0.0 {
17795 (scaled + 0.5) as i64
17796 } else {
17797 (scaled - 0.5) as i64
17798 };
17799 Some(Value::Money(cents))
17800 }
17801 (Value::Numeric { scaled, scale }, DataType::Money) => {
17802 let cents = if scale == 2 {
17805 scaled
17806 } else if scale < 2 {
17807 let mult = 10_i128.pow(u32::from(2 - scale));
17808 scaled.saturating_mul(mult)
17809 } else {
17810 let div = 10_i128.pow(u32::from(scale - 2));
17811 let half = div / 2;
17812 let bias = if scaled >= 0 { half } else { -half };
17813 (scaled + bias) / div
17814 };
17815 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17816 }
17817 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17819 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17823 Some(v) => Some(v),
17824 None => {
17825 return Err(EngineError::Eval(EvalError::TypeMismatch {
17826 detail: alloc::format!(
17827 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17828 ),
17829 }));
17830 }
17831 },
17832 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17834 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17836 Some(pairs) => Some(Value::Hstore(pairs)),
17837 None => {
17838 return Err(EngineError::Eval(EvalError::TypeMismatch {
17839 detail: alloc::format!(
17840 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17841 ),
17842 }));
17843 }
17844 },
17845 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17847 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17850 Ok(m) => Some(Value::IntArray2D(m)),
17851 Err(e) => {
17852 return Err(EngineError::Eval(EvalError::TypeMismatch {
17853 detail: alloc::format!(
17854 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17855 ),
17856 }));
17857 }
17858 },
17859 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17860 Ok(m) => Some(Value::BigIntArray2D(m)),
17861 Err(e) => {
17862 return Err(EngineError::Eval(EvalError::TypeMismatch {
17863 detail: alloc::format!(
17864 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17865 ),
17866 }));
17867 }
17868 },
17869 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17870 Ok(m) => Some(Value::TextArray2D(m)),
17871 Err(e) => {
17872 return Err(EngineError::Eval(EvalError::TypeMismatch {
17873 detail: alloc::format!(
17874 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17875 ),
17876 }));
17877 }
17878 },
17879 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17881 (Value::BigIntArray2D(rows), DataType::Text) => {
17882 Some(Value::Text(format_bigint_2d_text(&rows)))
17883 }
17884 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17885 (Value::Text(s), DataType::TextArray) => {
17890 let arr = decode_text_array_literal(&s).map_err(|e| {
17891 EngineError::Eval(EvalError::TypeMismatch {
17892 detail: alloc::format!(
17893 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17894 ),
17895 })
17896 })?;
17897 Some(Value::TextArray(arr))
17898 }
17899 (Value::Text(s), DataType::IntArray) => {
17905 let arr = decode_text_array_literal(&s).map_err(|e| {
17906 EngineError::Eval(EvalError::TypeMismatch {
17907 detail: alloc::format!(
17908 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17909 ),
17910 })
17911 })?;
17912 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17913 for elem in arr {
17914 match elem {
17915 None => out.push(None),
17916 Some(t) => {
17917 let n: i32 = t.parse().map_err(|_| {
17918 EngineError::Eval(EvalError::TypeMismatch {
17919 detail: alloc::format!(
17920 "cannot parse {t:?} as INT element for `{col_name}`"
17921 ),
17922 })
17923 })?;
17924 out.push(Some(n));
17925 }
17926 }
17927 }
17928 Some(Value::IntArray(out))
17929 }
17930 (Value::Text(s), DataType::BigIntArray) => {
17931 let arr = decode_text_array_literal(&s).map_err(|e| {
17932 EngineError::Eval(EvalError::TypeMismatch {
17933 detail: alloc::format!(
17934 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17935 ),
17936 })
17937 })?;
17938 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17939 for elem in arr {
17940 match elem {
17941 None => out.push(None),
17942 Some(t) => {
17943 let n: i64 = t.parse().map_err(|_| {
17944 EngineError::Eval(EvalError::TypeMismatch {
17945 detail: alloc::format!(
17946 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17947 ),
17948 })
17949 })?;
17950 out.push(Some(n));
17951 }
17952 }
17953 }
17954 Some(Value::BigIntArray(out))
17955 }
17956 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17960 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17969 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17970 EngineError::Eval(EvalError::TypeMismatch {
17971 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17972 })
17973 })?;
17974 if parsed.len() != dim as usize {
17975 return Err(EngineError::Eval(EvalError::TypeMismatch {
17976 detail: alloc::format!(
17977 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17978 parsed.len()
17979 ),
17980 }));
17981 }
17982 Some(match encoding {
17983 VecEncoding::F32 => Value::Vector(parsed),
17984 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17985 VecEncoding::F16 => {
17986 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17987 }
17988 })
17989 }
17990 (Value::Text(s), DataType::TsVector) => {
18000 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
18001 EngineError::Eval(EvalError::TypeMismatch {
18002 detail: alloc::format!(
18003 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
18004 ),
18005 })
18006 })?;
18007 Some(Value::TsVector(lexs))
18008 }
18009 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
18010 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
18011 EngineError::Eval(EvalError::TypeMismatch {
18012 detail: alloc::format!(
18013 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
18014 ),
18015 })
18016 })?;
18017 Some(Value::Timestamp(t))
18018 }
18019 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
18022 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
18023 }
18024 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
18028 (Value::Timestamp(t), DataType::Date) => {
18029 let days = t.div_euclid(86_400_000_000);
18030 i32::try_from(days).ok().map(Value::Date)
18031 }
18032 (
18033 Value::Numeric {
18034 scaled,
18035 scale: src_scale,
18036 },
18037 DataType::Numeric { precision, scale },
18038 ) => Some(numeric_rescale(
18039 scaled, src_scale, precision, scale, col_name,
18040 )?),
18041 #[allow(clippy::cast_precision_loss)]
18042 (Value::Numeric { scaled, scale }, DataType::Float) => {
18043 let mut div = 1.0_f64;
18044 for _ in 0..scale {
18045 div *= 10.0;
18046 }
18047 Some(Value::Float((scaled as f64) / div))
18048 }
18049 (Value::Numeric { scaled, scale }, DataType::Int) => {
18050 let truncated = numeric_truncate_to_integer(scaled, scale);
18051 i32::try_from(truncated).ok().map(Value::Int)
18052 }
18053 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
18054 let truncated = numeric_truncate_to_integer(scaled, scale);
18055 i64::try_from(truncated).ok().map(Value::BigInt)
18056 }
18057 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
18058 let truncated = numeric_truncate_to_integer(scaled, scale);
18059 i16::try_from(truncated).ok().map(Value::SmallInt)
18060 }
18061 (Value::Text(s), DataType::Varchar(max)) => {
18063 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
18064 Some(Value::Text(s))
18065 } else {
18066 return Err(EngineError::Unsupported(alloc::format!(
18067 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
18068 {} chars",
18069 s.chars().count()
18070 )));
18071 }
18072 }
18073 (
18081 Value::Vector(v),
18082 DataType::Vector {
18083 dim,
18084 encoding: VecEncoding::Sq8,
18085 },
18086 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
18087 (
18092 Value::Vector(v),
18093 DataType::Vector {
18094 dim,
18095 encoding: VecEncoding::F16,
18096 },
18097 ) if v.len() == dim as usize => Some(Value::HalfVector(
18098 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
18099 )),
18100 (Value::Text(s), DataType::Char(size)) => {
18104 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
18105 if len > size {
18106 return Err(EngineError::Unsupported(alloc::format!(
18107 "value for CHAR({size}) column `{col_name}` exceeds length: \
18108 {len} chars"
18109 )));
18110 }
18111 let need = (size - len) as usize;
18112 let mut padded = s;
18113 padded.reserve(need);
18114 for _ in 0..need {
18115 padded.push(' ');
18116 }
18117 Some(Value::Text(padded))
18118 }
18119 _ => None,
18120 };
18121 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
18122 column: col_name.into(),
18123 expected,
18124 actual,
18125 position,
18126 }))
18127}
18128
18129fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
18135 use core::fmt::Write;
18136 let mut out = alloc::string::String::from("(");
18137 for (i, a) in args.iter().enumerate() {
18138 if i > 0 {
18139 out.push_str(", ");
18140 }
18141 match a.mode {
18142 spg_sql::ast::FunctionArgMode::In => {}
18143 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
18144 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
18145 }
18146 if let Some(n) = &a.name {
18147 out.push_str(n);
18148 out.push(' ');
18149 }
18150 match &a.ty {
18151 spg_sql::ast::FunctionArgType::Typed(t) => {
18152 let _ = write!(out, "{t}");
18153 }
18154 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
18155 }
18156 }
18157 out.push(')');
18158 out
18159}
18160
18161fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
18170 match expr {
18171 spg_sql::ast::Expr::FunctionCall { name, args } => {
18172 name.eq_ignore_ascii_case("unnest") && args.len() == 1
18173 }
18174 _ => false,
18175 }
18176}
18177
18178fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
18182 match expr {
18183 spg_sql::ast::Expr::FunctionCall { name, args }
18184 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
18185 {
18186 Some(&args[0])
18187 }
18188 _ => None,
18189 }
18190}
18191
18192fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
18197 match v {
18198 Value::Null => Ok(Vec::new()),
18199 Value::TextArray(items) => Ok(items
18200 .iter()
18201 .map(|opt| {
18202 opt.as_ref()
18203 .map(|s| Value::Text(s.clone()))
18204 .unwrap_or(Value::Null)
18205 })
18206 .collect()),
18207 Value::IntArray(items) => Ok(items
18208 .iter()
18209 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
18210 .collect()),
18211 Value::BigIntArray(items) => Ok(items
18212 .iter()
18213 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
18214 .collect()),
18215 other => Err(EngineError::Eval(EvalError::TypeMismatch {
18216 detail: alloc::format!(
18217 "unnest() expects an array argument, got {:?}",
18218 other.data_type()
18219 ),
18220 })),
18221 }
18222}
18223
18224#[cfg(test)]
18225mod tests {
18226 use super::*;
18227 use alloc::vec;
18228
18229 fn unwrap_command_ok(r: &QueryResult) -> usize {
18230 match r {
18231 QueryResult::CommandOk { affected, .. } => *affected,
18232 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
18233 }
18234 }
18235
18236 #[test]
18237 fn update_seek_positions_engages_on_indexed_eq() {
18238 let mut e = Engine::new();
18239 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
18240 .unwrap();
18241 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
18242 for i in 0..100 {
18243 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
18244 .unwrap();
18245 }
18246 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
18247 .expect("parse");
18248 let Statement::Update(u) = stmt else {
18249 panic!("expected Update, got {stmt:?}");
18250 };
18251 let w = u.where_.as_ref().expect("where");
18252 let table = e.catalog().get("b").unwrap();
18253 let schema_cols = table.schema().columns.clone();
18254 let Expr::Binary { lhs, op, rhs } = w else {
18256 panic!("WHERE not Binary: {w:?}");
18257 };
18258 assert_eq!(*op, BinOp::Eq, "op not Eq");
18259 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
18260 assert!(
18261 pair.is_some(),
18262 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
18263 );
18264 let (col_pos, value) = pair.unwrap();
18265 assert!(
18266 table.index_on(col_pos).is_some(),
18267 "no index on col {col_pos}"
18268 );
18269 assert!(
18270 IndexKey::from_value(&value).is_some(),
18271 "IndexKey::from_value None for {value:?}"
18272 );
18273 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
18274 assert_eq!(positions, Some(vec![42]), "seek did not engage");
18275 }
18276
18277 #[test]
18278 fn create_table_registers_schema() {
18279 let mut e = Engine::new();
18280 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
18281 .unwrap();
18282 assert_eq!(e.catalog().table_count(), 1);
18283 let t = e.catalog().get("foo").unwrap();
18284 assert_eq!(t.schema().columns.len(), 2);
18285 assert_eq!(t.schema().columns[0].ty, DataType::Int);
18286 assert!(!t.schema().columns[0].nullable);
18287 assert_eq!(t.schema().columns[1].ty, DataType::Text);
18288 }
18289
18290 #[test]
18291 fn create_table_vector_default_is_f32_encoded() {
18292 let mut e = Engine::new();
18293 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
18294 let t = e.catalog().get("t").unwrap();
18295 assert_eq!(
18296 t.schema().columns[0].ty,
18297 DataType::Vector {
18298 dim: 8,
18299 encoding: VecEncoding::F32,
18300 },
18301 );
18302 }
18303
18304 #[test]
18305 fn create_table_vector_using_sq8_succeeds() {
18306 let mut e = Engine::new();
18310 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
18311 let t = e.catalog().get("t").unwrap();
18312 assert_eq!(
18313 t.schema().columns[0].ty,
18314 DataType::Vector {
18315 dim: 8,
18316 encoding: VecEncoding::Sq8,
18317 },
18318 );
18319 }
18320
18321 #[test]
18322 fn insert_into_sq8_column_quantises_f32_payload() {
18323 let mut e = Engine::new();
18330 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18331 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18332 .unwrap();
18333 let t = e.catalog().get("t").unwrap();
18334 assert_eq!(t.rows().len(), 1);
18335 match &t.rows()[0].values[0] {
18336 Value::Sq8Vector(q) => {
18337 assert_eq!(q.bytes.len(), 4);
18338 assert!((q.min - 0.0).abs() < 1e-6);
18340 assert!((q.max - 1.0).abs() < 1e-6);
18341 }
18342 other => panic!("expected Sq8Vector cell, got {other:?}"),
18343 }
18344 }
18345
18346 #[test]
18347 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
18348 let mut e = Engine::new();
18355 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18356 .unwrap();
18357 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18358 .unwrap();
18359 let t = e.catalog().get("t").unwrap();
18360 assert_eq!(t.rows().len(), 1);
18361 match &t.rows()[0].values[0] {
18362 Value::HalfVector(h) => {
18363 assert_eq!(h.dim(), 4);
18364 let back = h.to_f32_vec();
18365 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
18366 for (g, e) in back.iter().zip(expected.iter()) {
18367 assert!(
18368 (g - e).abs() < 1e-6,
18369 "{g} vs {e} should be exact on f16 grid"
18370 );
18371 }
18372 }
18373 other => panic!("expected HalfVector cell, got {other:?}"),
18374 }
18375 }
18376
18377 #[test]
18378 fn alter_index_rebuild_in_place_succeeds() {
18379 let mut e = Engine::new();
18384 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18385 .unwrap();
18386 for i in 0..8_i32 {
18387 #[allow(clippy::cast_precision_loss)]
18388 let base = (i as f32) * 0.1;
18389 e.execute(&alloc::format!(
18390 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
18391 b1 = base + 0.01,
18392 b2 = base + 0.02,
18393 ))
18394 .unwrap();
18395 }
18396 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18397 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18398 assert_eq!(
18400 e.catalog().get("t").unwrap().schema().columns[1].ty,
18401 DataType::Vector {
18402 dim: 3,
18403 encoding: VecEncoding::F32,
18404 },
18405 );
18406 }
18407
18408 #[test]
18409 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18410 let mut e = Engine::new();
18415 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18416 .unwrap();
18417 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18418 .unwrap();
18419 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18420 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18421 .unwrap();
18422 let t = e.catalog().get("t").unwrap();
18423 assert_eq!(
18424 t.schema().columns[1].ty,
18425 DataType::Vector {
18426 dim: 4,
18427 encoding: VecEncoding::Sq8,
18428 },
18429 );
18430 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18431 }
18432
18433 #[test]
18434 fn alter_index_rebuild_unknown_index_errors() {
18435 let mut e = Engine::new();
18436 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18437 assert!(
18438 matches!(
18439 &err,
18440 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18441 ),
18442 "got: {err}"
18443 );
18444 }
18445
18446 #[test]
18447 fn alter_index_rebuild_on_btree_index_errors() {
18448 let mut e = Engine::new();
18451 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18452 e.execute("INSERT INTO t VALUES (1)").unwrap();
18453 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18454 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18455 assert!(
18456 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18457 "got: {err}"
18458 );
18459 }
18460
18461 #[test]
18462 fn prepared_insert_substitutes_placeholders() {
18463 let mut e = Engine::new();
18469 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18470 .unwrap();
18471 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18472 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18473 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18474 .unwrap();
18475 }
18476 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18478 let QueryResult::Rows { rows, .. } = rows_result else {
18479 panic!("expected Rows")
18480 };
18481 assert_eq!(rows.len(), 3);
18482 }
18483
18484 #[test]
18485 fn prepared_select_with_placeholder_filters_rows() {
18486 let mut e = Engine::new();
18487 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18488 .unwrap();
18489 for i in 0..10_i32 {
18490 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18491 .unwrap();
18492 }
18493 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18494 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18495 else {
18496 panic!("expected Rows")
18497 };
18498 assert_eq!(rows.len(), 1);
18500 assert_eq!(rows[0].values[0], Value::Int(5));
18501 }
18502
18503 #[test]
18504 fn prepared_too_few_params_errors() {
18505 let mut e = Engine::new();
18506 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18507 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18508 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18509 assert!(
18510 matches!(
18511 &err,
18512 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18513 ),
18514 "got: {err}"
18515 );
18516 }
18517
18518 #[test]
18519 fn bytea_cast_round_trips_text_input() {
18520 let e = Engine::new();
18523 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18524 let QueryResult::Rows { rows, .. } = r else {
18525 panic!("expected Rows")
18526 };
18527 assert_eq!(rows.len(), 1);
18528 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18529 }
18530
18531 #[test]
18532 fn bytea_cast_pg_escape_hex_form() {
18533 let e = Engine::new();
18537 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18538 let QueryResult::Rows { rows, .. } = r else {
18539 panic!("expected Rows")
18540 };
18541 assert_eq!(
18542 rows[0].values[0],
18543 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18544 );
18545 }
18546
18547 #[test]
18548 fn bytea_cast_chains_through_octet_length() {
18549 let e = Engine::new();
18553 let r = e
18554 .execute_readonly("SELECT octet_length('hello'::bytea)")
18555 .unwrap();
18556 let QueryResult::Rows { rows, .. } = r else {
18557 panic!("expected Rows")
18558 };
18559 match &rows[0].values[0] {
18560 Value::Int(n) => assert_eq!(*n, 5),
18561 Value::BigInt(n) => assert_eq!(*n, 5),
18562 other => panic!("expected integer length, got {other:?}"),
18563 }
18564 }
18565
18566 #[test]
18567 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18568 let mut e = Engine::new();
18574 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18575 .unwrap();
18576 for i in 0..10_i32 {
18577 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18578 .unwrap();
18579 }
18580 let snapshot = e.clone_snapshot();
18581 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18582 let QueryResult::Rows { rows, .. } =
18583 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18584 .unwrap()
18585 else {
18586 panic!("expected Rows")
18587 };
18588 assert_eq!(rows.len(), 1);
18589 assert_eq!(rows[0].values[0], Value::Int(5));
18590 }
18591
18592 #[test]
18593 fn readonly_prepared_on_snapshot_rejects_writes() {
18594 let mut e = Engine::new();
18598 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18599 let snapshot = e.clone_snapshot();
18600 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18601 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18602 .unwrap_err();
18603 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18604 }
18605
18606 #[test]
18607 fn readonly_prepared_on_snapshot_frozen_view() {
18608 let mut e = Engine::new();
18614 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18615 e.execute("INSERT INTO t VALUES (1)").unwrap();
18616 let snapshot = e.clone_snapshot();
18617 e.execute("INSERT INTO t VALUES (2)").unwrap();
18618 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18619 let QueryResult::Rows { rows, .. } =
18620 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18621 .unwrap()
18622 else {
18623 panic!("expected Rows")
18624 };
18625 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18626 }
18627
18628 #[test]
18629 fn describe_prepared_on_snapshot_resolves_columns() {
18630 let mut e = Engine::new();
18635 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18636 .unwrap();
18637 let snapshot = e.clone_snapshot();
18638 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18639 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18640 assert_eq!(cols.len(), 2);
18641 assert_eq!(cols[0].name, "id");
18642 assert_eq!(cols[0].ty, DataType::Int);
18643 assert_eq!(cols[1].name, "name");
18644 assert_eq!(cols[1].ty, DataType::Text);
18645 }
18646
18647 #[test]
18648 fn insert_into_half_column_dim_mismatch_errors() {
18649 let mut e = Engine::new();
18650 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18651 .unwrap();
18652 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18653 assert!(matches!(
18654 &err,
18655 EngineError::Storage(StorageError::TypeMismatch { .. })
18656 ));
18657 }
18658
18659 #[test]
18660 fn insert_into_sq8_column_dim_mismatch_errors() {
18661 let mut e = Engine::new();
18666 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18667 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18668 assert!(
18669 matches!(
18670 &err,
18671 EngineError::Storage(StorageError::TypeMismatch { .. })
18672 ),
18673 "got: {err}",
18674 );
18675 }
18676
18677 #[test]
18678 fn create_table_duplicate_errors() {
18679 let mut e = Engine::new();
18680 e.execute("CREATE TABLE foo (a INT)").unwrap();
18681 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18682 assert!(matches!(
18683 err,
18684 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18685 ));
18686 }
18687
18688 #[test]
18689 fn insert_into_unknown_table_errors() {
18690 let mut e = Engine::new();
18691 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18692 assert!(matches!(
18693 err,
18694 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18695 ));
18696 }
18697
18698 #[test]
18699 fn insert_happy_path_reports_one_affected() {
18700 let mut e = Engine::new();
18701 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18702 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18703 assert_eq!(unwrap_command_ok(&r), 1);
18704 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18705 }
18706
18707 #[test]
18708 fn insert_arity_mismatch_propagates() {
18709 let mut e = Engine::new();
18710 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18711 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18712 assert!(matches!(
18713 err,
18714 EngineError::Storage(StorageError::ArityMismatch { .. })
18715 ));
18716 }
18717
18718 #[test]
18719 fn insert_negative_integer_via_unary_minus() {
18720 let mut e = Engine::new();
18721 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18722 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18723 let rows = e.catalog().get("foo").unwrap().rows();
18724 assert_eq!(rows[0].values[0], Value::Int(-7));
18725 }
18726
18727 #[test]
18728 fn insert_expression_evaluated_against_empty_context() {
18729 let mut e = Engine::new();
18734 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18735 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18736 let rows = e.catalog().get("foo").unwrap().rows();
18737 assert_eq!(rows[0].values[0], Value::Int(3));
18738 }
18739
18740 #[test]
18741 fn select_star_returns_all_rows_in_insertion_order() {
18742 let mut e = Engine::new();
18743 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18744 .unwrap();
18745 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18746 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18747 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18748
18749 let r = e.execute("SELECT * FROM foo").unwrap();
18750 let QueryResult::Rows { columns, rows } = r else {
18751 panic!("expected Rows")
18752 };
18753 assert_eq!(columns.len(), 2);
18754 assert_eq!(columns[0].name, "a");
18755 assert_eq!(rows.len(), 3);
18756 assert_eq!(
18757 rows[1].values,
18758 vec![Value::Int(2), Value::Text("two".into())]
18759 );
18760 }
18761
18762 #[test]
18763 fn select_star_on_empty_table_returns_zero_rows() {
18764 let mut e = Engine::new();
18765 e.execute("CREATE TABLE foo (a INT)").unwrap();
18766 let r = e.execute("SELECT * FROM foo").unwrap();
18767 match r {
18768 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18769 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18770 }
18771 }
18772
18773 fn make_three_row_users(e: &mut Engine) {
18776 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18777 .unwrap();
18778 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18779 .unwrap();
18780 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18781 .unwrap();
18782 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18783 .unwrap();
18784 }
18785
18786 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18787 match r {
18788 QueryResult::Rows { columns, rows } => (columns, rows),
18789 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18790 }
18791 }
18792
18793 #[test]
18794 fn where_filter_passes_only_true_rows() {
18795 let mut e = Engine::new();
18796 make_three_row_users(&mut e);
18797 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18798 let (_, rows) = unwrap_rows(r);
18799 assert_eq!(rows.len(), 2);
18800 assert_eq!(rows[0].values[0], Value::Int(2));
18801 assert_eq!(rows[1].values[0], Value::Int(3));
18802 }
18803
18804 #[test]
18805 fn where_with_null_result_filters_out_row() {
18806 let mut e = Engine::new();
18807 make_three_row_users(&mut e);
18808 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18810 let (_, rows) = unwrap_rows(r);
18811 assert_eq!(rows.len(), 1);
18812 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18813 }
18814
18815 #[test]
18816 fn projection_named_columns() {
18817 let mut e = Engine::new();
18818 make_three_row_users(&mut e);
18819 let r = e.execute("SELECT name, score FROM users").unwrap();
18820 let (cols, rows) = unwrap_rows(r);
18821 assert_eq!(cols.len(), 2);
18822 assert_eq!(cols[0].name, "name");
18823 assert_eq!(cols[1].name, "score");
18824 assert_eq!(rows.len(), 3);
18825 assert_eq!(
18826 rows[0].values,
18827 vec![Value::Text("alice".into()), Value::Int(90)]
18828 );
18829 }
18830
18831 #[test]
18832 fn projection_with_column_alias() {
18833 let mut e = Engine::new();
18834 make_three_row_users(&mut e);
18835 let r = e
18836 .execute("SELECT name AS who FROM users WHERE id = 1")
18837 .unwrap();
18838 let (cols, rows) = unwrap_rows(r);
18839 assert_eq!(cols[0].name, "who");
18840 assert_eq!(rows.len(), 1);
18841 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18842 }
18843
18844 #[test]
18845 fn qualified_column_with_table_alias_resolves() {
18846 let mut e = Engine::new();
18847 make_three_row_users(&mut e);
18848 let r = e
18849 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18850 .unwrap();
18851 let (cols, rows) = unwrap_rows(r);
18852 assert_eq!(cols.len(), 2);
18853 assert_eq!(rows.len(), 2);
18854 }
18855
18856 #[test]
18857 fn qualified_column_with_wrong_alias_errors() {
18858 let mut e = Engine::new();
18859 make_three_row_users(&mut e);
18860 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18861 assert!(matches!(
18862 err,
18863 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18864 ));
18865 }
18866
18867 #[test]
18868 fn select_unknown_column_errors_in_projection() {
18869 let mut e = Engine::new();
18870 make_three_row_users(&mut e);
18871 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18872 assert!(matches!(
18873 err,
18874 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18875 ));
18876 }
18877
18878 #[test]
18879 fn where_unknown_column_errors() {
18880 let mut e = Engine::new();
18881 make_three_row_users(&mut e);
18882 let err = e
18883 .execute("SELECT * FROM users WHERE ghost = 1")
18884 .unwrap_err();
18885 assert!(matches!(
18886 err,
18887 EngineError::Eval(EvalError::ColumnNotFound { .. })
18888 ));
18889 }
18890
18891 #[test]
18892 fn expression_projection_evaluates_and_renders() {
18893 let mut e = Engine::new();
18896 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18897 e.execute("INSERT INTO t VALUES (3)").unwrap();
18898 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18899 assert_eq!(rows.len(), 1);
18900 assert_eq!(rows[0].values[0], Value::Int(3));
18903 }
18904
18905 #[test]
18906 fn select_unknown_table_errors() {
18907 let mut e = Engine::new();
18908 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18909 assert!(matches!(
18910 err,
18911 EngineError::Storage(StorageError::TableNotFound { .. })
18912 ));
18913 }
18914
18915 #[test]
18916 fn invalid_sql_returns_parse_error() {
18917 let mut e = Engine::new();
18920 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18921 assert!(matches!(err, EngineError::Parse(_)));
18922 }
18923
18924 #[test]
18927 fn create_index_registers_on_table() {
18928 let mut e = Engine::new();
18929 make_three_row_users(&mut e);
18930 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18931 let t = e.catalog().get("users").unwrap();
18932 assert_eq!(t.indices().len(), 1);
18933 assert_eq!(t.indices()[0].name, "by_name");
18934 }
18935
18936 #[test]
18937 fn create_index_on_unknown_table_errors() {
18938 let mut e = Engine::new();
18939 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18940 assert!(matches!(
18941 err,
18942 EngineError::Storage(StorageError::TableNotFound { .. })
18943 ));
18944 }
18945
18946 #[test]
18947 fn create_index_on_unknown_column_errors() {
18948 let mut e = Engine::new();
18949 make_three_row_users(&mut e);
18950 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18951 assert!(matches!(
18952 err,
18953 EngineError::Storage(StorageError::ColumnNotFound { .. })
18954 ));
18955 }
18956
18957 #[test]
18958 fn select_eq_uses_index_returns_same_rows_as_scan() {
18959 let mut without = Engine::new();
18963 make_three_row_users(&mut without);
18964 let mut with = Engine::new();
18965 make_three_row_users(&mut with);
18966 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18967
18968 let q = "SELECT * FROM users WHERE id = 2";
18969 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18970 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18971 assert_eq!(no_idx_rows, idx_rows);
18972 assert_eq!(idx_rows.len(), 1);
18973 }
18974
18975 #[test]
18976 fn select_eq_with_no_matching_index_value_returns_empty() {
18977 let mut e = Engine::new();
18978 make_three_row_users(&mut e);
18979 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18980 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18981 assert_eq!(rows.len(), 0);
18982 }
18983
18984 #[test]
18987 fn begin_sets_in_transaction_flag() {
18988 let mut e = Engine::new();
18989 assert!(!e.in_transaction());
18990 e.execute("BEGIN").unwrap();
18991 assert!(e.in_transaction());
18992 }
18993
18994 #[test]
18995 fn double_begin_errors() {
18996 let mut e = Engine::new();
18997 e.execute("BEGIN").unwrap();
18998 let err = e.execute("BEGIN").unwrap_err();
18999 assert_eq!(err, EngineError::TransactionAlreadyOpen);
19000 }
19001
19002 #[test]
19003 fn commit_without_begin_errors() {
19004 let mut e = Engine::new();
19005 let err = e.execute("COMMIT").unwrap_err();
19006 assert_eq!(err, EngineError::NoActiveTransaction);
19007 }
19008
19009 #[test]
19010 fn rollback_without_begin_errors() {
19011 let mut e = Engine::new();
19012 let err = e.execute("ROLLBACK").unwrap_err();
19013 assert_eq!(err, EngineError::NoActiveTransaction);
19014 }
19015
19016 #[test]
19017 fn commit_applies_shadow_to_committed_catalog() {
19018 let mut e = Engine::new();
19019 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
19020 e.execute("BEGIN").unwrap();
19021 e.execute("INSERT INTO t VALUES (1)").unwrap();
19022 e.execute("INSERT INTO t VALUES (2)").unwrap();
19023 e.execute("COMMIT").unwrap();
19024 assert!(!e.in_transaction());
19025 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
19026 }
19027
19028 #[test]
19029 fn rollback_discards_shadow() {
19030 let mut e = Engine::new();
19031 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
19032 e.execute("BEGIN").unwrap();
19033 e.execute("INSERT INTO t VALUES (1)").unwrap();
19034 e.execute("INSERT INTO t VALUES (2)").unwrap();
19035 e.execute("ROLLBACK").unwrap();
19036 assert!(!e.in_transaction());
19037 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
19038 }
19039
19040 #[test]
19041 fn select_during_tx_sees_uncommitted_writes_own_session() {
19042 let mut e = Engine::new();
19045 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
19046 e.execute("BEGIN").unwrap();
19047 e.execute("INSERT INTO t VALUES (42)").unwrap();
19048 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
19049 assert_eq!(rows.len(), 1);
19050 assert_eq!(rows[0].values[0], Value::Int(42));
19051 }
19052
19053 #[test]
19054 fn snapshot_with_no_users_is_bare_catalog_format() {
19055 let mut e = Engine::new();
19056 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19057 let bytes = e.snapshot();
19058 assert_eq!(
19059 &bytes[..8],
19060 b"SPGDB001",
19061 "must be the bare v3.x catalog magic"
19062 );
19063 let e2 = Engine::restore_envelope(&bytes).unwrap();
19064 assert!(e2.users().is_empty());
19065 assert_eq!(e2.catalog().table_count(), 1);
19066 }
19067
19068 #[test]
19069 fn snapshot_with_users_round_trips_both_via_envelope() {
19070 let mut e = Engine::new();
19071 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19072 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
19073 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
19074 .unwrap();
19075 let bytes = e.snapshot();
19076 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
19077 let e2 = Engine::restore_envelope(&bytes).unwrap();
19078 assert_eq!(e2.users().len(), 2);
19079 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
19080 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
19081 assert_eq!(e2.verify_user("alice", "wrong"), None);
19082 assert_eq!(e2.catalog().table_count(), 1);
19083 }
19084
19085 #[test]
19086 fn ddl_inside_tx_also_rolled_back() {
19087 let mut e = Engine::new();
19088 e.execute("BEGIN").unwrap();
19089 e.execute("CREATE TABLE t (v INT)").unwrap();
19090 e.execute("SELECT * FROM t").unwrap();
19092 e.execute("ROLLBACK").unwrap();
19093 let err = e.execute("SELECT * FROM t").unwrap_err();
19095 assert!(matches!(
19096 err,
19097 EngineError::Storage(StorageError::TableNotFound { .. })
19098 ));
19099 }
19100
19101 #[test]
19104 fn create_publication_lands_in_catalog() {
19105 let mut e = Engine::new();
19106 assert!(e.publications().is_empty());
19107 e.execute("CREATE PUBLICATION pub_a").unwrap();
19108 assert_eq!(e.publications().len(), 1);
19109 assert!(e.publications().contains("pub_a"));
19110 }
19111
19112 #[test]
19113 fn create_publication_duplicate_errors() {
19114 let mut e = Engine::new();
19115 e.execute("CREATE PUBLICATION pub_a").unwrap();
19116 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
19117 assert!(
19118 alloc::format!("{err:?}").contains("DuplicateName"),
19119 "got {err:?}"
19120 );
19121 }
19122
19123 #[test]
19124 fn drop_publication_silent_when_absent() {
19125 let mut e = Engine::new();
19126 let r = e.execute("DROP PUBLICATION nope").unwrap();
19129 match r {
19130 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19131 other => panic!("expected CommandOk, got {other:?}"),
19132 }
19133 }
19134
19135 #[test]
19136 fn drop_publication_present_reports_one_affected() {
19137 let mut e = Engine::new();
19138 e.execute("CREATE PUBLICATION pub_a").unwrap();
19139 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
19140 match r {
19141 QueryResult::CommandOk {
19142 affected,
19143 modified_catalog,
19144 } => {
19145 assert_eq!(affected, 1);
19146 assert!(modified_catalog);
19147 }
19148 other => panic!("expected CommandOk, got {other:?}"),
19149 }
19150 assert!(e.publications().is_empty());
19151 }
19152
19153 #[test]
19154 fn publications_persist_across_snapshot_restore() {
19155 let mut e = Engine::new();
19160 e.execute("CREATE PUBLICATION pub_a").unwrap();
19161 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
19162 .unwrap();
19163 let snap = e.snapshot();
19164 let e2 = Engine::restore_envelope(&snap).unwrap();
19165 assert_eq!(e2.publications().len(), 2);
19166 assert!(e2.publications().contains("pub_a"));
19167 assert!(e2.publications().contains("pub_b"));
19168 }
19169
19170 #[test]
19171 fn create_publication_allowed_inside_transaction() {
19172 let mut e = Engine::new();
19176 e.execute("BEGIN").unwrap();
19177 e.execute("CREATE PUBLICATION pub_a").unwrap();
19178 e.execute("COMMIT").unwrap();
19179 assert!(e.publications().contains("pub_a"));
19180 }
19181
19182 #[test]
19185 fn create_publication_for_table_list_lands_with_scope() {
19186 let mut e = Engine::new();
19187 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19188 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
19189 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
19190 .unwrap();
19191 let scope = e.publications().get("pub_a").cloned();
19192 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
19193 panic!("expected ForTables scope, got {scope:?}")
19194 };
19195 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19196 }
19197
19198 #[test]
19199 fn create_publication_all_tables_except_lands_with_scope() {
19200 let mut e = Engine::new();
19201 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
19202 .unwrap();
19203 let scope = e.publications().get("pub_a").cloned();
19204 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
19205 panic!("expected AllTablesExcept scope, got {scope:?}")
19206 };
19207 assert_eq!(ts, alloc::vec!["t3".to_string()]);
19208 }
19209
19210 #[test]
19211 fn show_publications_empty_returns_zero_rows() {
19212 let e = Engine::new();
19213 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19214 let QueryResult::Rows { rows, columns } = r else {
19215 panic!()
19216 };
19217 assert!(rows.is_empty());
19218 assert_eq!(columns.len(), 3);
19219 assert_eq!(columns[0].name, "name");
19220 assert_eq!(columns[1].name, "scope");
19221 assert_eq!(columns[2].name, "table_count");
19222 }
19223
19224 #[test]
19225 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
19226 let mut e = Engine::new();
19227 e.execute("CREATE PUBLICATION z_pub").unwrap();
19228 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
19229 .unwrap();
19230 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
19231 .unwrap();
19232 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19233 let QueryResult::Rows { rows, .. } = r else {
19234 panic!()
19235 };
19236 assert_eq!(rows.len(), 3);
19237 let names: Vec<&str> = rows
19239 .iter()
19240 .map(|r| {
19241 if let Value::Text(s) = &r.values[0] {
19242 s.as_str()
19243 } else {
19244 panic!()
19245 }
19246 })
19247 .collect();
19248 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
19249 match &rows[0].values[1] {
19251 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
19252 other => panic!("expected Text, got {other:?}"),
19253 }
19254 assert_eq!(rows[0].values[2], Value::Int(2));
19255 match &rows[1].values[1] {
19257 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
19258 other => panic!("expected Text, got {other:?}"),
19259 }
19260 assert_eq!(rows[1].values[2], Value::Int(1));
19261 match &rows[2].values[1] {
19263 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
19264 other => panic!("expected Text, got {other:?}"),
19265 }
19266 assert_eq!(rows[2].values[2], Value::Null);
19267 }
19268
19269 #[test]
19270 fn for_list_scopes_persist_across_snapshot() {
19271 let mut e = Engine::new();
19274 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
19275 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
19276 .unwrap();
19277 let snap = e.snapshot();
19278 let e2 = Engine::restore_envelope(&snap).unwrap();
19279 assert_eq!(e2.publications().len(), 2);
19280 let p1 = e2.publications().get("p1").cloned();
19281 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
19282 panic!("p1 scope lost: {p1:?}")
19283 };
19284 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19285 let p2 = e2.publications().get("p2").cloned();
19286 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
19287 panic!("p2 scope lost: {p2:?}")
19288 };
19289 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
19290 }
19291
19292 #[test]
19295 fn create_subscription_lands_in_catalog_with_defaults() {
19296 let mut e = Engine::new();
19297 e.execute(
19298 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
19299 )
19300 .unwrap();
19301 let s = e.subscriptions().get("sub_a").cloned().expect("present");
19302 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
19303 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
19304 assert!(s.enabled);
19305 assert_eq!(s.last_received_pos, 0);
19306 }
19307
19308 #[test]
19309 fn create_subscription_duplicate_name_errors() {
19310 let mut e = Engine::new();
19311 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
19312 .unwrap();
19313 let err = e
19314 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
19315 .unwrap_err();
19316 assert!(
19317 alloc::format!("{err:?}").contains("DuplicateName"),
19318 "got {err:?}"
19319 );
19320 }
19321
19322 #[test]
19323 fn drop_subscription_silent_when_absent() {
19324 let mut e = Engine::new();
19325 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
19326 match r {
19327 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19328 other => panic!("expected CommandOk, got {other:?}"),
19329 }
19330 }
19331
19332 #[test]
19333 fn subscription_advance_updates_last_pos_monotone() {
19334 let mut e = Engine::new();
19335 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19336 .unwrap();
19337 assert!(e.subscription_advance("s", 100));
19338 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19339 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19341 assert!(e.subscription_advance("s", 200));
19342 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
19343 assert!(!e.subscription_advance("missing", 1));
19344 }
19345
19346 #[test]
19347 fn show_subscriptions_returns_rows_ordered_by_name() {
19348 let mut e = Engine::new();
19349 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
19350 .unwrap();
19351 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
19352 .unwrap();
19353 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
19354 let QueryResult::Rows { rows, columns } = r else {
19355 panic!()
19356 };
19357 assert_eq!(rows.len(), 2);
19358 assert_eq!(columns.len(), 5);
19359 assert_eq!(columns[0].name, "name");
19360 assert_eq!(columns[4].name, "last_received_pos");
19361 let names: Vec<&str> = rows
19363 .iter()
19364 .map(|r| {
19365 if let Value::Text(s) = &r.values[0] {
19366 s.as_str()
19367 } else {
19368 panic!()
19369 }
19370 })
19371 .collect();
19372 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
19373 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
19375 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
19376 assert_eq!(rows[0].values[3], Value::Bool(true));
19377 assert_eq!(rows[0].values[4], Value::BigInt(0));
19378 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
19380 }
19381
19382 #[test]
19383 fn subscriptions_persist_across_snapshot_envelope_v4() {
19384 let mut e = Engine::new();
19385 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
19386 .unwrap();
19387 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
19388 .unwrap();
19389 e.subscription_advance("s2", 42);
19390 let snap = e.snapshot();
19391 let e2 = Engine::restore_envelope(&snap).unwrap();
19392 assert_eq!(e2.subscriptions().len(), 2);
19393 let s1 = e2.subscriptions().get("s1").unwrap();
19394 assert_eq!(s1.conn_str, "h=A");
19395 assert_eq!(
19396 s1.publications,
19397 alloc::vec!["p1".to_string(), "p2".to_string()]
19398 );
19399 assert_eq!(s1.last_received_pos, 0);
19400 let s2 = e2.subscriptions().get("s2").unwrap();
19401 assert_eq!(s2.last_received_pos, 42);
19402 }
19403
19404 #[test]
19405 fn v3_envelope_loads_with_empty_subscriptions() {
19406 let mut e = Engine::new();
19410 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19411 let catalog = e.catalog.serialize();
19412 let users = crate::users::serialize_users(&e.users);
19413 let pubs = e.publications.serialize();
19414 let mut buf = Vec::new();
19415 buf.extend_from_slice(b"SPGENV01");
19416 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19418 buf.extend_from_slice(&catalog);
19419 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19420 buf.extend_from_slice(&users);
19421 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19422 buf.extend_from_slice(&pubs);
19423 let crc = spg_crypto::crc32::crc32(&buf);
19424 buf.extend_from_slice(&crc.to_le_bytes());
19425
19426 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19427 assert!(e2.subscriptions().is_empty());
19428 assert!(e2.publications().contains("pub_legacy"));
19429 }
19430
19431 #[test]
19432 fn create_subscription_allowed_inside_transaction() {
19433 let mut e = Engine::new();
19434 e.execute("BEGIN").unwrap();
19435 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19436 .unwrap();
19437 e.execute("COMMIT").unwrap();
19438 assert!(e.subscriptions().contains("s"));
19439 }
19440
19441 #[test]
19443 fn analyze_populates_histogram_bounds() {
19444 let mut e = Engine::new();
19445 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19446 .unwrap();
19447 for i in 0..50 {
19448 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19449 .unwrap();
19450 }
19451 e.execute("ANALYZE t").unwrap();
19452 let stats = e.statistics();
19453 let id_stats = stats.get("t", "id").unwrap();
19454 assert!(id_stats.histogram_bounds.len() >= 2);
19455 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19456 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19457 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19458 assert_eq!(id_stats.n_distinct, 50);
19459 }
19460
19461 #[test]
19462 fn reanalyze_overwrites_prior_stats() {
19463 let mut e = Engine::new();
19464 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19465 for i in 0..10 {
19466 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19467 .unwrap();
19468 }
19469 e.execute("ANALYZE t").unwrap();
19470 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19471 assert_eq!(n1, 10);
19472 for i in 10..30 {
19473 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19474 .unwrap();
19475 }
19476 e.execute("ANALYZE t").unwrap();
19477 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19478 assert_eq!(n2, 30);
19479 }
19480
19481 #[test]
19482 fn analyze_unknown_table_errors() {
19483 let mut e = Engine::new();
19484 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19485 assert!(matches!(
19486 err,
19487 EngineError::Storage(StorageError::TableNotFound { .. })
19488 ));
19489 }
19490
19491 #[test]
19492 fn bare_analyze_covers_all_user_tables() {
19493 let mut e = Engine::new();
19494 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19495 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19496 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19497 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19498 let r = e.execute("ANALYZE").unwrap();
19499 match r {
19500 QueryResult::CommandOk {
19501 affected,
19502 modified_catalog,
19503 } => {
19504 assert_eq!(affected, 2);
19505 assert!(modified_catalog);
19506 }
19507 other => panic!("expected CommandOk, got {other:?}"),
19508 }
19509 assert!(e.statistics().get("t1", "id").is_some());
19510 assert!(e.statistics().get("t2", "name").is_some());
19511 }
19512
19513 #[test]
19514 fn select_from_spg_statistic_returns_rows_per_column() {
19515 let mut e = Engine::new();
19516 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19517 .unwrap();
19518 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19519 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19520 e.execute("ANALYZE t").unwrap();
19521 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19522 let QueryResult::Rows { rows, columns } = r else {
19523 panic!()
19524 };
19525 assert_eq!(columns.len(), 6);
19527 assert_eq!(columns[0].name, "table_name");
19528 assert_eq!(columns[4].name, "histogram_bounds");
19529 assert_eq!(columns[5].name, "cold_row_count");
19530 assert_eq!(rows.len(), 2, "one row per column of t");
19531 match (&rows[0].values[0], &rows[0].values[1]) {
19533 (Value::Text(t), Value::Text(c)) => {
19534 assert_eq!(t, "t");
19535 assert_eq!(c, "id");
19537 }
19538 _ => panic!(),
19539 }
19540 }
19541
19542 #[test]
19543 fn analyze_skips_vector_columns() {
19544 let mut e = Engine::new();
19547 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19548 .unwrap();
19549 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19550 e.execute("ANALYZE t").unwrap();
19551 assert!(e.statistics().get("t", "id").is_some());
19552 assert!(e.statistics().get("t", "v").is_none());
19553 }
19554
19555 #[test]
19556 fn statistics_persist_across_envelope_v5_round_trip() {
19557 let mut e = Engine::new();
19558 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19559 for i in 0..20 {
19560 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19561 .unwrap();
19562 }
19563 e.execute("ANALYZE").unwrap();
19564 let snap = e.snapshot();
19565 let e2 = Engine::restore_envelope(&snap).unwrap();
19566 let s = e2.statistics().get("t", "id").unwrap();
19567 assert_eq!(s.n_distinct, 20);
19568 }
19569
19570 #[test]
19573 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19574 let mut e = Engine::new();
19578 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19579 for i in 0..9 {
19580 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19581 .unwrap();
19582 }
19583 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19584 e.execute("INSERT INTO t VALUES (9)").unwrap();
19585 let needs = e.tables_needing_analyze();
19586 assert_eq!(needs, alloc::vec!["t".to_string()]);
19587 }
19588
19589 #[test]
19590 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19591 let mut e = Engine::new();
19597 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19598 for i in 0..1000 {
19599 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19600 .unwrap();
19601 }
19602 e.execute("ANALYZE t").unwrap();
19603 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19604 for i in 1000..1050 {
19605 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19606 .unwrap();
19607 }
19608 assert!(
19609 e.tables_needing_analyze().is_empty(),
19610 "50 inserts < threshold of ~105"
19611 );
19612 for i in 1050..1200 {
19613 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19614 .unwrap();
19615 }
19616 assert_eq!(
19617 e.tables_needing_analyze(),
19618 alloc::vec!["t".to_string()],
19619 "200 inserts > 0.1 × 1200 threshold"
19620 );
19621 }
19622
19623 #[test]
19624 fn auto_analyze_threshold_resets_after_analyze() {
19625 let mut e = Engine::new();
19626 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19627 for i in 0..200 {
19628 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19629 .unwrap();
19630 }
19631 assert!(!e.tables_needing_analyze().is_empty());
19632 e.execute("ANALYZE").unwrap();
19633 assert!(
19634 e.tables_needing_analyze().is_empty(),
19635 "ANALYZE must reset the counter"
19636 );
19637 }
19638
19639 #[test]
19640 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19641 let mut e = Engine::new();
19642 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19643 .unwrap();
19644 for i in 0..50 {
19645 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19646 .unwrap();
19647 }
19648 e.execute("ANALYZE t").unwrap();
19649 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19652 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19653 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19654 }
19655
19656 #[test]
19657 fn v4_envelope_loads_with_empty_statistics() {
19658 let mut e = Engine::new();
19662 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19663 .unwrap();
19664 let catalog = e.catalog.serialize();
19665 let users = crate::users::serialize_users(&e.users);
19666 let pubs = e.publications.serialize();
19667 let subs = e.subscriptions.serialize();
19668 let mut buf = Vec::new();
19669 buf.extend_from_slice(b"SPGENV01");
19670 buf.push(4u8);
19671 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19672 buf.extend_from_slice(&catalog);
19673 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19674 buf.extend_from_slice(&users);
19675 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19676 buf.extend_from_slice(&pubs);
19677 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19678 buf.extend_from_slice(&subs);
19679 let crc = spg_crypto::crc32::crc32(&buf);
19680 buf.extend_from_slice(&crc.to_le_bytes());
19681 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19682 assert!(e2.statistics().is_empty());
19683 }
19684
19685 #[test]
19686 fn v1_v2_envelope_loads_with_empty_publications() {
19687 let mut e = Engine::new();
19694 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19697 .unwrap();
19698
19699 let catalog = e.catalog.serialize();
19701 let users = crate::users::serialize_users(&e.users);
19702 let mut buf = Vec::new();
19703 buf.extend_from_slice(b"SPGENV01");
19704 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19706 buf.extend_from_slice(&catalog);
19707 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19708 buf.extend_from_slice(&users);
19709 let crc = spg_crypto::crc32::crc32(&buf);
19710 buf.extend_from_slice(&crc.to_le_bytes());
19711
19712 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19713 assert!(e2.publications().is_empty());
19714 }
19715}