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 filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7596 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7597 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7598 return Ok(QueryResult::Rows {
7599 columns: agg.columns,
7600 rows: agg.rows,
7601 });
7602 }
7603 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7605 let mut projected_rows: alloc::vec::Vec<Row> =
7606 alloc::vec::Vec::with_capacity(filtered.len());
7607 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7616 if let Some(srf_idx) = srf_position {
7617 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7618 .expect("checked by is_top_level_unnest above");
7619 for row in &filtered {
7620 let arr_val =
7621 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7622 let elements = array_value_to_elements(&arr_val)?;
7623 for elem in elements {
7627 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7628 for (i, p) in projection.iter().enumerate() {
7629 if i == srf_idx {
7630 vals.push(elem.clone());
7631 } else {
7632 vals.push(
7633 eval::eval_expr(&p.expr, row, &scan_ctx)
7634 .map_err(EngineError::Eval)?,
7635 );
7636 }
7637 }
7638 projected_rows.push(Row::new(vals));
7639 }
7640 }
7641 } else {
7642 let mut proj_memo = memoize::MemoizeCache::default();
7646 for row in &filtered {
7647 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7648 for p in &projection {
7649 vals.push(self.eval_expr_with_correlated(
7650 &p.expr,
7651 row,
7652 &scan_ctx,
7653 cancel,
7654 Some(&mut proj_memo),
7655 )?);
7656 }
7657 projected_rows.push(Row::new(vals));
7658 }
7659 }
7660 let columns: alloc::vec::Vec<ColumnSchema> = projection
7663 .iter()
7664 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7665 .collect();
7666 if !stmt.order_by.is_empty() {
7669 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7670 .iter()
7671 .enumerate()
7672 .map(|(i, r)| -> Result<_, EngineError> {
7673 let keys: Result<Vec<Value>, EngineError> = stmt
7674 .order_by
7675 .iter()
7676 .map(|ob| {
7677 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7678 })
7679 .collect();
7680 Ok((i, keys?))
7681 })
7682 .collect::<Result<_, _>>()?;
7683 indexed.sort_by(|a, b| {
7684 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7685 let o = &stmt.order_by[idx];
7686 let cmp = order_by_value_cmp(o.desc, o.nulls_first, ka, kb);
7687 if cmp != core::cmp::Ordering::Equal {
7688 return cmp;
7689 }
7690 }
7691 core::cmp::Ordering::Equal
7692 });
7693 projected_rows = indexed
7694 .into_iter()
7695 .map(|(i, _)| projected_rows[i].clone())
7696 .collect();
7697 }
7698 if let Some(offset) = stmt.offset_literal() {
7700 let off = (offset as usize).min(projected_rows.len());
7701 projected_rows.drain(..off);
7702 }
7703 if let Some(limit) = stmt.limit_literal() {
7704 projected_rows.truncate(limit as usize);
7705 }
7706 Ok(QueryResult::Rows {
7707 columns,
7708 rows: projected_rows,
7709 })
7710 }
7711
7712 fn exec_select_generate_series(
7723 &self,
7724 stmt: &SelectStatement,
7725 primary: &TableRef,
7726 cancel: CancelToken<'_>,
7727 ) -> Result<QueryResult, EngineError> {
7728 let args = primary
7729 .generate_series_args
7730 .as_ref()
7731 .expect("caller guards generate_series_args.is_some()");
7732 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7733 let ctx = EvalContext::new(&empty_schema, None);
7734 let dummy_row = Row::new(alloc::vec::Vec::new());
7735 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7736 for a in args {
7737 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7738 }
7739 let (elem_dtype, rows) = match arg_values.as_slice() {
7743 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7744 let interval_step = match step {
7745 Value::Interval { .. } => step.clone(),
7746 other => {
7747 return Err(EngineError::Unsupported(alloc::format!(
7748 "generate_series(timestamp, timestamp, …): \
7749 step must be INTERVAL, got {:?}",
7750 other.data_type()
7751 )));
7752 }
7753 };
7754 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7755 (DataType::Timestamp, rows)
7756 }
7757 [start, stop, step]
7758 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7759 {
7760 let s = value_to_i64(start);
7761 let e = value_to_i64(stop);
7762 let st = value_to_i64(step);
7763 let rows = generate_series_integers(s, e, st, &cancel)?;
7764 (DataType::BigInt, rows)
7765 }
7766 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7767 let s = value_to_i64(start);
7768 let e = value_to_i64(stop);
7769 let rows = generate_series_integers(s, e, 1, &cancel)?;
7770 (DataType::BigInt, rows)
7771 }
7772 _ => {
7773 return Err(EngineError::Unsupported(alloc::format!(
7774 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7775 argument shapes; got {:?}",
7776 arg_values
7777 .iter()
7778 .map(|v| v.data_type())
7779 .collect::<alloc::vec::Vec<_>>()
7780 )));
7781 }
7782 };
7783 let alias = primary
7784 .alias
7785 .clone()
7786 .unwrap_or_else(|| "generate_series".to_string());
7787 let col_name = alias.clone();
7788 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7789 let schema_cols = alloc::vec![col_schema.clone()];
7790 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7791 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7793 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7794 for row in rows {
7795 cancel.check()?;
7796 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7797 if matches!(v, Value::Bool(true)) {
7798 out.push(row);
7799 }
7800 }
7801 out
7802 } else {
7803 rows
7804 };
7805 if aggregate::uses_aggregate(stmt) {
7815 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7816 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7817 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7818 return Ok(QueryResult::Rows {
7819 columns: agg.columns,
7820 rows: agg.rows,
7821 });
7822 }
7823 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7825 let mut projected_rows: alloc::vec::Vec<Row> =
7826 alloc::vec::Vec::with_capacity(filtered.len());
7827 let mut proj_memo = memoize::MemoizeCache::default();
7828 for row in &filtered {
7829 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7830 for p in &projection {
7831 vals.push(self.eval_expr_with_correlated(
7833 &p.expr,
7834 row,
7835 &scan_ctx,
7836 cancel,
7837 Some(&mut proj_memo),
7838 )?);
7839 }
7840 projected_rows.push(Row::new(vals));
7841 }
7842 let columns: alloc::vec::Vec<ColumnSchema> = projection
7843 .iter()
7844 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7845 .collect();
7846 if !stmt.order_by.is_empty() {
7848 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7849 .iter()
7850 .enumerate()
7851 .map(|(i, r)| -> Result<_, EngineError> {
7852 let keys: Result<Vec<Value>, EngineError> = stmt
7853 .order_by
7854 .iter()
7855 .map(|ob| {
7856 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7857 })
7858 .collect();
7859 Ok((i, keys?))
7860 })
7861 .collect::<Result<_, _>>()?;
7862 indexed.sort_by(|a, b| {
7863 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7864 let o = &stmt.order_by[idx];
7865 let cmp = order_by_value_cmp(o.desc, o.nulls_first, ka, kb);
7866 if cmp != core::cmp::Ordering::Equal {
7867 return cmp;
7868 }
7869 }
7870 core::cmp::Ordering::Equal
7871 });
7872 projected_rows = indexed
7873 .into_iter()
7874 .map(|(i, _)| projected_rows[i].clone())
7875 .collect();
7876 }
7877 if let Some(offset) = stmt.offset_literal() {
7878 let off = (offset as usize).min(projected_rows.len());
7879 projected_rows.drain(..off);
7880 }
7881 if let Some(limit) = stmt.limit_literal() {
7882 projected_rows.truncate(limit as usize);
7883 }
7884 Ok(QueryResult::Rows {
7885 columns,
7886 rows: projected_rows,
7887 })
7888 }
7889
7890 fn exec_bare_select_cancel(
7891 &self,
7892 stmt: &SelectStatement,
7893 cancel: CancelToken<'_>,
7894 ) -> Result<QueryResult, EngineError> {
7895 check_with_ties_requires_order_by(stmt)?;
7900 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7908 return self.exec_select_with_meta_views(stmt, cancel);
7909 }
7910 if select_has_window(stmt) {
7915 return self.exec_select_with_window(stmt, cancel);
7916 }
7917 let Some(from) = &stmt.from else {
7922 let empty_schema: Vec<ColumnSchema> = Vec::new();
7923 let ctx = self.ev_ctx(&empty_schema, None);
7924 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7925 let dummy_row = Row::new(Vec::new());
7926 let mut values = Vec::with_capacity(projection.len());
7927 for p in &projection {
7928 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7929 }
7930 let columns: Vec<ColumnSchema> = projection
7931 .into_iter()
7932 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7933 .collect();
7934 return Ok(QueryResult::Rows {
7935 columns,
7936 rows: alloc::vec![Row::new(values)],
7937 });
7938 };
7939 if !from.joins.is_empty() {
7943 return self.exec_joined_select(stmt, from, cancel);
7944 }
7945 if from.primary.unnest_expr.is_some() {
7952 return self.exec_select_unnest(stmt, &from.primary, cancel);
7953 }
7954 if from.primary.generate_series_args.is_some() {
7960 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7961 }
7962 let primary = &from.primary;
7963 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7964 StorageError::TableNotFound {
7965 name: primary.name.clone(),
7966 }
7967 })?;
7968 let schema_cols = &table.schema().columns;
7969 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7972 let ctx = self.ev_ctx(schema_cols, Some(alias));
7973
7974 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7979 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7980 }
7981
7982 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7990 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7993 .or_else(|| {
7994 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
8000 })
8001 .or_else(|| {
8002 try_trgm_seek(w, schema_cols, table, alias)
8008 })
8009 });
8010
8011 if aggregate::uses_aggregate(stmt) {
8014 let mut filtered: Vec<&Row> = Vec::new();
8015 let mut memo = memoize::MemoizeCache::new();
8019 if let Some(rows) = &indexed_rows {
8020 for cow in rows {
8021 let row = cow.as_ref();
8022 if let Some(where_expr) = &stmt.where_ {
8023 let cond = self.eval_expr_with_correlated(
8024 where_expr,
8025 row,
8026 &ctx,
8027 cancel,
8028 Some(&mut memo),
8029 )?;
8030 if !matches!(cond, Value::Bool(true)) {
8031 continue;
8032 }
8033 }
8034 filtered.push(row);
8035 }
8036 } else {
8037 for i in 0..table.row_count() {
8038 let row = &table.rows()[i];
8039 if let Some(where_expr) = &stmt.where_ {
8040 let cond = self.eval_expr_with_correlated(
8041 where_expr,
8042 row,
8043 &ctx,
8044 cancel,
8045 Some(&mut memo),
8046 )?;
8047 if !matches!(cond, Value::Bool(true)) {
8048 continue;
8049 }
8050 }
8051 filtered.push(row);
8052 }
8053 }
8054 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
8055 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8056 return Ok(QueryResult::Rows {
8057 columns: agg.columns,
8058 rows: agg.rows,
8059 });
8060 }
8061
8062 let projection = build_projection(&stmt.items, schema_cols, alias)?;
8063 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
8071
8072 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8075 let mut memo = memoize::MemoizeCache::new();
8077 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
8080 if loop_idx.is_multiple_of(256) {
8081 cancel.check()?;
8082 }
8083 if let Some(where_expr) = &stmt.where_ {
8084 let cond =
8085 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
8086 if !matches!(cond, Value::Bool(true)) {
8087 return Ok(());
8088 }
8089 }
8090 let order_keys = if stmt.order_by.is_empty() {
8091 Vec::new()
8092 } else {
8093 build_order_keys(&stmt.order_by, row, &ctx)?
8094 };
8095 if let Some(srf_idx) = srf_position {
8096 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
8097 .expect("checked by is_top_level_unnest above");
8098 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
8099 let elements = array_value_to_elements(&arr_val)?;
8100 for elem in elements {
8101 let mut values = Vec::with_capacity(projection.len());
8102 for (i, p) in projection.iter().enumerate() {
8103 if i == srf_idx {
8104 values.push(elem.clone());
8105 } else {
8106 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8107 }
8108 }
8109 tagged.push((order_keys.clone(), Row::new(values)));
8110 }
8111 } else {
8112 let mut values = Vec::with_capacity(projection.len());
8113 for p in &projection {
8114 values.push(self.eval_expr_with_correlated(&p.expr, row, &ctx, cancel, None)?);
8116 }
8117 tagged.push((order_keys, Row::new(values)));
8118 }
8119 Ok(())
8120 };
8121 if let Some(rows) = &indexed_rows {
8122 for (loop_idx, cow) in rows.iter().enumerate() {
8123 process_row(cow.as_ref(), loop_idx)?;
8124 }
8125 } else {
8126 for i in 0..table.row_count() {
8127 process_row(&table.rows()[i], i)?;
8128 }
8129 }
8130
8131 if !stmt.order_by.is_empty() {
8132 let keep = if stmt.distinct || stmt.limit_with_ties {
8140 None
8141 } else {
8142 stmt.limit_literal()
8143 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8144 };
8145 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8146 partial_sort_tagged(&mut tagged, keep, &descs);
8147 }
8148
8149 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
8159 apply_offset_and_limit_tagged(
8160 &mut tagged,
8161 stmt.offset_literal(),
8162 stmt.limit_literal(),
8163 true,
8164 );
8165 tagged.into_iter().map(|(_, r)| r).collect()
8166 } else {
8167 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8168 if stmt.distinct {
8169 output_rows = dedup_rows(output_rows);
8170 }
8171 apply_offset_and_limit(
8172 &mut output_rows,
8173 stmt.offset_literal(),
8174 stmt.limit_literal(),
8175 );
8176 output_rows
8177 };
8178
8179 let columns: Vec<ColumnSchema> = projection
8180 .into_iter()
8181 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8182 .collect();
8183
8184 Ok(QueryResult::Rows {
8185 columns,
8186 rows: output_rows,
8187 })
8188 }
8189
8190 #[allow(clippy::too_many_lines)]
8197 fn materialise_table_ref(
8205 &self,
8206 tref: &TableRef,
8207 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8208 if let Some(expr) = tref.unnest_expr.as_deref() {
8209 let empty_schema: Vec<ColumnSchema> = Vec::new();
8210 let ctx = EvalContext::new(&empty_schema, None);
8211 let dummy_row = Row::new(Vec::new());
8212 let (elem_dtype, rows) =
8213 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8214 Value::Null => (DataType::Text, Vec::new()),
8215 Value::TextArray(items) => (
8216 DataType::Text,
8217 items
8218 .into_iter()
8219 .map(|item| {
8220 Row::new(alloc::vec![match item {
8221 Some(s) => Value::Text(s),
8222 None => Value::Null,
8223 }])
8224 })
8225 .collect(),
8226 ),
8227 Value::IntArray(items) => (
8228 DataType::Int,
8229 items
8230 .into_iter()
8231 .map(|item| {
8232 Row::new(alloc::vec![match item {
8233 Some(n) => Value::Int(n),
8234 None => Value::Null,
8235 }])
8236 })
8237 .collect(),
8238 ),
8239 Value::BigIntArray(items) => (
8240 DataType::BigInt,
8241 items
8242 .into_iter()
8243 .map(|item| {
8244 Row::new(alloc::vec![match item {
8245 Some(n) => Value::BigInt(n),
8246 None => Value::Null,
8247 }])
8248 })
8249 .collect(),
8250 ),
8251 other => {
8252 return Err(EngineError::Unsupported(alloc::format!(
8253 "unnest() expects an array argument, got {:?}",
8254 other.data_type()
8255 )));
8256 }
8257 };
8258 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8259 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8260 return Ok((
8261 rows,
8262 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8263 ));
8264 }
8265 let table =
8266 self.active_catalog()
8267 .get(&tref.name)
8268 .ok_or_else(|| StorageError::TableNotFound {
8269 name: tref.name.clone(),
8270 })?;
8271 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8272 let cols = table.schema().columns.clone();
8273 Ok((rows, cols))
8274 }
8275
8276 fn build_joined_filtered_rows(
8288 &self,
8289 from: &FromClause,
8290 where_: Option<&Expr>,
8291 cancel: CancelToken<'_>,
8292 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8293 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8294 let primary_alias = from
8295 .primary
8296 .alias
8297 .as_deref()
8298 .unwrap_or(from.primary.name.as_str())
8299 .to_string();
8300 #[allow(clippy::type_complexity)]
8307 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8308 for j in &from.joins {
8309 let a = j
8310 .table
8311 .alias
8312 .as_deref()
8313 .unwrap_or(j.table.name.as_str())
8314 .to_string();
8315 if let Some(inner_box) = &j.table.lateral_subquery {
8316 let schema = self.lateral_probe_schema(inner_box)?;
8321 joined.push(JoinedPeer {
8322 eager_rows: None,
8323 cols: schema,
8324 alias: a,
8325 kind: j.kind,
8326 on: j.on.as_ref(),
8327 lateral: Some(inner_box.as_ref()),
8328 });
8329 } else {
8330 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8331 joined.push(JoinedPeer {
8332 eager_rows: Some(rows),
8333 cols,
8334 alias: a,
8335 kind: j.kind,
8336 on: j.on.as_ref(),
8337 lateral: None,
8338 });
8339 }
8340 }
8341 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8342 for col in &primary_cols {
8343 combined_schema.push(ColumnSchema::new(
8344 alloc::format!("{primary_alias}.{}", col.name),
8345 col.ty,
8346 col.nullable,
8347 ));
8348 }
8349 for peer in &joined {
8350 for col in &peer.cols {
8351 combined_schema.push(ColumnSchema::new(
8352 alloc::format!("{}.{}", peer.alias, col.name),
8353 col.ty,
8354 col.nullable,
8355 ));
8356 }
8357 }
8358 let ctx = EvalContext::new(&combined_schema, None);
8359 let mut working: Vec<Row> = primary_rows;
8360 let mut consumed_cols = primary_cols.len();
8363 for peer in &joined {
8364 let right_arity = peer.cols.len();
8365 let mut next: Vec<Row> = Vec::new();
8366 for left in &working {
8367 let mut left_matched = false;
8368 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8369 Some(inner) => {
8370 let outer_schema = &combined_schema[..consumed_cols];
8374 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8375 alloc::borrow::Cow::Owned(rows)
8376 }
8377 None => {
8378 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8379 alloc::borrow::Cow::Borrowed(r.as_slice())
8380 }
8381 };
8382 for right in per_left_rrows.as_ref() {
8383 let mut combined_vals = left.values.clone();
8384 combined_vals.extend(right.values.iter().cloned());
8385 let combined = Row::new(combined_vals);
8386 let keep = if let Some(on_expr) = peer.on {
8387 let cond =
8390 self.eval_expr_with_correlated(on_expr, &combined, &ctx, cancel, None)?;
8391 matches!(cond, Value::Bool(true))
8392 } else {
8393 true
8394 };
8395 if keep {
8396 next.push(combined);
8397 left_matched = true;
8398 }
8399 }
8400 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8401 let mut combined_vals = left.values.clone();
8402 for _ in 0..right_arity {
8403 combined_vals.push(Value::Null);
8404 }
8405 next.push(Row::new(combined_vals));
8406 }
8407 }
8408 working = next;
8409 consumed_cols += right_arity;
8410 debug_assert!(consumed_cols <= combined_schema.len());
8411 }
8412 let mut filtered: Vec<Row> = Vec::new();
8413 let mut memo = memoize::MemoizeCache::default();
8419 for row in working {
8420 if let Some(where_expr) = where_ {
8421 let cond = self.eval_expr_with_correlated(
8422 where_expr,
8423 &row,
8424 &ctx,
8425 cancel,
8426 Some(&mut memo),
8427 )?;
8428 if !matches!(cond, Value::Bool(true)) {
8429 continue;
8430 }
8431 }
8432 filtered.push(row);
8433 }
8434 Ok((combined_schema, filtered))
8435 }
8436
8437 fn lateral_probe_schema(
8443 &self,
8444 inner: &SelectStatement,
8445 ) -> Result<Vec<ColumnSchema>, EngineError> {
8446 match self.execute_readonly_select_for_lateral_probe(inner) {
8456 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8457 _ => {
8463 let mut out: Vec<ColumnSchema> = Vec::new();
8464 for (i, item) in inner.items.iter().enumerate() {
8465 let name = match item {
8466 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8467 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8468 SelectItem::Wildcard => alloc::format!("col{i}"),
8469 };
8470 out.push(ColumnSchema::new(name, DataType::Text, true));
8471 }
8472 Ok(out)
8473 }
8474 }
8475 }
8476
8477 fn execute_readonly_select_for_lateral_probe(
8483 &self,
8484 inner: &SelectStatement,
8485 ) -> Result<QueryResult, EngineError> {
8486 self.exec_bare_select_cancel(inner, CancelToken::none())
8487 }
8488
8489 fn materialise_lateral_for_outer(
8495 &self,
8496 inner: &SelectStatement,
8497 outer_schema: &[ColumnSchema],
8498 outer_row: &Row,
8499 ) -> Result<Vec<Row>, EngineError> {
8500 let mut substituted = inner.clone();
8501 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8502 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8503 match result {
8504 QueryResult::Rows { rows, .. } => Ok(rows),
8505 _ => Err(EngineError::Unsupported(
8506 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8507 )),
8508 }
8509 }
8510
8511 fn exec_joined_select(
8512 &self,
8513 stmt: &SelectStatement,
8514 from: &FromClause,
8515 cancel: CancelToken<'_>,
8516 ) -> Result<QueryResult, EngineError> {
8517 let (combined_schema, filtered) =
8525 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel)?;
8526 let ctx = EvalContext::new(&combined_schema, None);
8527 if aggregate::uses_aggregate(stmt) {
8530 let refs: Vec<&Row> = filtered.iter().collect();
8531 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8532 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8533 return Ok(QueryResult::Rows {
8534 columns: agg.columns,
8535 rows: agg.rows,
8536 });
8537 }
8538
8539 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8540 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8541 let mut proj_memo = memoize::MemoizeCache::default();
8542 for row in &filtered {
8543 let mut values = Vec::with_capacity(projection.len());
8544 for p in &projection {
8545 values.push(self.eval_expr_with_correlated(
8548 &p.expr,
8549 row,
8550 &ctx,
8551 cancel,
8552 Some(&mut proj_memo),
8553 )?);
8554 }
8555 let order_keys = if stmt.order_by.is_empty() {
8556 Vec::new()
8557 } else {
8558 build_order_keys(&stmt.order_by, row, &ctx)?
8559 };
8560 tagged.push((order_keys, Row::new(values)));
8561 }
8562 if !stmt.order_by.is_empty() {
8563 let keep = if stmt.distinct {
8564 None
8565 } else {
8566 stmt.limit_literal()
8567 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8568 };
8569 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8570 partial_sort_tagged(&mut tagged, keep, &descs);
8571 }
8572 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8573 if stmt.distinct {
8574 output_rows = dedup_rows(output_rows);
8575 }
8576 apply_offset_and_limit(
8577 &mut output_rows,
8578 stmt.offset_literal(),
8579 stmt.limit_literal(),
8580 );
8581 let columns: Vec<ColumnSchema> = projection
8582 .into_iter()
8583 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8584 .collect();
8585 Ok(QueryResult::Rows {
8586 columns,
8587 rows: output_rows,
8588 })
8589 }
8590}
8591
8592#[derive(Debug, Clone)]
8595struct ProjectedItem {
8596 expr: Expr,
8597 output_name: String,
8598 ty: DataType,
8599 nullable: bool,
8600}
8601
8602fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8608 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8609 for r in rows {
8610 if !out.iter().any(|seen| seen == &r) {
8611 out.push(r);
8612 }
8613 }
8614 out
8615}
8616
8617fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8621 match v {
8622 Value::Null => Ok(f64::INFINITY),
8623 Value::SmallInt(n) => Ok(f64::from(*n)),
8624 Value::Int(n) => Ok(f64::from(*n)),
8625 Value::Date(d) => Ok(f64::from(*d)),
8626 #[allow(clippy::cast_precision_loss)]
8627 Value::Timestamp(t) => Ok(*t as f64),
8628 #[allow(clippy::cast_precision_loss)]
8631 Value::Time(us) => Ok(*us as f64),
8632 Value::Year(y) => Ok(f64::from(*y)),
8636 #[allow(clippy::cast_precision_loss)]
8641 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8642 #[allow(clippy::cast_precision_loss)]
8644 Value::Money(c) => Ok(*c as f64),
8645 Value::Range { .. } => Err(EngineError::Unsupported(
8648 "ORDER BY of a range value is not supported in v7.17.0".into(),
8649 )),
8650 Value::Hstore(_) => Err(EngineError::Unsupported(
8652 "ORDER BY of a hstore value is not supported".into(),
8653 )),
8654 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8656 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8657 ),
8658 #[allow(clippy::cast_precision_loss)]
8659 Value::Numeric { scaled, scale } => {
8660 let mut divisor = 1.0_f64;
8666 for _ in 0..*scale {
8667 divisor *= 10.0;
8668 }
8669 Ok((*scaled as f64) / divisor)
8670 }
8671 #[allow(clippy::cast_precision_loss)]
8672 Value::BigInt(n) => Ok(*n as f64),
8673 Value::Float(x) => Ok(*x),
8674 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8675 Value::Text(s) => {
8676 let mut key: u64 = 0;
8680 for &b in s.as_bytes().iter().take(8) {
8681 key = (key << 8) | u64::from(b);
8682 }
8683 #[allow(clippy::cast_precision_loss)]
8684 Ok(key as f64)
8685 }
8686 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8687 Err(EngineError::Unsupported(
8688 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8689 ))
8690 }
8691 Value::Interval { .. } => Err(EngineError::Unsupported(
8692 "ORDER BY of an INTERVAL is not supported in v2.11 \
8693 (months vs micros has no single canonical ordering)"
8694 .into(),
8695 )),
8696 Value::Json(_) => Err(EngineError::Unsupported(
8697 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8698 )),
8699 _ => Err(EngineError::Unsupported(
8703 "ORDER BY of this value type is not supported".into(),
8704 )),
8705 }
8706}
8707
8708fn try_nsw_knn(
8722 stmt: &SelectStatement,
8723 table: &Table,
8724 schema_cols: &[ColumnSchema],
8725 table_alias: &str,
8726) -> Option<Vec<usize>> {
8727 if stmt.distinct {
8728 return None;
8729 }
8730 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8731 if limit == 0 {
8732 return None;
8733 }
8734 if stmt.order_by.len() != 1 {
8738 return None;
8739 }
8740 let order = &stmt.order_by[0];
8741 if order.desc {
8745 return None;
8746 }
8747 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8748 return None;
8749 };
8750 let metric = match op {
8751 BinOp::L2Distance => spg_storage::NswMetric::L2,
8752 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8753 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8754 _ => return None,
8755 };
8756 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8758 (lhs.as_ref(), rhs.as_ref())
8759 else {
8760 return None;
8761 };
8762 if let Some(q) = &col.qualifier
8763 && q != table_alias
8764 {
8765 return None;
8766 }
8767 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8768 let query = literal_to_vector(literal)?;
8769 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8770 if let Some(where_expr) = &stmt.where_ {
8771 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8775 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8776 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8777 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8778 for i in candidates {
8779 let row = &table.rows()[i];
8780 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8781 if matches!(cond, Value::Bool(true)) {
8782 kept.push(i);
8783 if kept.len() >= limit {
8784 break;
8785 }
8786 }
8787 }
8788 Some(kept)
8789 } else {
8790 Some(spg_storage::nsw_query(
8791 table, &idx.name, &query, limit, metric,
8792 ))
8793 }
8794}
8795
8796const NSW_OVER_FETCH_FLOOR: usize = 32;
8800
8801fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8804 match e {
8805 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8806 Expr::Cast { expr, .. } => literal_to_vector(expr),
8807 _ => None,
8808 }
8809}
8810
8811fn materialise_in_order(
8815 stmt: &SelectStatement,
8816 table: &Table,
8817 schema_cols: &[ColumnSchema],
8818 table_alias: &str,
8819 ordered_rows: &[usize],
8820) -> Result<QueryResult, EngineError> {
8821 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8822 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8823 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8824 for &i in ordered_rows {
8825 let row = &table.rows()[i];
8826 let mut values = Vec::with_capacity(projection.len());
8827 for p in &projection {
8828 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8829 }
8830 output_rows.push(Row::new(values));
8831 }
8832 apply_offset_and_limit(
8833 &mut output_rows,
8834 stmt.offset_literal(),
8835 stmt.limit_literal(),
8836 );
8837 let columns: Vec<ColumnSchema> = projection
8838 .into_iter()
8839 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8840 .collect();
8841 Ok(QueryResult::Rows {
8842 columns,
8843 rows: output_rows,
8844 })
8845}
8846
8847fn try_index_seek_positions(
8860 where_expr: &Expr,
8861 schema_cols: &[ColumnSchema],
8862 table: &Table,
8863 table_alias: &str,
8864) -> Option<Vec<usize>> {
8865 if let Expr::Binary {
8866 lhs,
8867 op: BinOp::And,
8868 rhs,
8869 } = where_expr
8870 {
8871 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8872 return Some(p);
8873 }
8874 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8875 }
8876 let Expr::Binary {
8877 lhs,
8878 op: BinOp::Eq,
8879 rhs,
8880 } = where_expr
8881 else {
8882 return None;
8883 };
8884 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8885 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8886 let idx = table.index_on(col_pos)?;
8887 let key = IndexKey::from_value(&value)?;
8888 let locators = idx.lookup_eq(&key);
8889 let mut out = Vec::with_capacity(locators.len());
8890 for loc in locators {
8891 match *loc {
8892 spg_storage::RowLocator::Hot(i) => out.push(i),
8893 spg_storage::RowLocator::Cold { .. } => return None,
8894 }
8895 }
8896 Some(out)
8897}
8898
8899fn try_index_seek<'a>(
8900 where_expr: &Expr,
8901 schema_cols: &[ColumnSchema],
8902 catalog: &'a Catalog,
8903 table: &'a Table,
8904 table_alias: &str,
8905) -> Option<Vec<Cow<'a, Row>>> {
8906 if let Expr::Binary {
8913 lhs,
8914 op: BinOp::And,
8915 rhs,
8916 } = where_expr
8917 {
8918 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8921 return Some(rows);
8922 }
8923 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8924 }
8925 let Expr::Binary {
8926 lhs,
8927 op: BinOp::Eq,
8928 rhs,
8929 } = where_expr
8930 else {
8931 return None;
8932 };
8933 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8934 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8935 let idx = table.index_on(col_pos)?;
8936 let key = IndexKey::from_value(&value)?;
8937 let locators = idx.lookup_eq(&key);
8938 let table_name = table.schema().name.as_str();
8939 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8947 for loc in locators {
8948 match *loc {
8949 spg_storage::RowLocator::Hot(i) => {
8950 if let Some(row) = table.rows().get(i) {
8951 out.push(Cow::Borrowed(row));
8952 }
8953 }
8954 spg_storage::RowLocator::Cold { segment_id, .. } => {
8955 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8956 out.push(Cow::Owned(row));
8957 }
8958 }
8959 }
8960 }
8961 Some(out)
8962}
8963
8964fn try_gin_seek<'a>(
8983 where_expr: &Expr,
8984 schema_cols: &[ColumnSchema],
8985 catalog: &'a Catalog,
8986 table: &'a Table,
8987 table_alias: &str,
8988 ctx: &eval::EvalContext<'_>,
8989) -> Option<Vec<Cow<'a, Row>>> {
8990 if let Expr::Binary {
8991 lhs,
8992 op: BinOp::And,
8993 rhs,
8994 } = where_expr
8995 {
8996 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8997 return Some(rows);
8998 }
8999 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
9000 }
9001 if let Expr::Binary {
9010 lhs,
9011 op: BinOp::Or,
9012 rhs,
9013 } = where_expr
9014 {
9015 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
9016 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
9017 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
9018 out.extend(left);
9019 out.extend(right);
9020 return Some(out);
9021 }
9022 let Expr::Binary {
9023 lhs,
9024 op: BinOp::TsMatch,
9025 rhs,
9026 } = where_expr
9027 else {
9028 return None;
9029 };
9030 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
9035 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
9036 let idx = table
9043 .indices()
9044 .iter()
9045 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
9046 let candidates = gin_query_candidates(idx, &query)?;
9047 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
9049 for loc in candidates {
9050 match loc {
9051 spg_storage::RowLocator::Hot(i) => {
9052 if let Some(row) = table.rows().get(i) {
9053 out.push(Cow::Borrowed(row));
9054 }
9055 }
9056 spg_storage::RowLocator::Cold { .. } => {}
9063 }
9064 }
9065 Some(out)
9066}
9067
9068fn try_trgm_seek<'a>(
9084 where_expr: &Expr,
9085 schema_cols: &[ColumnSchema],
9086 table: &'a Table,
9087 table_alias: &str,
9088) -> Option<Vec<Cow<'a, Row>>> {
9089 if let Expr::Binary {
9090 lhs,
9091 op: BinOp::And,
9092 rhs,
9093 } = where_expr
9094 {
9095 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9096 return Some(rows);
9097 }
9098 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9099 }
9100 let Expr::Like { expr, pattern, .. } = where_expr else {
9106 return None;
9107 };
9108 let Expr::Column(c) = expr.as_ref() else {
9110 return None;
9111 };
9112 if let Some(q) = &c.qualifier
9113 && q != table_alias
9114 {
9115 return None;
9116 }
9117 let col_pos = schema_cols
9118 .iter()
9119 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9120 let idx = table
9122 .indices()
9123 .iter()
9124 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9125 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9129 return None;
9130 };
9131 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9132 let mut iter = trigrams.iter();
9135 let first = iter.next()?;
9136 let mut acc: Vec<spg_storage::RowLocator> = {
9137 let mut v = idx.gin_trgm_lookup(first).to_vec();
9138 v.sort_by_key(locator_sort_key);
9139 v.dedup_by_key(|l| locator_sort_key(l));
9140 v
9141 };
9142 for tri in iter {
9143 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9144 next.sort_by_key(locator_sort_key);
9145 next.dedup_by_key(|l| locator_sort_key(l));
9146 let mut merged: Vec<spg_storage::RowLocator> =
9148 Vec::with_capacity(acc.len().min(next.len()));
9149 let (mut i, mut j) = (0usize, 0usize);
9150 while i < acc.len() && j < next.len() {
9151 let lk = locator_sort_key(&acc[i]);
9152 let rk = locator_sort_key(&next[j]);
9153 match lk.cmp(&rk) {
9154 core::cmp::Ordering::Less => i += 1,
9155 core::cmp::Ordering::Greater => j += 1,
9156 core::cmp::Ordering::Equal => {
9157 merged.push(acc[i]);
9158 i += 1;
9159 j += 1;
9160 }
9161 }
9162 }
9163 acc = merged;
9164 if acc.is_empty() {
9165 break;
9166 }
9167 }
9168 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9169 for loc in acc {
9170 if let spg_storage::RowLocator::Hot(i) = loc
9171 && let Some(row) = table.rows().get(i)
9172 {
9173 out.push(Cow::Borrowed(row));
9174 }
9175 }
9177 Some(out)
9178}
9179
9180fn resolve_gin_col_query(
9186 col_side: &Expr,
9187 query_side: &Expr,
9188 schema_cols: &[ColumnSchema],
9189 table_alias: &str,
9190 ctx: &eval::EvalContext<'_>,
9191) -> Option<(usize, spg_storage::TsQueryAst)> {
9192 let column = match col_side {
9197 Expr::Column(c) => c,
9198 Expr::FunctionCall { name, args }
9199 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9200 {
9201 if let Expr::Column(c) = args.last().unwrap() {
9205 c
9206 } else {
9207 return None;
9208 }
9209 }
9210 _ => return None,
9211 };
9212 let c = column;
9213 if let Some(q) = &c.qualifier
9214 && q != table_alias
9215 {
9216 return None;
9217 }
9218 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9219 let empty_row = Row::new(Vec::new());
9223 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9224 let Value::TsQuery(q) = v else { return None };
9225 Some((pos, q))
9226}
9227
9228fn gin_query_candidates(
9239 idx: &spg_storage::Index,
9240 query: &spg_storage::TsQueryAst,
9241) -> Option<Vec<spg_storage::RowLocator>> {
9242 use spg_storage::TsQueryAst;
9243 match query {
9244 TsQueryAst::Term { word, .. } => {
9245 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9246 v.sort_by_key(locator_sort_key);
9247 v.dedup_by_key(|l| locator_sort_key(l));
9248 Some(v)
9249 }
9250 TsQueryAst::And(l, r) => {
9251 let mut left = gin_query_candidates(idx, l)?;
9252 let mut right = gin_query_candidates(idx, r)?;
9253 left.sort_by_key(locator_sort_key);
9254 right.sort_by_key(locator_sort_key);
9255 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9257 let (mut i, mut j) = (0usize, 0usize);
9258 while i < left.len() && j < right.len() {
9259 let lk = locator_sort_key(&left[i]);
9260 let rk = locator_sort_key(&right[j]);
9261 match lk.cmp(&rk) {
9262 core::cmp::Ordering::Less => i += 1,
9263 core::cmp::Ordering::Greater => j += 1,
9264 core::cmp::Ordering::Equal => {
9265 out.push(left[i]);
9266 i += 1;
9267 j += 1;
9268 }
9269 }
9270 }
9271 Some(out)
9272 }
9273 TsQueryAst::Or(l, r) => {
9274 let mut out = gin_query_candidates(idx, l)?;
9275 out.extend(gin_query_candidates(idx, r)?);
9276 out.sort_by_key(locator_sort_key);
9277 out.dedup_by_key(|l| locator_sort_key(l));
9278 Some(out)
9279 }
9280 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9285 }
9286}
9287
9288fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9293 match *l {
9294 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9295 spg_storage::RowLocator::Cold {
9296 segment_id,
9297 page_offset,
9298 } => (1, u64::from(segment_id), u64::from(page_offset)),
9299 }
9300}
9301
9302fn try_pk_predicate(
9314 where_expr: &Expr,
9315 schema_cols: &[ColumnSchema],
9316 table_alias: &str,
9317) -> Option<(usize, IndexKey)> {
9318 let Expr::Binary {
9319 lhs,
9320 op: BinOp::Eq,
9321 rhs,
9322 } = where_expr
9323 else {
9324 return None;
9325 };
9326 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9327 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9328 let key = IndexKey::from_value(&value)?;
9329 Some((col_pos, key))
9330}
9331
9332fn resolve_col_literal_pair(
9333 col_side: &Expr,
9334 lit_side: &Expr,
9335 schema_cols: &[ColumnSchema],
9336 table_alias: &str,
9337) -> Option<(usize, Value)> {
9338 let Expr::Column(c) = col_side else {
9339 return None;
9340 };
9341 if let Some(q) = &c.qualifier
9342 && q != table_alias
9343 {
9344 return None;
9345 }
9346 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9347 let Expr::Literal(l) = lit_side else {
9348 return None;
9349 };
9350 let v = match l {
9351 Literal::Integer(n) => {
9352 if let Ok(small) = i32::try_from(*n) {
9353 Value::Int(small)
9354 } else {
9355 Value::BigInt(*n)
9356 }
9357 }
9358 Literal::Float(x) => Value::Float(*x),
9359 Literal::String(s) => Value::Text(s.clone()),
9360 Literal::Bool(b) => Value::Bool(*b),
9361 Literal::Null => Value::Null,
9362 Literal::Vector(_)
9365 | Literal::Interval { .. }
9366 | Literal::TextArray(_)
9367 | Literal::IntArray(_)
9368 | Literal::BigIntArray(_) => return None,
9369 };
9370 Some((pos, v))
9371}
9372
9373fn resolve_projection_column<'a>(
9378 c: &ColumnName,
9379 schema_cols: &'a [ColumnSchema],
9380 table_alias: &str,
9381) -> Result<&'a ColumnSchema, EngineError> {
9382 if let Some(q) = &c.qualifier {
9383 let composite = alloc::format!("{q}.{name}", name = c.name);
9384 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9385 return Ok(s);
9386 }
9387 if q == table_alias
9390 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9391 {
9392 return Ok(s);
9393 }
9394 let prefix = alloc::format!("{q}.");
9398 let qualifier_known =
9399 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9400 if !qualifier_known {
9401 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9402 qualifier: q.clone(),
9403 }));
9404 }
9405 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9406 name: c.name.clone(),
9407 }));
9408 }
9409 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9410 return Ok(s);
9411 }
9412 let suffix = alloc::format!(".{name}", name = c.name);
9413 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9414 let first = matches.next();
9415 let extra = matches.next();
9416 match (first, extra) {
9417 (Some(s), None) => Ok(s),
9418 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9419 detail: alloc::format!("ambiguous column reference: {}", c.name),
9420 })),
9421 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9422 name: c.name.clone(),
9423 })),
9424 }
9425}
9426
9427fn build_projection(
9428 items: &[SelectItem],
9429 schema_cols: &[ColumnSchema],
9430 table_alias: &str,
9431) -> Result<Vec<ProjectedItem>, EngineError> {
9432 let mut out = Vec::new();
9433 for item in items {
9434 match item {
9435 SelectItem::Wildcard => {
9436 for col in schema_cols {
9437 out.push(ProjectedItem {
9438 expr: Expr::Column(ColumnName {
9439 qualifier: None,
9440 name: col.name.clone(),
9441 }),
9442 output_name: col.name.clone(),
9443 ty: col.ty,
9444 nullable: col.nullable,
9445 });
9446 }
9447 }
9448 SelectItem::Expr { expr, alias } => {
9449 if let Expr::Column(c) = expr {
9456 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9457 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9458 out.push(ProjectedItem {
9459 expr: expr.clone(),
9460 output_name,
9461 ty: sch.ty,
9462 nullable: sch.nullable,
9463 });
9464 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9465 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9466 out.push(ProjectedItem {
9467 expr: expr.clone(),
9468 output_name,
9469 ty: shape.ty,
9470 nullable: shape.nullable,
9471 });
9472 } else {
9473 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9474 out.push(ProjectedItem {
9475 expr: expr.clone(),
9476 output_name,
9477 ty: DataType::Text,
9478 nullable: true,
9479 });
9480 }
9481 }
9482 }
9483 }
9484 Ok(out)
9485}
9486
9487fn numeric_from_integer(
9491 n: i128,
9492 precision: u8,
9493 scale: u8,
9494 col_name: &str,
9495) -> Result<Value, EngineError> {
9496 let factor = pow10_i128(scale);
9497 let scaled = n.checked_mul(factor).ok_or_else(|| {
9498 EngineError::Unsupported(alloc::format!(
9499 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9500 ))
9501 })?;
9502 check_precision(scaled, precision, col_name)?;
9503 Ok(Value::Numeric { scaled, scale })
9504}
9505
9506#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9509fn numeric_from_float(
9510 x: f64,
9511 precision: u8,
9512 scale: u8,
9513 col_name: &str,
9514) -> Result<Value, EngineError> {
9515 if !x.is_finite() {
9516 return Err(EngineError::Unsupported(alloc::format!(
9517 "cannot store non-finite float in NUMERIC column `{col_name}`"
9518 )));
9519 }
9520 let mut factor = 1.0_f64;
9521 for _ in 0..scale {
9522 factor *= 10.0;
9523 }
9524 let shifted = x * factor;
9529 let biased = if shifted >= 0.0 {
9530 shifted + 0.5
9531 } else {
9532 shifted - 0.5
9533 };
9534 if !(-1e38..=1e38).contains(&biased) {
9537 return Err(EngineError::Unsupported(alloc::format!(
9538 "value {x} overflows NUMERIC range for column `{col_name}`"
9539 )));
9540 }
9541 let scaled = biased as i128;
9542 check_precision(scaled, precision, col_name)?;
9543 Ok(Value::Numeric { scaled, scale })
9544}
9545
9546fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9553 let s = s.trim();
9554 if s.is_empty() {
9555 return None;
9556 }
9557 let (negative, rest) = match s.as_bytes()[0] {
9558 b'-' => (true, &s[1..]),
9559 b'+' => (false, &s[1..]),
9560 _ => (false, s),
9561 };
9562 if rest.is_empty() {
9563 return None;
9564 }
9565 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9569 return None;
9570 }
9571 let (int_part, frac_part) = match rest.find('.') {
9572 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9573 None => (rest, ""),
9574 };
9575 if int_part.is_empty() && frac_part.is_empty() {
9576 return None;
9577 }
9578 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9579 return None;
9580 }
9581 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9582 return None;
9583 }
9584 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9585 if scale_u32 > u32::from(u8::MAX) {
9586 return None;
9587 }
9588 let scale = scale_u32 as u8;
9589 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9590 if negative {
9591 digits.push('-');
9592 }
9593 digits.push_str(int_part);
9594 digits.push_str(frac_part);
9595 let digits = if digits == "-" {
9597 return None;
9598 } else if digits.is_empty() {
9599 "0"
9600 } else {
9601 digits.as_str()
9602 };
9603 let mantissa: i128 = digits.parse().ok()?;
9604 Some((mantissa, scale))
9605}
9606
9607fn numeric_rescale(
9610 scaled: i128,
9611 src_scale: u8,
9612 precision: u8,
9613 dst_scale: u8,
9614 col_name: &str,
9615) -> Result<Value, EngineError> {
9616 let new_scaled = if dst_scale >= src_scale {
9617 let bump = pow10_i128(dst_scale - src_scale);
9618 scaled.checked_mul(bump).ok_or_else(|| {
9619 EngineError::Unsupported(alloc::format!(
9620 "overflow rescaling NUMERIC for column `{col_name}`"
9621 ))
9622 })?
9623 } else {
9624 let drop = pow10_i128(src_scale - dst_scale);
9625 let half = drop / 2;
9626 if scaled >= 0 {
9627 (scaled + half) / drop
9628 } else {
9629 (scaled - half) / drop
9630 }
9631 };
9632 check_precision(new_scaled, precision, col_name)?;
9633 Ok(Value::Numeric {
9634 scaled: new_scaled,
9635 scale: dst_scale,
9636 })
9637}
9638
9639const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9642 if scale == 0 {
9643 return scaled;
9644 }
9645 let factor = pow10_i128_const(scale);
9646 scaled / factor
9647}
9648
9649fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9653 if precision == 0 {
9654 return Ok(());
9655 }
9656 let limit = pow10_i128(precision);
9657 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9658 return Err(EngineError::Unsupported(alloc::format!(
9659 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9660 )));
9661 }
9662 Ok(())
9663}
9664
9665const fn pow10_i128_const(p: u8) -> i128 {
9666 let mut acc: i128 = 1;
9667 let mut i = 0;
9668 while i < p {
9669 acc *= 10;
9670 i += 1;
9671 }
9672 acc
9673}
9674
9675fn pow10_i128(p: u8) -> i128 {
9676 pow10_i128_const(p)
9677}
9678
9679impl Engine {
9694 #[allow(
9705 clippy::too_many_lines,
9706 clippy::type_complexity,
9707 clippy::needless_range_loop
9708 )] fn exec_select_with_window(
9710 &self,
9711 stmt: &SelectStatement,
9712 cancel: CancelToken<'_>,
9713 ) -> Result<QueryResult, EngineError> {
9714 let from = stmt.from.as_ref().ok_or_else(|| {
9715 EngineError::Unsupported("window functions require a FROM clause".into())
9716 })?;
9717 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9726 let filtered: Vec<Row>;
9727 if from.joins.is_empty() {
9728 let primary = &from.primary;
9729 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9730 StorageError::TableNotFound {
9731 name: primary.name.clone(),
9732 }
9733 })?;
9734 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9735 schema_cols_owned = table.schema().columns.clone();
9736 alias_opt = Some(alias);
9737 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9742 let mut owned: Vec<Row> = Vec::new();
9743 for (i, row) in table.rows().iter().enumerate() {
9744 if i.is_multiple_of(256) {
9745 cancel.check()?;
9746 }
9747 if let Some(w) = &stmt.where_ {
9748 let cond = eval::eval_expr(w, row, &ctx)?;
9749 if !matches!(cond, Value::Bool(true)) {
9750 continue;
9751 }
9752 }
9753 owned.push(row.clone());
9754 }
9755 filtered = owned;
9756 } else {
9757 let (combined_schema, rows) =
9758 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel)?;
9759 schema_cols_owned = combined_schema;
9760 alias_opt = None;
9761 filtered = rows;
9762 }
9763 let schema_cols = &schema_cols_owned;
9764 let ctx = self.ev_ctx(schema_cols, alias_opt);
9765 let alias = alias_opt.unwrap_or("");
9766 let n_rows = filtered.len();
9767 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9771
9772 let mut window_nodes: Vec<Expr> = Vec::new();
9774 for item in &stmt.items {
9775 if let SelectItem::Expr { expr, .. } = item {
9776 collect_window_nodes(expr, &mut window_nodes);
9777 }
9778 }
9779
9780 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9783 for wnode in &window_nodes {
9784 let Expr::WindowFunction {
9785 name,
9786 args,
9787 partition_by,
9788 order_by,
9789 frame,
9790 null_treatment,
9791 } = wnode
9792 else {
9793 unreachable!("collect_window_nodes pushes only WindowFunction");
9794 };
9795 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)> =
9797 Vec::with_capacity(n_rows);
9798 for (i, row) in filtered.iter().enumerate() {
9799 let pkey: Vec<Value> = partition_by
9800 .iter()
9801 .map(|p| eval::eval_expr(p, row, &ctx))
9802 .collect::<Result<_, _>>()?;
9803 let okey: Vec<(Value, bool, Option<bool>)> = order_by
9804 .iter()
9805 .map(|(e, desc, nf)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc, *nf)))
9806 .collect::<Result<_, _>>()?;
9807 indexed.push((pkey, okey, i));
9808 }
9809 indexed.sort_by(|a, b| {
9812 let p_cmp = partition_key_cmp(&a.0, &b.0);
9813 if p_cmp != core::cmp::Ordering::Equal {
9814 return p_cmp;
9815 }
9816 order_key_cmp(&a.1, &b.1)
9817 });
9818 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9820 let mut p_start = 0;
9821 while p_start < indexed.len() {
9822 let mut p_end = p_start + 1;
9823 while p_end < indexed.len()
9824 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9825 == core::cmp::Ordering::Equal
9826 {
9827 p_end += 1;
9828 }
9829 compute_window_partition(
9831 name,
9832 args,
9833 !order_by.is_empty(),
9834 frame.as_ref(),
9835 *null_treatment,
9836 &indexed[p_start..p_end],
9837 &filtered_refs,
9838 &ctx,
9839 &mut out_vals,
9840 )?;
9841 p_start = p_end;
9842 }
9843 win_vals.push(out_vals);
9844 }
9845
9846 let mut ext_cols = schema_cols.clone();
9848 for i in 0..window_nodes.len() {
9849 ext_cols.push(ColumnSchema::new(
9850 alloc::format!("__win_{i}"),
9851 DataType::Text, true,
9853 ));
9854 }
9855 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9857 for i in 0..n_rows {
9858 let mut values = filtered[i].values.clone();
9859 for w in 0..window_nodes.len() {
9860 values.push(win_vals[w][i].clone());
9861 }
9862 ext_rows.push(Row::new(values));
9863 }
9864 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9866 for item in &stmt.items {
9867 let new_item = match item {
9868 SelectItem::Wildcard => SelectItem::Wildcard,
9869 SelectItem::Expr { expr, alias } => {
9870 let mut e = expr.clone();
9871 rewrite_window_to_columns(&mut e, &window_nodes);
9872 SelectItem::Expr {
9873 expr: e,
9874 alias: alias.clone(),
9875 }
9876 }
9877 };
9878 rewritten_items.push(new_item);
9879 }
9880
9881 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9887 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9888 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9889 for (i, row) in ext_rows.iter().enumerate() {
9890 if i.is_multiple_of(256) {
9891 cancel.check()?;
9892 }
9893 let mut values = Vec::with_capacity(projection.len());
9894 for p in &projection {
9895 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9896 }
9897 let order_keys = if stmt.order_by.is_empty() {
9898 Vec::new()
9899 } else {
9900 let mut keys = Vec::with_capacity(stmt.order_by.len());
9901 for o in &stmt.order_by {
9902 let mut e = o.expr.clone();
9903 rewrite_window_to_columns(&mut e, &window_nodes);
9904 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9905 keys.push(value_to_order_key(&key)?);
9906 }
9907 keys
9908 };
9909 tagged.push((order_keys, Row::new(values)));
9910 }
9911 if !stmt.order_by.is_empty() {
9913 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9914 sort_by_keys(&mut tagged, &descs);
9915 }
9916 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9917 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9918 let final_cols: Vec<ColumnSchema> = projection
9919 .into_iter()
9920 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9921 .collect();
9922 Ok(QueryResult::Rows {
9923 columns: final_cols,
9924 rows: out_rows,
9925 })
9926 }
9927
9928 fn exec_select_with_meta_views(
9945 &self,
9946 stmt: &SelectStatement,
9947 cancel: CancelToken<'_>,
9948 ) -> Result<QueryResult, EngineError> {
9949 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9950 collect_meta_view_names(stmt, &mut needed);
9951 let mut catalog = self.active_catalog().clone();
9952 for view in &needed {
9953 if catalog.get(view).is_some() {
9954 continue;
9955 }
9956 match view.as_str() {
9957 "__spg_info_columns" => {
9958 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9959 materialise_meta_view(&mut catalog, view, schema, rows)?;
9960 }
9961 "__spg_info_tables" => {
9962 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9963 materialise_meta_view(&mut catalog, view, schema, rows)?;
9964 }
9965 "__spg_pg_class" => {
9966 let (schema, rows) = synth_pg_class(self.active_catalog());
9967 materialise_meta_view(&mut catalog, view, schema, rows)?;
9968 }
9969 "__spg_pg_attribute" => {
9970 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9971 materialise_meta_view(&mut catalog, view, schema, rows)?;
9972 }
9973 "__spg_pg_type" => {
9976 let (schema, rows) = synth_pg_type(self.active_catalog());
9977 materialise_meta_view(&mut catalog, view, schema, rows)?;
9978 }
9979 "__spg_pg_proc" => {
9982 let (schema, rows) = synth_pg_proc(self.active_catalog());
9983 materialise_meta_view(&mut catalog, view, schema, rows)?;
9984 }
9985 "__spg_pg_trigger" => {
9992 let (schema, rows) = synth_pg_trigger(self.active_catalog());
9993 materialise_meta_view(&mut catalog, view, schema, rows)?;
9994 }
9995 "__spg_pg_namespace" => {
9998 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9999 materialise_meta_view(&mut catalog, view, schema, rows)?;
10000 }
10001 "__spg_pg_indexes" => {
10004 let (schema, rows) = synth_pg_indexes(self.active_catalog());
10005 materialise_meta_view(&mut catalog, view, schema, rows)?;
10006 }
10007 "__spg_pg_index" => {
10010 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
10011 materialise_meta_view(&mut catalog, view, schema, rows)?;
10012 }
10013 "__spg_pg_constraint" => {
10016 let (schema, rows) = synth_pg_constraint(self.active_catalog());
10017 materialise_meta_view(&mut catalog, view, schema, rows)?;
10018 }
10019 "__spg_pg_database" => {
10024 let (schema, rows) = synth_pg_database(self.active_catalog());
10025 materialise_meta_view(&mut catalog, view, schema, rows)?;
10026 }
10027 "__spg_pg_roles" | "__spg_pg_user" => {
10028 let (schema, rows) = synth_pg_roles(self);
10029 materialise_meta_view(&mut catalog, view, schema, rows)?;
10030 }
10031 "__spg_pg_views" => {
10035 let (schema, rows) = synth_pg_views(self.active_catalog());
10036 materialise_meta_view(&mut catalog, view, schema, rows)?;
10037 }
10038 "__spg_pg_matviews" => {
10042 let (schema, _) = synth_pg_views(self.active_catalog());
10043 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
10044 }
10045 "__spg_pg_extension" => {
10048 let (schema, rows) = synth_pg_extension();
10049 materialise_meta_view(&mut catalog, view, schema, rows)?;
10050 }
10051 "__spg_pg_settings" => {
10053 let (schema, rows) = synth_pg_settings(self);
10054 materialise_meta_view(&mut catalog, view, schema, rows)?;
10055 }
10056 "__spg_info_key_column_usage" => {
10058 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
10059 materialise_meta_view(&mut catalog, view, schema, rows)?;
10060 }
10061 "__spg_info_referential_constraints" => {
10063 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
10064 materialise_meta_view(&mut catalog, view, schema, rows)?;
10065 }
10066 "__spg_info_statistics" => {
10068 let (schema, rows) = synth_info_statistics(self.active_catalog());
10069 materialise_meta_view(&mut catalog, view, schema, rows)?;
10070 }
10071 "__spg_info_routines" => {
10073 let (schema, rows) = synth_info_routines();
10074 materialise_meta_view(&mut catalog, view, schema, rows)?;
10075 }
10076 "__spg_mysql_user" => {
10078 let (schema, rows) = synth_mysql_user(self);
10079 materialise_meta_view(&mut catalog, view, schema, rows)?;
10080 }
10081 "__spg_mysql_db" => {
10082 let (schema, rows) = synth_mysql_db();
10083 materialise_meta_view(&mut catalog, view, schema, rows)?;
10084 }
10085 _ => {
10086 return Err(EngineError::Unsupported(alloc::format!(
10087 "meta view {view:?} is not yet materialisable; \
10088 v7.16.2 covers information_schema.columns / .tables \
10089 and pg_catalog.pg_class / pg_attribute; \
10090 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
10091 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
10092 pg_user / pg_views / pg_matviews / pg_settings"
10093 )));
10094 }
10095 }
10096 }
10097 let mut temp = Engine::restore(catalog);
10098 if let Some(c) = self.clock {
10099 temp = temp.with_clock(c);
10100 }
10101 if let Some(f) = self.salt_fn {
10102 temp = temp.with_salt_fn(f);
10103 }
10104 temp.meta_views_materialised = true;
10105 temp.exec_select_cancel(stmt, cancel)
10106 }
10107
10108 fn exec_with_ctes(
10109 &self,
10110 stmt: &SelectStatement,
10111 cancel: CancelToken<'_>,
10112 ) -> Result<QueryResult, EngineError> {
10113 cancel.check()?;
10114 let mut catalog = self.active_catalog().clone();
10115 for cte in &stmt.ctes {
10116 if catalog.get(&cte.name).is_some() {
10117 return Err(EngineError::Unsupported(alloc::format!(
10118 "CTE name {:?} shadows an existing table; rename the CTE",
10119 cte.name
10120 )));
10121 }
10122 let (columns, rows) = if cte.recursive {
10123 self.materialise_recursive_cte(cte, &catalog, cancel)?
10124 } else {
10125 let mut cte_engine = Engine::restore(catalog.clone());
10131 if let Some(c) = self.clock {
10132 cte_engine = cte_engine.with_clock(c);
10133 }
10134 if let Some(f) = self.salt_fn {
10135 cte_engine = cte_engine.with_salt_fn(f);
10136 }
10137 let body_result = cte_engine.exec_select_cancel(&cte.body, cancel)?;
10138 let QueryResult::Rows { columns, rows } = body_result else {
10139 return Err(EngineError::Unsupported(alloc::format!(
10140 "CTE {:?} body did not return rows",
10141 cte.name
10142 )));
10143 };
10144 (columns, rows)
10145 };
10146 let inferred = infer_column_types(&columns, &rows);
10151 let mut columns = inferred;
10152 if !cte.column_overrides.is_empty() {
10154 if cte.column_overrides.len() != columns.len() {
10155 return Err(EngineError::Unsupported(alloc::format!(
10156 "CTE {:?} column list has {} names but body returns {} columns",
10157 cte.name,
10158 cte.column_overrides.len(),
10159 columns.len()
10160 )));
10161 }
10162 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10163 col.name.clone_from(name);
10164 }
10165 }
10166 let schema = TableSchema::new(cte.name.clone(), columns);
10167 catalog.create_table(schema).map_err(EngineError::Storage)?;
10168 let table = catalog
10169 .get_mut(&cte.name)
10170 .expect("just-created CTE table must exist");
10171 for row in rows {
10172 table.insert(row).map_err(EngineError::Storage)?;
10173 }
10174 }
10175 let mut body = stmt.clone();
10178 body.ctes = Vec::new();
10179 let mut temp = Engine::restore(catalog);
10180 if let Some(c) = self.clock {
10181 temp = temp.with_clock(c);
10182 }
10183 if let Some(f) = self.salt_fn {
10184 temp = temp.with_salt_fn(f);
10185 }
10186 temp.exec_select_cancel(&body, cancel)
10187 }
10188
10189 #[allow(clippy::too_many_lines)]
10199 fn materialise_recursive_cte(
10200 &self,
10201 cte: &spg_sql::ast::Cte,
10202 base_catalog: &Catalog,
10203 cancel: CancelToken<'_>,
10204 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10205 const MAX_TOTAL_ROWS: usize = 1_000_000;
10206 const MAX_ITERATIONS: usize = 100_000;
10207 cancel.check()?;
10208 if cte.body.unions.is_empty() {
10209 return Err(EngineError::Unsupported(alloc::format!(
10210 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10211 cte.name
10212 )));
10213 }
10214 let mut anchor = cte.body.clone();
10216 let union_terms = core::mem::take(&mut anchor.unions);
10217 anchor.ctes = Vec::new();
10218 if select_refers_to(&anchor, &cte.name) {
10220 return Err(EngineError::Unsupported(alloc::format!(
10221 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10222 cte.name
10223 )));
10224 }
10225 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10226 let QueryResult::Rows {
10227 columns: anchor_cols,
10228 rows: anchor_rows,
10229 } = anchor_result
10230 else {
10231 return Err(EngineError::Unsupported(alloc::format!(
10232 "WITH RECURSIVE {:?}: anchor did not return rows",
10233 cte.name
10234 )));
10235 };
10236 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10240 if !cte.column_overrides.is_empty() {
10241 if cte.column_overrides.len() != columns.len() {
10242 return Err(EngineError::Unsupported(alloc::format!(
10243 "CTE {:?} column list has {} names but anchor returns {} columns",
10244 cte.name,
10245 cte.column_overrides.len(),
10246 columns.len()
10247 )));
10248 }
10249 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10250 col.name.clone_from(name);
10251 }
10252 }
10253 let mut all_rows: Vec<Row> = anchor_rows.clone();
10254 let mut working_set: Vec<Row> = anchor_rows;
10255 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10256 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10259 if !all_union_all {
10260 for r in &all_rows {
10261 seen.insert(encode_row_key(r));
10262 }
10263 }
10264 for iter in 0..MAX_ITERATIONS {
10265 cancel.check()?;
10266 if working_set.is_empty() {
10267 break;
10268 }
10269 let mut iter_catalog = base_catalog.clone();
10271 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10272 iter_catalog
10273 .create_table(schema)
10274 .map_err(EngineError::Storage)?;
10275 {
10276 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10277 for row in &working_set {
10278 table.insert(row.clone()).map_err(EngineError::Storage)?;
10279 }
10280 }
10281 let mut iter_engine = Engine::restore(iter_catalog);
10282 if let Some(c) = self.clock {
10283 iter_engine = iter_engine.with_clock(c);
10284 }
10285 if let Some(f) = self.salt_fn {
10286 iter_engine = iter_engine.with_salt_fn(f);
10287 }
10288 let mut next_set: Vec<Row> = Vec::new();
10290 for (_, term) in &union_terms {
10291 let mut term = term.clone();
10292 term.ctes = Vec::new();
10293 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10294 let QueryResult::Rows {
10295 columns: rc,
10296 rows: rs,
10297 } = r
10298 else {
10299 return Err(EngineError::Unsupported(alloc::format!(
10300 "WITH RECURSIVE {:?}: recursive term did not return rows",
10301 cte.name
10302 )));
10303 };
10304 if rc.len() != columns.len() {
10305 return Err(EngineError::Unsupported(alloc::format!(
10306 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10307 cte.name,
10308 rc.len(),
10309 columns.len()
10310 )));
10311 }
10312 for row in rs {
10313 if !all_union_all {
10314 let key = encode_row_key(&row);
10315 if !seen.insert(key) {
10316 continue;
10317 }
10318 }
10319 next_set.push(row);
10320 }
10321 }
10322 if next_set.is_empty() {
10323 break;
10324 }
10325 all_rows.extend(next_set.iter().cloned());
10326 working_set = next_set;
10327 if all_rows.len() > MAX_TOTAL_ROWS {
10328 return Err(EngineError::Unsupported(alloc::format!(
10329 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10330 cte.name
10331 )));
10332 }
10333 if iter + 1 == MAX_ITERATIONS {
10334 return Err(EngineError::Unsupported(alloc::format!(
10335 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10336 cte.name
10337 )));
10338 }
10339 }
10340 Ok((columns, all_rows))
10341 }
10342
10343 fn resolve_select_subqueries(
10344 &self,
10345 stmt: &mut SelectStatement,
10346 cancel: CancelToken<'_>,
10347 ) -> Result<(), EngineError> {
10348 for item in &mut stmt.items {
10349 if let SelectItem::Expr { expr, .. } = item {
10350 self.resolve_expr_subqueries(expr, cancel)?;
10351 }
10352 }
10353 if let Some(w) = &mut stmt.where_ {
10354 self.resolve_expr_subqueries(w, cancel)?;
10355 }
10356 if let Some(from) = &mut stmt.from {
10360 for j in &mut from.joins {
10361 if let Some(on) = &mut j.on {
10362 self.resolve_expr_subqueries(on, cancel)?;
10363 }
10364 }
10365 }
10366 if let Some(gs) = &mut stmt.group_by {
10367 for g in gs {
10368 self.resolve_expr_subqueries(g, cancel)?;
10369 }
10370 }
10371 if let Some(h) = &mut stmt.having {
10372 self.resolve_expr_subqueries(h, cancel)?;
10373 }
10374 for o in &mut stmt.order_by {
10375 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10376 }
10377 for (_, peer) in &mut stmt.unions {
10378 self.resolve_select_subqueries(peer, cancel)?;
10379 }
10380 Ok(())
10381 }
10382
10383 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10385 &self,
10386 e: &mut Expr,
10387 cancel: CancelToken<'_>,
10388 ) -> Result<(), EngineError> {
10389 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10391 *e = replacement;
10392 return Ok(());
10393 }
10394 match e {
10395 Expr::AggregateOrdered { call, order_by, .. } => {
10396 self.resolve_expr_subqueries(call, cancel)?;
10397 for o in order_by.iter_mut() {
10398 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10399 }
10400 }
10401 Expr::Binary { lhs, rhs, .. } => {
10402 self.resolve_expr_subqueries(lhs, cancel)?;
10403 self.resolve_expr_subqueries(rhs, cancel)?;
10404 }
10405 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10406 self.resolve_expr_subqueries(expr, cancel)?;
10407 }
10408 Expr::FunctionCall { args, .. } => {
10409 for a in args {
10410 self.resolve_expr_subqueries(a, cancel)?;
10411 }
10412 }
10413 Expr::Like { expr, pattern, .. } => {
10414 self.resolve_expr_subqueries(expr, cancel)?;
10415 self.resolve_expr_subqueries(pattern, cancel)?;
10416 }
10417 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10418 Expr::WindowFunction {
10421 args,
10422 partition_by,
10423 order_by,
10424 ..
10425 } => {
10426 for a in args {
10427 self.resolve_expr_subqueries(a, cancel)?;
10428 }
10429 for p in partition_by {
10430 self.resolve_expr_subqueries(p, cancel)?;
10431 }
10432 for (e, _, _) in order_by {
10433 self.resolve_expr_subqueries(e, cancel)?;
10434 }
10435 }
10436 Expr::ScalarSubquery(_)
10440 | Expr::Exists { .. }
10441 | Expr::InSubquery { .. }
10442 | Expr::Literal(_)
10443 | Expr::Placeholder(_)
10444 | Expr::Column(_) => {}
10445 Expr::Array(items) => {
10447 for elem in items {
10448 self.resolve_expr_subqueries(elem, cancel)?;
10449 }
10450 }
10451 Expr::ArraySubscript { target, index } => {
10452 self.resolve_expr_subqueries(target, cancel)?;
10453 self.resolve_expr_subqueries(index, cancel)?;
10454 }
10455 Expr::AnyAll { expr, array, .. } => {
10456 self.resolve_expr_subqueries(expr, cancel)?;
10457 self.resolve_expr_subqueries(array, cancel)?;
10458 }
10459 Expr::Case {
10460 operand,
10461 branches,
10462 else_branch,
10463 } => {
10464 if let Some(o) = operand {
10465 self.resolve_expr_subqueries(o, cancel)?;
10466 }
10467 for (w, t) in branches {
10468 self.resolve_expr_subqueries(w, cancel)?;
10469 self.resolve_expr_subqueries(t, cancel)?;
10470 }
10471 if let Some(e) = else_branch {
10472 self.resolve_expr_subqueries(e, cancel)?;
10473 }
10474 }
10475 }
10476 Ok(())
10477 }
10478
10479 fn eval_expr_with_correlated(
10487 &self,
10488 expr: &Expr,
10489 row: &Row,
10490 ctx: &EvalContext<'_>,
10491 cancel: CancelToken<'_>,
10492 memo: Option<&mut memoize::MemoizeCache>,
10493 ) -> Result<Value, EngineError> {
10494 if !expr_has_subquery(expr) {
10495 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10496 }
10497 let mut e = expr.clone();
10498 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10499 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10500 }
10501
10502 fn resolve_correlated_in_expr(
10503 &self,
10504 e: &mut Expr,
10505 row: &Row,
10506 ctx: &EvalContext<'_>,
10507 cancel: CancelToken<'_>,
10508 mut memo: Option<&mut memoize::MemoizeCache>,
10509 ) -> Result<(), EngineError> {
10510 match e {
10511 Expr::AggregateOrdered { call, order_by, .. } => {
10512 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
10513 for o in order_by.iter_mut() {
10514 self.resolve_correlated_in_expr(
10515 &mut o.expr,
10516 row,
10517 ctx,
10518 cancel,
10519 memo.as_deref_mut(),
10520 )?;
10521 }
10522 }
10523 Expr::ScalarSubquery(inner) => {
10524 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10529 subquery_repr: alloc::format!("{}", **inner),
10530 outer_values: row.values.clone(),
10531 });
10532 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10533 && let Some(cached) = cache.get(k)
10534 {
10535 *e = value_to_literal_expr(cached)?;
10536 return Ok(());
10537 }
10538 let mut s = (**inner).clone();
10539 substitute_outer_columns(&mut s, row, ctx);
10540 let r = self.exec_select_cancel(&s, cancel)?;
10541 let QueryResult::Rows { rows, .. } = r else {
10542 return Err(EngineError::Unsupported(
10543 "scalar subquery: inner did not return rows".into(),
10544 ));
10545 };
10546 let value = match rows.as_slice() {
10547 [] => Value::Null,
10548 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10549 _ => {
10550 return Err(EngineError::Unsupported(alloc::format!(
10551 "scalar subquery returned {} rows; expected 0 or 1",
10552 rows.len()
10553 )));
10554 }
10555 };
10556 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10557 cache.insert(k, value.clone());
10558 }
10559 *e = value_to_literal_expr(value)?;
10560 }
10561 Expr::Exists { subquery, negated } => {
10562 let mut s = (**subquery).clone();
10563 substitute_outer_columns(&mut s, row, ctx);
10564 let r = self.exec_select_cancel(&s, cancel)?;
10565 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10566 let bit = if *negated { !exists } else { exists };
10567 *e = Expr::Literal(Literal::Bool(bit));
10568 }
10569 Expr::InSubquery {
10570 expr: lhs,
10571 subquery,
10572 negated,
10573 } => {
10574 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10575 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10576 let mut s = (**subquery).clone();
10577 substitute_outer_columns(&mut s, row, ctx);
10578 let r = self.exec_select_cancel(&s, cancel)?;
10579 let QueryResult::Rows { columns, rows, .. } = r else {
10580 return Err(EngineError::Unsupported(
10581 "IN-subquery: inner did not return rows".into(),
10582 ));
10583 };
10584 if columns.len() != 1 {
10585 return Err(EngineError::Unsupported(alloc::format!(
10586 "IN-subquery must project exactly one column; got {}",
10587 columns.len()
10588 )));
10589 }
10590 let mut found = false;
10591 let mut any_null = false;
10592 for r0 in rows {
10593 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10594 if v.is_null() {
10595 any_null = true;
10596 continue;
10597 }
10598 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10599 found = true;
10600 break;
10601 }
10602 }
10603 let bit = if found {
10604 !*negated
10605 } else if any_null {
10606 return Err(EngineError::Unsupported(
10607 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10608 ));
10609 } else {
10610 *negated
10611 };
10612 *e = Expr::Literal(Literal::Bool(bit));
10613 }
10614 Expr::Binary { lhs, rhs, .. } => {
10615 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10616 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10617 }
10618 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10619 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10620 }
10621 Expr::Like { expr, pattern, .. } => {
10622 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10623 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10624 }
10625 Expr::FunctionCall { args, .. } => {
10626 for a in args {
10627 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10628 }
10629 }
10630 Expr::Extract { source, .. } => {
10631 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10632 }
10633 Expr::WindowFunction { .. }
10634 | Expr::Literal(_)
10635 | Expr::Placeholder(_)
10636 | Expr::Column(_) => {}
10637 Expr::Array(items) => {
10639 for elem in items {
10640 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10641 }
10642 }
10643 Expr::ArraySubscript { target, index } => {
10644 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10645 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10646 }
10647 Expr::AnyAll { expr, array, .. } => {
10648 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10649 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10650 }
10651 Expr::Case {
10652 operand,
10653 branches,
10654 else_branch,
10655 } => {
10656 if let Some(o) = operand {
10657 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10658 }
10659 for (w, t) in branches {
10660 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10661 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10662 }
10663 if let Some(e) = else_branch {
10664 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10665 }
10666 }
10667 }
10668 Ok(())
10669 }
10670
10671 fn subquery_replacement(
10672 &self,
10673 e: &Expr,
10674 cancel: CancelToken<'_>,
10675 ) -> Result<Option<Expr>, EngineError> {
10676 match e {
10677 Expr::ScalarSubquery(inner) => {
10678 let mut s = (**inner).clone();
10679 self.resolve_select_subqueries(&mut s, cancel)?;
10682 let r = match self.exec_bare_select_cancel(&s, cancel) {
10683 Ok(r) => r,
10684 Err(e) if is_correlation_error(&e) => return Ok(None),
10685 Err(e) => return Err(e),
10686 };
10687 let QueryResult::Rows { rows, .. } = r else {
10688 return Err(EngineError::Unsupported(
10689 "scalar subquery: inner statement did not return rows".into(),
10690 ));
10691 };
10692 let value = match rows.as_slice() {
10693 [] => Value::Null,
10694 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10695 _ => {
10696 return Err(EngineError::Unsupported(alloc::format!(
10697 "scalar subquery returned {} rows; expected 0 or 1",
10698 rows.len()
10699 )));
10700 }
10701 };
10702 Ok(Some(value_to_literal_expr(value)?))
10703 }
10704 Expr::Exists { subquery, negated } => {
10705 let mut s = (**subquery).clone();
10706 self.resolve_select_subqueries(&mut s, cancel)?;
10707 let r = match self.exec_bare_select_cancel(&s, cancel) {
10708 Ok(r) => r,
10709 Err(e) if is_correlation_error(&e) => return Ok(None),
10710 Err(e) => return Err(e),
10711 };
10712 let exists = match r {
10713 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10714 QueryResult::CommandOk { .. } => false,
10715 };
10716 let bit = if *negated { !exists } else { exists };
10717 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10718 }
10719 Expr::InSubquery {
10720 expr,
10721 subquery,
10722 negated,
10723 } => {
10724 let mut s = (**subquery).clone();
10725 self.resolve_select_subqueries(&mut s, cancel)?;
10726 let r = match self.exec_bare_select_cancel(&s, cancel) {
10727 Ok(r) => r,
10728 Err(e) if is_correlation_error(&e) => return Ok(None),
10729 Err(e) => return Err(e),
10730 };
10731 let QueryResult::Rows { columns, rows, .. } = r else {
10732 return Err(EngineError::Unsupported(
10733 "IN-subquery: inner statement did not return rows".into(),
10734 ));
10735 };
10736 if columns.len() != 1 {
10737 return Err(EngineError::Unsupported(alloc::format!(
10738 "IN-subquery must project exactly one column; got {}",
10739 columns.len()
10740 )));
10741 }
10742 let mut acc: Option<Expr> = None;
10745 for row in rows {
10746 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10747 let lit = value_to_literal_expr(v)?;
10748 let cmp = Expr::Binary {
10749 lhs: expr.clone(),
10750 op: BinOp::Eq,
10751 rhs: Box::new(lit),
10752 };
10753 acc = Some(match acc {
10754 None => cmp,
10755 Some(prev) => Expr::Binary {
10756 lhs: Box::new(prev),
10757 op: BinOp::Or,
10758 rhs: Box::new(cmp),
10759 },
10760 });
10761 }
10762 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10763 let final_expr = if *negated {
10764 Expr::Unary {
10765 op: UnOp::Not,
10766 expr: Box::new(combined),
10767 }
10768 } else {
10769 combined
10770 };
10771 Ok(Some(final_expr))
10772 }
10773 _ => Ok(None),
10774 }
10775 }
10776}
10777
10778fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10790 if let Some(from) = &stmt.from
10791 && from_refers_to(from, target)
10792 {
10793 return true;
10794 }
10795 for (_, peer) in &stmt.unions {
10796 if select_refers_to(peer, target) {
10797 return true;
10798 }
10799 }
10800 for item in &stmt.items {
10801 if let SelectItem::Expr { expr, .. } = item
10802 && expr_refers_to(expr, target)
10803 {
10804 return true;
10805 }
10806 }
10807 if let Some(w) = &stmt.where_
10808 && expr_refers_to(w, target)
10809 {
10810 return true;
10811 }
10812 false
10813}
10814
10815fn from_refers_to(from: &FromClause, target: &str) -> bool {
10816 if from.primary.name.eq_ignore_ascii_case(target) {
10817 return true;
10818 }
10819 from.joins
10820 .iter()
10821 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10822}
10823
10824fn expr_refers_to(e: &Expr, target: &str) -> bool {
10825 match e {
10826 Expr::AggregateOrdered { call, order_by, .. } => {
10827 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
10828 }
10829 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10830 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10831 select_refers_to(subquery, target)
10832 }
10833 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10834 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10835 expr_refers_to(expr, target)
10836 }
10837 Expr::Like { expr, pattern, .. } => {
10838 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10839 }
10840 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10841 Expr::Extract { source, .. } => expr_refers_to(source, target),
10842 Expr::WindowFunction {
10843 args,
10844 partition_by,
10845 order_by,
10846 ..
10847 } => {
10848 args.iter().any(|a| expr_refers_to(a, target))
10849 || partition_by.iter().any(|p| expr_refers_to(p, target))
10850 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
10851 }
10852 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10853 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10854 Expr::ArraySubscript { target: t, index } => {
10855 expr_refers_to(t, target) || expr_refers_to(index, target)
10856 }
10857 Expr::AnyAll { expr, array, .. } => {
10858 expr_refers_to(expr, target) || expr_refers_to(array, target)
10859 }
10860 Expr::Case {
10861 operand,
10862 branches,
10863 else_branch,
10864 } => {
10865 operand
10866 .as_deref()
10867 .is_some_and(|o| expr_refers_to(o, target))
10868 || branches
10869 .iter()
10870 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10871 || else_branch
10872 .as_deref()
10873 .is_some_and(|e| expr_refers_to(e, target))
10874 }
10875 }
10876}
10877
10878fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10889 let s = match ty {
10890 DataType::Int => "integer",
10891 DataType::BigInt => "bigint",
10892 DataType::SmallInt => "smallint",
10893 DataType::Float => "double precision",
10894 DataType::Bool => "boolean",
10895 DataType::Text => "text",
10896 DataType::Varchar(_) => "character varying",
10897 DataType::Date => "date",
10898 DataType::Timestamp => "timestamp without time zone",
10899 DataType::Timestamptz => "timestamp with time zone",
10900 DataType::Json => "jsonb",
10901 DataType::Bytes => "bytea",
10902 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10903 DataType::TsVector => "tsvector",
10904 DataType::TsQuery => "tsquery",
10905 DataType::Vector { .. } => "USER-DEFINED",
10906 _ => "USER-DEFINED",
10909 };
10910 alloc::string::String::from(s)
10911}
10912
10913fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10920 let schema = alloc::vec![
10921 ColumnSchema::new("table_catalog", DataType::Text, false),
10922 ColumnSchema::new("table_schema", DataType::Text, false),
10923 ColumnSchema::new("table_name", DataType::Text, false),
10924 ColumnSchema::new("column_name", DataType::Text, false),
10925 ColumnSchema::new("ordinal_position", DataType::Int, false),
10926 ColumnSchema::new("is_nullable", DataType::Text, false),
10927 ColumnSchema::new("data_type", DataType::Text, false),
10928 ];
10929 let mut rows: Vec<Row> = Vec::new();
10930 for tname in cat.table_names() {
10931 let Some(t) = cat.get(&tname) else { continue };
10932 for (i, col) in t.schema().columns.iter().enumerate() {
10933 #[allow(clippy::cast_possible_wrap)]
10934 let ordinal = (i + 1) as i32;
10935 rows.push(Row::new(alloc::vec![
10936 Value::Text("spg".into()),
10937 Value::Text("public".into()),
10938 Value::Text(tname.clone()),
10939 Value::Text(col.name.clone()),
10940 Value::Int(ordinal),
10941 Value::Text(if col.nullable {
10942 "YES".into()
10943 } else {
10944 "NO".into()
10945 }),
10946 Value::Text(pg_data_type_text(col.ty)),
10947 ]));
10948 }
10949 }
10950 (schema, rows)
10951}
10952
10953fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10955 let schema = alloc::vec![
10956 ColumnSchema::new("table_catalog", DataType::Text, false),
10957 ColumnSchema::new("table_schema", DataType::Text, false),
10958 ColumnSchema::new("table_name", DataType::Text, false),
10959 ColumnSchema::new("table_type", DataType::Text, false),
10960 ];
10961 let mut rows: Vec<Row> = Vec::new();
10962 for tname in cat.table_names() {
10963 rows.push(Row::new(alloc::vec![
10964 Value::Text("spg".into()),
10965 Value::Text("public".into()),
10966 Value::Text(tname.clone()),
10967 Value::Text("BASE TABLE".into()),
10968 ]));
10969 }
10970 (schema, rows)
10971}
10972
10973fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10977 let schema = alloc::vec![
10978 ColumnSchema::new("relname", DataType::Text, false),
10979 ColumnSchema::new("relkind", DataType::Text, false),
10980 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10981 ];
10982 let mut rows: Vec<Row> = Vec::new();
10983 for tname in cat.table_names() {
10984 rows.push(Row::new(alloc::vec![
10985 Value::Text(tname.clone()),
10986 Value::Text("r".into()),
10987 Value::BigInt(2200), ]));
10989 }
10990 (schema, rows)
10991}
10992
10993fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10997 let schema = alloc::vec![
10998 ColumnSchema::new("attrelid", DataType::Text, false),
10999 ColumnSchema::new("attname", DataType::Text, false),
11000 ColumnSchema::new("attnum", DataType::Int, false),
11001 ColumnSchema::new("atttypid", DataType::Text, false),
11002 ColumnSchema::new("attnotnull", DataType::Bool, false),
11003 ];
11004 let mut rows: Vec<Row> = Vec::new();
11005 for tname in cat.table_names() {
11006 let Some(t) = cat.get(&tname) else { continue };
11007 for (i, col) in t.schema().columns.iter().enumerate() {
11008 #[allow(clippy::cast_possible_wrap)]
11009 let ordinal = (i + 1) as i32;
11010 rows.push(Row::new(alloc::vec![
11011 Value::Text(tname.clone()),
11012 Value::Text(col.name.clone()),
11013 Value::Int(ordinal),
11014 Value::Text(pg_data_type_text(col.ty)),
11015 Value::Bool(!col.nullable),
11016 ]));
11017 }
11018 }
11019 (schema, rows)
11020}
11021
11022fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11039 let schema = alloc::vec![
11040 ColumnSchema::new("oid", DataType::BigInt, false),
11041 ColumnSchema::new("typname", DataType::Text, false),
11042 ColumnSchema::new("typlen", DataType::SmallInt, false),
11043 ColumnSchema::new("typtype", DataType::Text, false),
11044 ColumnSchema::new("typcategory", DataType::Text, false),
11045 ColumnSchema::new("typelem", DataType::BigInt, false),
11046 ColumnSchema::new("typarray", DataType::BigInt, false),
11047 ColumnSchema::new("typnamespace", DataType::BigInt, false),
11048 ];
11049 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
11052 (16, "bool", 1, "b", "B", 0, 1000),
11054 (17, "bytea", -1, "b", "U", 0, 1001),
11055 (18, "char", 1, "b", "S", 0, 1002),
11056 (19, "name", 64, "b", "S", 0, 1003),
11057 (20, "int8", 8, "b", "N", 0, 1016),
11058 (21, "int2", 2, "b", "N", 0, 1005),
11059 (23, "int4", 4, "b", "N", 0, 1007),
11060 (24, "regproc", 4, "b", "N", 0, 1008),
11061 (25, "text", -1, "b", "S", 0, 1009),
11062 (26, "oid", 4, "b", "N", 0, 1028),
11063 (114, "json", -1, "b", "U", 0, 199),
11064 (142, "xml", -1, "b", "U", 0, 143),
11065 (700, "float4", 4, "b", "N", 0, 1021),
11066 (701, "float8", 8, "b", "N", 0, 1022),
11067 (650, "cidr", -1, "b", "I", 0, 651),
11068 (869, "inet", -1, "b", "I", 0, 1041),
11069 (829, "macaddr", 6, "b", "U", 0, 1040),
11070 (1042, "bpchar", -1, "b", "S", 0, 1014),
11071 (1043, "varchar", -1, "b", "S", 0, 1015),
11072 (1082, "date", 4, "b", "D", 0, 1182),
11073 (1083, "time", 8, "b", "D", 0, 1183),
11074 (1114, "timestamp", 8, "b", "D", 0, 1115),
11075 (1184, "timestamptz", 8, "b", "D", 0, 1185),
11076 (1186, "interval", 16, "b", "T", 0, 1187),
11077 (1266, "timetz", 12, "b", "D", 0, 1270),
11078 (1700, "numeric", -1, "b", "N", 0, 1231),
11079 (790, "money", 8, "b", "N", 0, 791),
11080 (2950, "uuid", 16, "b", "U", 0, 2951),
11081 (3802, "jsonb", -1, "b", "U", 0, 3807),
11082 (3614, "tsvector", -1, "b", "U", 0, 3643),
11083 (3615, "tsquery", -1, "b", "U", 0, 3645),
11084 (3908, "tstzrange", -1, "r", "R", 0, 3909),
11086 (3910, "tsrange", -1, "r", "R", 0, 3911),
11087 (3904, "int4range", -1, "r", "R", 0, 3905),
11088 (3926, "int8range", -1, "r", "R", 0, 3927),
11089 (3906, "numrange", -1, "r", "R", 0, 3907),
11090 (3912, "daterange", -1, "r", "R", 0, 3913),
11091 ];
11092 let arrays: &[(i64, &str, i64)] = &[
11095 (1000, "_bool", 16),
11096 (1001, "_bytea", 17),
11097 (1002, "_char", 18),
11098 (1003, "_name", 19),
11099 (1016, "_int8", 20),
11100 (1005, "_int2", 21),
11101 (1007, "_int4", 23),
11102 (1008, "_regproc", 24),
11103 (1009, "_text", 25),
11104 (1028, "_oid", 26),
11105 (199, "_json", 114),
11106 (143, "_xml", 142),
11107 (1021, "_float4", 700),
11108 (1022, "_float8", 701),
11109 (651, "_cidr", 650),
11110 (1041, "_inet", 869),
11111 (1040, "_macaddr", 829),
11112 (1014, "_bpchar", 1042),
11113 (1015, "_varchar", 1043),
11114 (1182, "_date", 1082),
11115 (1183, "_time", 1083),
11116 (1115, "_timestamp", 1114),
11117 (1185, "_timestamptz", 1184),
11118 (1187, "_interval", 1186),
11119 (1270, "_timetz", 1266),
11120 (1231, "_numeric", 1700),
11121 (791, "_money", 790),
11122 (2951, "_uuid", 2950),
11123 (3807, "_jsonb", 3802),
11124 (3643, "_tsvector", 3614),
11125 (3645, "_tsquery", 3615),
11126 ];
11127 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
11128 for &(oid, name, len, ty, cat, elem, arr) in scalars {
11129 rows.push(Row::new(alloc::vec![
11130 Value::BigInt(oid),
11131 Value::Text(name.into()),
11132 Value::SmallInt(len),
11133 Value::Text(ty.into()),
11134 Value::Text(cat.into()),
11135 Value::BigInt(elem),
11136 Value::BigInt(arr),
11137 Value::BigInt(2200),
11138 ]));
11139 }
11140 for &(oid, name, elem) in arrays {
11141 rows.push(Row::new(alloc::vec![
11142 Value::BigInt(oid),
11143 Value::Text(name.into()),
11144 Value::SmallInt(-1),
11145 Value::Text("b".into()),
11146 Value::Text("A".into()),
11147 Value::BigInt(elem),
11148 Value::BigInt(0),
11149 Value::BigInt(2200),
11150 ]));
11151 }
11152 (schema, rows)
11153}
11154
11155fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11174 let schema = alloc::vec![
11175 ColumnSchema::new("tgname", DataType::Text, false),
11176 ColumnSchema::new("relname", DataType::Text, false),
11177 ColumnSchema::new("tgenabled", DataType::Text, false),
11178 ColumnSchema::new("timing", DataType::Text, false),
11179 ColumnSchema::new("events", DataType::Text, false),
11180 ColumnSchema::new("function", DataType::Text, false),
11181 ];
11182 let rows: Vec<Row> = cat
11183 .triggers()
11184 .iter()
11185 .map(|t| {
11186 Row::new(alloc::vec![
11187 Value::Text(t.name.clone()),
11188 Value::Text(t.table.clone()),
11189 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
11190 Value::Text(t.timing.clone()),
11191 Value::Text(t.events.join(" OR ")),
11192 Value::Text(t.function.clone()),
11193 ])
11194 })
11195 .collect();
11196 (schema, rows)
11197}
11198
11199fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11200 let schema = alloc::vec![
11201 ColumnSchema::new("oid", DataType::BigInt, false),
11202 ColumnSchema::new("proname", DataType::Text, false),
11203 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11204 ColumnSchema::new("prokind", DataType::Text, false),
11205 ColumnSchema::new("pronargs", DataType::Int, false),
11206 ColumnSchema::new("prorettype", DataType::BigInt, false),
11207 ];
11208 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11211 (1318, "length", "f", 1, 23),
11213 (871, "upper", "f", 1, 25),
11214 (870, "lower", "f", 1, 25),
11215 (936, "substring", "f", 3, 25),
11216 (937, "substring", "f", 2, 25),
11217 (3055, "btrim", "f", 1, 25),
11218 (885, "btrim", "f", 2, 25),
11219 (3056, "ltrim", "f", 1, 25),
11220 (875, "ltrim", "f", 2, 25),
11221 (3057, "rtrim", "f", 1, 25),
11222 (876, "rtrim", "f", 2, 25),
11223 (1397, "abs", "f", 1, 23),
11224 (1396, "abs", "f", 1, 20),
11225 (1606, "round", "f", 1, 1700),
11226 (1707, "round", "f", 2, 1700),
11227 (2308, "ceil", "f", 1, 701),
11228 (2309, "ceiling", "f", 1, 701),
11229 (2310, "floor", "f", 1, 701),
11230 (1376, "sqrt", "f", 1, 701),
11231 (1369, "ln", "f", 1, 701),
11232 (1373, "exp", "f", 1, 701),
11233 (1368, "power", "f", 2, 701),
11234 (2228, "random", "f", 0, 701),
11235 (1299, "now", "f", 0, 1184),
11237 (1274, "current_timestamp", "f", 0, 1184),
11238 (1140, "current_date", "f", 0, 1082),
11239 (2050, "current_time", "f", 0, 1083),
11240 (1158, "date_trunc", "f", 2, 1184),
11241 (1171, "date_part", "f", 2, 701),
11242 (1172, "age", "f", 1, 1186),
11243 (936, "to_char", "f", 2, 25),
11244 (861, "current_database", "f", 0, 19),
11246 (745, "current_user", "f", 0, 19),
11247 (745, "session_user", "f", 0, 19),
11248 (1402, "current_schema", "f", 0, 19),
11249 (3058, "concat", "f", -1, 25),
11251 (3059, "concat_ws", "f", -1, 25),
11252 (3539, "format", "f", -1, 25),
11253 (2877, "pg_typeof", "f", 1, 2206),
11255 (3198, "json_build_object", "f", -1, 114),
11257 (3199, "jsonb_build_object", "f", -1, 3802),
11258 (3271, "json_build_array", "f", -1, 114),
11259 (3272, "jsonb_build_array", "f", -1, 3802),
11260 (3253, "gen_random_uuid", "f", 0, 2950),
11262 (3252, "uuid_generate_v4", "f", 0, 2950),
11263 (2147, "count", "a", 0, 20),
11265 (2803, "count", "a", -1, 20),
11266 (2116, "max", "a", 1, 23),
11267 (2132, "min", "a", 1, 23),
11268 (2108, "sum", "a", 1, 20),
11269 (2100, "avg", "a", 1, 1700),
11270 (2517, "string_agg", "a", 2, 25),
11271 (2747, "array_agg", "a", 1, 1009),
11272 (2517, "bool_and", "a", 1, 16),
11273 (2518, "bool_or", "a", 1, 16),
11274 (2519, "every", "a", 1, 16),
11275 (3100, "row_number", "w", 0, 20),
11277 (3101, "rank", "w", 0, 20),
11278 (3102, "dense_rank", "w", 0, 20),
11279 (3103, "percent_rank", "w", 0, 701),
11280 (3104, "cume_dist", "w", 0, 701),
11281 (3105, "lag", "w", -1, 2283),
11282 (3106, "lead", "w", -1, 2283),
11283 (3107, "first_value", "w", 1, 2283),
11284 (3108, "last_value", "w", 1, 2283),
11285 (3109, "nth_value", "w", 2, 2283),
11286 ];
11287 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11288 for &(oid, name, kind, nargs, rettype) in funcs {
11289 rows.push(Row::new(alloc::vec![
11290 Value::BigInt(oid),
11291 Value::Text(name.into()),
11292 Value::BigInt(11),
11293 Value::Text(kind.into()),
11294 Value::Int(nargs),
11295 Value::BigInt(rettype),
11296 ]));
11297 }
11298 (schema, rows)
11299}
11300
11301fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11307 let schema = alloc::vec![
11308 ColumnSchema::new("user", DataType::Text, false),
11309 ColumnSchema::new("host", DataType::Text, false),
11310 ColumnSchema::new("select_priv", DataType::Text, false),
11311 ];
11312 let mut rows: Vec<Row> = Vec::new();
11313 rows.push(Row::new(alloc::vec![
11314 Value::Text("root".into()),
11315 Value::Text("localhost".into()),
11316 Value::Text("Y".into()),
11317 ]));
11318 for (name, _) in engine.users.iter() {
11319 if name != "root" {
11320 rows.push(Row::new(alloc::vec![
11321 Value::Text(name.to_string()),
11322 Value::Text("%".into()),
11323 Value::Text("Y".into()),
11324 ]));
11325 }
11326 }
11327 (schema, rows)
11328}
11329
11330fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11335 let schema = alloc::vec![
11336 ColumnSchema::new("host", DataType::Text, false),
11337 ColumnSchema::new("db", DataType::Text, false),
11338 ColumnSchema::new("user", DataType::Text, false),
11339 ColumnSchema::new("select_priv", DataType::Text, false),
11340 ];
11341 let rows = alloc::vec![Row::new(alloc::vec![
11342 Value::Text("localhost".into()),
11343 Value::Text("postgres".into()),
11344 Value::Text("root".into()),
11345 Value::Text("Y".into()),
11346 ])];
11347 (schema, rows)
11348}
11349
11350fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11363 let schema = alloc::vec![
11364 ColumnSchema::new("constraint_name", DataType::Text, false),
11365 ColumnSchema::new("table_name", DataType::Text, false),
11366 ColumnSchema::new("column_name", DataType::Text, false),
11367 ColumnSchema::new("ordinal_position", DataType::Int, false),
11368 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11369 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11370 ];
11371 let mut rows: Vec<Row> = Vec::new();
11372 for tname in cat.table_names() {
11373 let Some(t) = cat.get(&tname) else { continue };
11374 let cols = &t.schema().columns;
11375 let col_name_at = |pos: usize| -> String {
11376 cols.get(pos)
11377 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11378 };
11379 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11381 let conname = fk
11382 .name
11383 .clone()
11384 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11385 for (i, (&local, &parent)) in fk
11386 .local_columns
11387 .iter()
11388 .zip(fk.parent_columns.iter())
11389 .enumerate()
11390 {
11391 let parent_name = cat
11392 .get(&fk.parent_table)
11393 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11394 .unwrap_or_else(|| alloc::format!("col{parent}"));
11395 #[allow(clippy::cast_possible_wrap)]
11396 let ordinal = (i + 1) as i32;
11397 rows.push(Row::new(alloc::vec![
11398 Value::Text(conname.clone()),
11399 Value::Text(tname.clone()),
11400 Value::Text(col_name_at(local)),
11401 Value::Int(ordinal),
11402 Value::Text(fk.parent_table.clone()),
11403 Value::Text(parent_name),
11404 ]));
11405 }
11406 }
11407 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11409 let conname = if uc.is_primary_key {
11410 alloc::format!("{}_pkey", tname)
11411 } else {
11412 alloc::format!("{}_uniq{ci}", tname)
11413 };
11414 for (i, &local) in uc.columns.iter().enumerate() {
11415 #[allow(clippy::cast_possible_wrap)]
11416 let ordinal = (i + 1) as i32;
11417 rows.push(Row::new(alloc::vec![
11418 Value::Text(conname.clone()),
11419 Value::Text(tname.clone()),
11420 Value::Text(col_name_at(local)),
11421 Value::Int(ordinal),
11422 Value::Text(String::new()),
11423 Value::Text(String::new()),
11424 ]));
11425 }
11426 }
11427 }
11428 (schema, rows)
11429}
11430
11431fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11434 let schema = alloc::vec![
11435 ColumnSchema::new("constraint_name", DataType::Text, false),
11436 ColumnSchema::new("table_name", DataType::Text, false),
11437 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11438 ColumnSchema::new("update_rule", DataType::Text, false),
11439 ColumnSchema::new("delete_rule", DataType::Text, false),
11440 ];
11441 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11442 match a {
11443 spg_storage::FkAction::Cascade => "CASCADE",
11444 spg_storage::FkAction::SetNull => "SET NULL",
11445 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11446 spg_storage::FkAction::Restrict => "RESTRICT",
11447 spg_storage::FkAction::NoAction => "NO ACTION",
11448 }
11449 }
11450 let mut rows: Vec<Row> = Vec::new();
11451 for tname in cat.table_names() {
11452 let Some(t) = cat.get(&tname) else { continue };
11453 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11454 let conname = fk
11455 .name
11456 .clone()
11457 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11458 rows.push(Row::new(alloc::vec![
11459 Value::Text(conname),
11460 Value::Text(tname.clone()),
11461 Value::Text(fk.parent_table.clone()),
11462 Value::Text(rule_name(fk.on_update).into()),
11463 Value::Text(rule_name(fk.on_delete).into()),
11464 ]));
11465 }
11466 }
11467 (schema, rows)
11468}
11469
11470fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11474 let schema = alloc::vec![
11475 ColumnSchema::new("table_name", DataType::Text, false),
11476 ColumnSchema::new("index_name", DataType::Text, false),
11477 ColumnSchema::new("column_name", DataType::Text, false),
11478 ColumnSchema::new("seq_in_index", DataType::Int, false),
11479 ColumnSchema::new("non_unique", DataType::Int, false),
11480 ColumnSchema::new("index_type", DataType::Text, false),
11481 ];
11482 let mut rows: Vec<Row> = Vec::new();
11483 for tname in cat.table_names() {
11484 let Some(t) = cat.get(&tname) else { continue };
11485 for idx in t.indices() {
11486 let col = t
11487 .schema()
11488 .columns
11489 .get(idx.column_position)
11490 .map_or("?".into(), |c| c.name.clone());
11491 rows.push(Row::new(alloc::vec![
11492 Value::Text(tname.clone()),
11493 Value::Text(idx.name.clone()),
11494 Value::Text(col),
11495 Value::Int(1),
11496 Value::Int(i32::from(!idx.is_unique)),
11497 Value::Text("BTREE".into()),
11498 ]));
11499 }
11500 }
11501 (schema, rows)
11502}
11503
11504fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11508 let schema = alloc::vec![
11509 ColumnSchema::new("routine_name", DataType::Text, false),
11510 ColumnSchema::new("routine_type", DataType::Text, false),
11511 ColumnSchema::new("data_type", DataType::Text, false),
11512 ];
11513 (schema, Vec::new())
11514}
11515
11516fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11531 let schema = alloc::vec![
11532 ColumnSchema::new("conname", DataType::Text, false),
11533 ColumnSchema::new("contype", DataType::Text, false),
11534 ColumnSchema::new("conrelid", DataType::Text, false),
11535 ColumnSchema::new("confrelid", DataType::Text, false),
11536 ColumnSchema::new("conkey", DataType::Text, false),
11537 ColumnSchema::new("confkey", DataType::Text, false),
11538 ];
11539 let mut rows: Vec<Row> = Vec::new();
11540 for tname in cat.table_names() {
11541 let Some(t) = cat.get(&tname) else { continue };
11542 let cols = &t.schema().columns;
11543 let col_name_at = |pos: usize| -> String {
11544 cols.get(pos)
11545 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11546 };
11547 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11549 let kind = if uc.is_primary_key { "p" } else { "u" };
11550 let conname = if uc.is_primary_key {
11551 alloc::format!("{}_pkey", tname)
11552 } else {
11553 alloc::format!("{}_uniq{ci}", tname)
11554 };
11555 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11556 rows.push(Row::new(alloc::vec![
11557 Value::Text(conname),
11558 Value::Text(kind.into()),
11559 Value::Text(tname.clone()),
11560 Value::Text(String::new()),
11561 Value::Text(conkey.join(",")),
11562 Value::Text(String::new()),
11563 ]));
11564 }
11565 for idx in t.indices() {
11570 if !idx.is_unique {
11571 continue;
11572 }
11573 let is_primary = idx.name.ends_with("_pkey");
11574 let conname = idx.name.clone();
11575 let kind = if is_primary { "p" } else { "u" };
11576 let col_name = col_name_at(idx.column_position);
11577 let already = t
11580 .schema()
11581 .uniqueness_constraints
11582 .iter()
11583 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11584 if already {
11585 continue;
11586 }
11587 rows.push(Row::new(alloc::vec![
11588 Value::Text(conname),
11589 Value::Text(kind.into()),
11590 Value::Text(tname.clone()),
11591 Value::Text(String::new()),
11592 Value::Text(col_name),
11593 Value::Text(String::new()),
11594 ]));
11595 }
11596 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11598 let conname = fk
11599 .name
11600 .clone()
11601 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11602 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11603 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11606 fk.parent_columns
11607 .iter()
11608 .map(|&p| {
11609 parent
11610 .schema()
11611 .columns
11612 .get(p)
11613 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11614 })
11615 .collect()
11616 } else {
11617 fk.parent_columns
11618 .iter()
11619 .map(|p| alloc::format!("col{p}"))
11620 .collect()
11621 };
11622 rows.push(Row::new(alloc::vec![
11623 Value::Text(conname),
11624 Value::Text("f".into()),
11625 Value::Text(tname.clone()),
11626 Value::Text(fk.parent_table.clone()),
11627 Value::Text(conkey.join(",")),
11628 Value::Text(confkey.join(",")),
11629 ]));
11630 }
11631 }
11632 (schema, rows)
11633}
11634
11635fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11640 let schema = alloc::vec![
11641 ColumnSchema::new("oid", DataType::BigInt, false),
11642 ColumnSchema::new("datname", DataType::Text, false),
11643 ColumnSchema::new("datdba", DataType::BigInt, false),
11644 ColumnSchema::new("encoding", DataType::Int, false),
11645 ColumnSchema::new("datcollate", DataType::Text, false),
11646 ];
11647 let rows = alloc::vec![Row::new(alloc::vec![
11648 Value::BigInt(16384),
11649 Value::Text("postgres".into()),
11650 Value::BigInt(10),
11651 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11653 ])];
11654 (schema, rows)
11655}
11656
11657fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11662 let schema = alloc::vec![
11663 ColumnSchema::new("oid", DataType::BigInt, false),
11664 ColumnSchema::new("rolname", DataType::Text, false),
11665 ColumnSchema::new("rolsuper", DataType::Bool, false),
11666 ColumnSchema::new("rolinherit", DataType::Bool, false),
11667 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11668 ];
11669 let mut rows: Vec<Row> = Vec::new();
11670 let oid: i64 = 10;
11671 for (i, (name, _)) in engine.users.iter().enumerate() {
11672 rows.push(Row::new(alloc::vec![
11673 Value::BigInt(oid + (i as i64) + 1),
11674 Value::Text(name.to_string()),
11675 Value::Bool(false),
11676 Value::Bool(true),
11677 Value::Bool(true),
11678 ]));
11679 }
11680 if !rows
11683 .iter()
11684 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11685 {
11686 rows.insert(
11687 0,
11688 Row::new(alloc::vec![
11689 Value::BigInt(10),
11690 Value::Text("postgres".into()),
11691 Value::Bool(true),
11692 Value::Bool(true),
11693 Value::Bool(true),
11694 ]),
11695 );
11696 }
11697 (schema, rows)
11698}
11699
11700fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11709 let schema = alloc::vec![
11710 ColumnSchema::new("oid", DataType::BigInt, false),
11711 ColumnSchema::new("extname", DataType::Text, false),
11712 ColumnSchema::new("extversion", DataType::Text, false),
11713 ColumnSchema::new("extnamespace", DataType::Text, false),
11714 ];
11715 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11716 let rows = exts
11717 .iter()
11718 .enumerate()
11719 .map(|(i, (name, ver))| {
11720 Row::new(alloc::vec![
11721 Value::BigInt(16384 + i as i64),
11722 Value::Text((*name).into()),
11723 Value::Text((*ver).into()),
11724 Value::Text("pg_catalog".into()),
11725 ])
11726 })
11727 .collect();
11728 (schema, rows)
11729}
11730
11731fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11732 let schema = alloc::vec![
11733 ColumnSchema::new("schemaname", DataType::Text, false),
11734 ColumnSchema::new("viewname", DataType::Text, false),
11735 ColumnSchema::new("definition", DataType::Text, false),
11736 ];
11737 let mut rows: Vec<Row> = Vec::new();
11738 for (name, def) in cat.views() {
11739 rows.push(Row::new(alloc::vec![
11740 Value::Text("public".into()),
11741 Value::Text(name.clone()),
11742 Value::Text(def.body.clone()),
11743 ]));
11744 }
11745 (schema, rows)
11746}
11747
11748fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11754 let schema = alloc::vec![
11755 ColumnSchema::new("name", DataType::Text, false),
11756 ColumnSchema::new("setting", DataType::Text, false),
11757 ColumnSchema::new("category", DataType::Text, false),
11758 ];
11759 let mut rows: Vec<Row> = Vec::new();
11760 let defaults: &[(&str, &str, &str)] = &[
11762 ("server_version", "16.0 (spg)", "Preset Options"),
11763 ("server_encoding", "UTF8", "Client Connection Defaults"),
11764 ("client_encoding", "UTF8", "Client Connection Defaults"),
11765 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11766 ("TimeZone", "UTC", "Client Connection Defaults"),
11767 ("standard_conforming_strings", "on", "Compatibility"),
11768 ("integer_datetimes", "on", "Compatibility"),
11769 ("max_connections", "100", "Connections and Authentication"),
11770 ];
11771 for &(name, val, cat) in defaults {
11772 rows.push(Row::new(alloc::vec![
11773 Value::Text(name.into()),
11774 Value::Text(val.into()),
11775 Value::Text(cat.into()),
11776 ]));
11777 }
11778 for (k, v) in &engine.session_params {
11780 if !defaults
11781 .iter()
11782 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11783 {
11784 rows.push(Row::new(alloc::vec![
11785 Value::Text(k.clone()),
11786 Value::Text(v.clone()),
11787 Value::Text("Session".into()),
11788 ]));
11789 }
11790 }
11791 (schema, rows)
11792}
11793
11794fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11805 let schema = alloc::vec![
11806 ColumnSchema::new("schemaname", DataType::Text, false),
11807 ColumnSchema::new("tablename", DataType::Text, false),
11808 ColumnSchema::new("indexname", DataType::Text, false),
11809 ColumnSchema::new("indexdef", DataType::Text, false),
11810 ];
11811 let mut rows: Vec<Row> = Vec::new();
11812 for tname in cat.table_names() {
11813 let Some(t) = cat.get(&tname) else { continue };
11814 for idx in t.indices() {
11815 let col_name = t
11816 .schema()
11817 .columns
11818 .get(idx.column_position)
11819 .map_or("?".into(), |c| c.name.clone());
11820 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11821 let indexdef = alloc::format!(
11822 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11823 idx.name,
11824 tname,
11825 col_name
11826 );
11827 rows.push(Row::new(alloc::vec![
11828 Value::Text("public".into()),
11829 Value::Text(tname.clone()),
11830 Value::Text(idx.name.clone()),
11831 Value::Text(indexdef),
11832 ]));
11833 }
11834 }
11835 (schema, rows)
11836}
11837
11838fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11850 let schema = alloc::vec![
11851 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11852 ColumnSchema::new("indrelid", DataType::BigInt, false),
11853 ColumnSchema::new("indnatts", DataType::Int, false),
11854 ColumnSchema::new("indisunique", DataType::Bool, false),
11855 ColumnSchema::new("indisprimary", DataType::Bool, false),
11856 ];
11857 let mut rows: Vec<Row> = Vec::new();
11858 let mut idx_oid: i64 = 100_000;
11859 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11860 let Some(t) = cat.get(tname) else { continue };
11861 for idx in t.indices() {
11862 idx_oid += 1;
11863 #[allow(clippy::cast_possible_wrap)]
11864 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11865 let is_primary = idx.name.ends_with("_pkey");
11868 rows.push(Row::new(alloc::vec![
11869 Value::BigInt(idx_oid),
11870 Value::BigInt((table_idx + 1) as i64),
11871 Value::Int(nattrs),
11872 Value::Bool(idx.is_unique),
11873 Value::Bool(is_primary),
11874 ]));
11875 }
11876 }
11877 (schema, rows)
11878}
11879
11880fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11885 let schema = alloc::vec![
11886 ColumnSchema::new("oid", DataType::BigInt, false),
11887 ColumnSchema::new("nspname", DataType::Text, false),
11888 ColumnSchema::new("nspowner", DataType::BigInt, false),
11889 ];
11890 let rows = alloc::vec![
11891 Row::new(alloc::vec![
11892 Value::BigInt(11),
11893 Value::Text("pg_catalog".into()),
11894 Value::BigInt(10),
11895 ]),
11896 Row::new(alloc::vec![
11897 Value::BigInt(2200),
11898 Value::Text("public".into()),
11899 Value::BigInt(10),
11900 ]),
11901 Row::new(alloc::vec![
11902 Value::BigInt(13000),
11903 Value::Text("information_schema".into()),
11904 Value::BigInt(10),
11905 ]),
11906 ];
11907 (schema, rows)
11908}
11909
11910fn materialise_meta_view(
11913 catalog: &mut Catalog,
11914 name: &str,
11915 columns: Vec<ColumnSchema>,
11916 rows: Vec<Row>,
11917) -> Result<(), EngineError> {
11918 let schema = TableSchema::new(name.to_string(), columns);
11919 catalog.create_table(schema).map_err(EngineError::Storage)?;
11920 let table = catalog
11921 .get_mut(name)
11922 .expect("just-created meta view must exist");
11923 for row in rows {
11924 table.insert(row).map_err(EngineError::Storage)?;
11925 }
11926 Ok(())
11927}
11928
11929fn collect_view_refs(
11942 tref: &spg_sql::ast::TableRef,
11943 cat: &spg_storage::Catalog,
11944 into: &mut Vec<String>,
11945) {
11946 if cat.views().contains_key(&tref.name)
11947 && cat.get(&tref.name).is_none()
11948 && !into.iter().any(|n| n == &tref.name)
11949 {
11950 into.push(tref.name.clone());
11951 }
11952}
11953
11954fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11955 fn is_meta(name: &str) -> bool {
11956 name.starts_with("__spg_info_")
11957 || name.starts_with("__spg_pg_")
11958 || name.starts_with("__spg_mysql_")
11959 }
11960 if let Some(from) = &stmt.from {
11961 if is_meta(&from.primary.name) {
11962 return true;
11963 }
11964 for j in &from.joins {
11965 if is_meta(&j.table.name) {
11966 return true;
11967 }
11968 }
11969 }
11970 for cte in &stmt.ctes {
11971 if select_references_meta_view(&cte.body) {
11972 return true;
11973 }
11974 }
11975 false
11976}
11977
11978fn collect_meta_view_names(
11983 stmt: &SelectStatement,
11984 into: &mut alloc::collections::BTreeSet<String>,
11985) {
11986 fn is_meta(name: &str) -> bool {
11987 name.starts_with("__spg_info_")
11988 || name.starts_with("__spg_pg_")
11989 || name.starts_with("__spg_mysql_")
11990 }
11991 if let Some(from) = &stmt.from {
11992 if is_meta(&from.primary.name) {
11993 into.insert(from.primary.name.clone());
11994 }
11995 for j in &from.joins {
11996 if is_meta(&j.table.name) {
11997 into.insert(j.table.name.clone());
11998 }
11999 }
12000 }
12001 for cte in &stmt.ctes {
12002 collect_meta_view_names(&cte.body, into);
12003 }
12004}
12005
12006fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
12007 let mut out = columns.to_vec();
12008 for (col_idx, col) in out.iter_mut().enumerate() {
12009 if col.ty != DataType::Text {
12010 continue;
12011 }
12012 let mut inferred: Option<DataType> = None;
12013 let mut all_null = true;
12014 for row in rows {
12015 let Some(v) = row.values.get(col_idx) else {
12016 continue;
12017 };
12018 let ty = match v {
12019 Value::Null => continue,
12020 Value::SmallInt(_) => DataType::SmallInt,
12021 Value::Int(_) => DataType::Int,
12022 Value::BigInt(_) => DataType::BigInt,
12023 Value::Float(_) => DataType::Float,
12024 Value::Bool(_) => DataType::Bool,
12025 Value::Vector(_) => DataType::Vector {
12026 dim: 0,
12027 encoding: VecEncoding::F32,
12028 },
12029 _ => DataType::Text,
12030 };
12031 all_null = false;
12032 inferred = Some(match inferred {
12033 None => ty,
12034 Some(prev) if prev == ty => prev,
12035 Some(_) => DataType::Text,
12036 });
12037 }
12038 if let Some(t) = inferred {
12039 col.ty = t;
12040 col.nullable = true;
12041 } else if all_null {
12042 col.nullable = true;
12043 }
12044 }
12045 out
12046}
12047
12048#[allow(clippy::too_many_lines, clippy::format_push_string)]
12053fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
12070 use alloc::collections::BTreeSet;
12071 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
12072 let mut out: Vec<String> = Vec::new();
12073 let cat = engine.active_catalog();
12074 let Some(from) = &stmt.from else {
12078 return out;
12079 };
12080 let mut tables: Vec<String> = Vec::new();
12081 tables.push(from.primary.name.clone());
12082 for j in &from.joins {
12083 tables.push(j.table.name.clone());
12084 }
12085 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
12088 if let Some(w) = &stmt.where_ {
12089 collect_column_refs(w, &mut col_refs);
12090 }
12091 for j in &from.joins {
12092 if let Some(on) = &j.on {
12093 collect_column_refs(on, &mut col_refs);
12094 }
12095 }
12096 for cn in &col_refs {
12097 let owner: Option<String> = if let Some(q) = &cn.qualifier {
12100 tables.iter().find(|t| t == &q).cloned()
12101 } else {
12102 tables.iter().find_map(|t| {
12103 cat.get(t).and_then(|tbl| {
12104 if tbl.schema().column_position(&cn.name).is_some() {
12105 Some(t.clone())
12106 } else {
12107 None
12108 }
12109 })
12110 })
12111 };
12112 let Some(owner) = owner else {
12113 continue;
12114 };
12115 let Some(tbl) = cat.get(&owner) else {
12116 continue;
12117 };
12118 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
12119 continue;
12120 };
12121 let already_indexed = tbl.indices().iter().any(|i| {
12124 matches!(i.kind, spg_storage::IndexKind::BTree(_))
12125 && i.column_position == col_pos
12126 && i.expression.is_none()
12127 && i.partial_predicate.is_none()
12128 });
12129 if already_indexed {
12130 continue;
12131 }
12132 if seen.insert((owner.clone(), cn.name.clone())) {
12133 out.push(alloc::format!(
12134 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
12135 owner,
12136 cn.name,
12137 owner,
12138 cn.name
12139 ));
12140 }
12141 }
12142 out
12143}
12144
12145fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
12148 match expr {
12149 Expr::Column(cn) => out.push(cn.clone()),
12150 Expr::FunctionCall { args, .. } => {
12151 for a in args {
12152 collect_column_refs(a, out);
12153 }
12154 }
12155 Expr::Binary { lhs, rhs, .. } => {
12156 collect_column_refs(lhs, out);
12157 collect_column_refs(rhs, out);
12158 }
12159 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
12160 _ => {}
12161 }
12162}
12163
12164fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
12165 let catalog = engine.active_catalog();
12166 let cold_ids = catalog.cold_segment_ids_global();
12167 let any_cold = !cold_ids.is_empty();
12168 let cold_ids_repr = if any_cold {
12169 let mut s = alloc::string::String::from("[");
12170 for (i, id) in cold_ids.iter().enumerate() {
12171 if i > 0 {
12172 s.push(',');
12173 }
12174 s.push_str(&alloc::format!("{id}"));
12175 }
12176 s.push(']');
12177 s
12178 } else {
12179 alloc::string::String::new()
12180 };
12181 for (idx, line) in lines.iter_mut().enumerate() {
12182 let trimmed = line.trim_start();
12183 let is_top_level = idx == 0;
12184 if is_top_level {
12185 line.push_str(&alloc::format!(" (rows={total_rows})"));
12186 continue;
12187 }
12188 if let Some(rest) = trimmed.strip_prefix("From: ") {
12189 let (name, scan_kind) = match rest.split_once(" [") {
12190 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12191 None => (rest.trim(), ""),
12192 };
12193 let bare = name.split_whitespace().next().unwrap_or(name);
12194 let hot = catalog.get(bare).map(|t| t.rows().len());
12195 let annot = match (hot, scan_kind) {
12200 (Some(h), "full scan") => {
12201 let mut s = alloc::format!(" (hot_rows={h}");
12202 if any_cold {
12203 s.push_str(&alloc::format!(
12204 ", cold_tier=present, cold_segments={cold_ids_repr}"
12205 ));
12206 }
12207 s.push(')');
12208 s
12209 }
12210 (Some(h), "index seek") => {
12211 let mut s = alloc::format!(" (hot_rows≤{h}");
12212 if any_cold {
12213 s.push_str(&alloc::format!(
12214 ", cold_tier=present, cold_segments={cold_ids_repr}"
12215 ));
12216 }
12217 s.push(')');
12218 s
12219 }
12220 _ => " (rows=—)".to_string(),
12221 };
12222 line.push_str(&annot);
12223 continue;
12224 }
12225 line.push_str(" (rows=—)");
12227 }
12228}
12229
12230fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
12231 let pad = " ".repeat(depth);
12232 let top = if !stmt.ctes.is_empty() {
12234 if stmt.ctes.iter().any(|c| c.recursive) {
12235 "CTEScan (WITH RECURSIVE)"
12236 } else {
12237 "CTEScan (WITH)"
12238 }
12239 } else if !stmt.unions.is_empty() {
12240 "UnionScan"
12241 } else if select_has_window(stmt) {
12242 "WindowAgg"
12243 } else if aggregate::uses_aggregate(stmt) {
12244 "Aggregate"
12245 } else if stmt.distinct {
12246 "Distinct"
12247 } else if stmt.from.is_some() {
12248 "TableScan"
12249 } else {
12250 "Result"
12251 };
12252 out.push(alloc::format!("{pad}{top}"));
12253 let child = " ".repeat(depth + 1);
12254 for cte in &stmt.ctes {
12256 let head = if cte.recursive {
12257 alloc::format!("{child}CTE (recursive): {}", cte.name)
12258 } else {
12259 alloc::format!("{child}CTE: {}", cte.name)
12260 };
12261 out.push(head);
12262 explain_select(&cte.body, engine, depth + 2, out);
12263 }
12264 if let Some(from) = &stmt.from {
12266 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12267 if let Some(alias) = &from.primary.alias {
12268 tag.push_str(&alloc::format!(" AS {alias}"));
12269 }
12270 if let Some(w) = &stmt.where_
12273 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12274 {
12275 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12276 let cols = &table.schema().columns;
12277 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12278 tag.push_str(" [index seek]");
12279 } else {
12280 tag.push_str(" [full scan]");
12281 }
12282 } else {
12283 tag.push_str(" [full scan]");
12284 }
12285 out.push(tag);
12286 for j in &from.joins {
12287 let kind = match j.kind {
12288 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12289 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12290 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12291 };
12292 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12293 if let Some(alias) = &j.table.alias {
12294 s.push_str(&alloc::format!(" AS {alias}"));
12295 }
12296 if j.on.is_some() {
12297 s.push_str(" (ON …)");
12298 }
12299 out.push(s);
12300 }
12301 }
12302 if let Some(w) = &stmt.where_ {
12304 let mut s = alloc::format!("{child}Filter: {w}");
12305 if expr_has_subquery(w) {
12306 s.push_str(" [subquery]");
12307 }
12308 out.push(s);
12309 }
12310 if let Some(gs) = &stmt.group_by {
12311 let mut parts = Vec::new();
12312 for g in gs {
12313 parts.push(alloc::format!("{g}"));
12314 }
12315 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12316 }
12317 if let Some(h) = &stmt.having {
12318 out.push(alloc::format!("{child}Having: {h}"));
12319 }
12320 for o in &stmt.order_by {
12321 let dir = if o.desc { "DESC" } else { "ASC" };
12322 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12323 }
12324 if let Some(lim) = stmt.limit {
12325 out.push(alloc::format!("{child}Limit: {lim}"));
12326 }
12327 if let Some(off) = stmt.offset {
12328 out.push(alloc::format!("{child}Offset: {off}"));
12329 }
12330 if stmt
12332 .items
12333 .iter()
12334 .any(|it| matches!(it, SelectItem::Wildcard))
12335 {
12336 out.push(alloc::format!("{child}Project: *"));
12337 } else {
12338 out.push(alloc::format!(
12339 "{child}Project: {} item(s)",
12340 stmt.items.len()
12341 ));
12342 }
12343 for (kind, peer) in &stmt.unions {
12345 let label = match kind {
12346 UnionKind::All => "UNION ALL",
12347 UnionKind::Distinct => "UNION",
12348 };
12349 out.push(alloc::format!("{child}{label}"));
12350 explain_select(peer, engine, depth + 2, out);
12351 }
12352}
12353
12354fn is_correlation_error(e: &EngineError) -> bool {
12359 matches!(
12360 e,
12361 EngineError::Eval(
12362 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12363 )
12364 )
12365}
12366
12367struct JoinedPeer<'a> {
12378 eager_rows: Option<Vec<Row>>,
12379 cols: Vec<ColumnSchema>,
12380 alias: String,
12381 kind: JoinKind,
12382 on: Option<&'a Expr>,
12383 lateral: Option<&'a SelectStatement>,
12384}
12385
12386fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12393 match expr {
12394 Expr::Column(c) => c.name.clone(),
12396 Expr::FunctionCall { name, .. } => name.clone(),
12399 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12401 _ => alloc::format!("column{}", idx + 1),
12403 }
12404}
12405
12406fn substitute_outer_columns_multi(
12413 stmt: &mut SelectStatement,
12414 outer_row: &Row,
12415 outer_schema: &[ColumnSchema],
12416) {
12417 substitute_outer_in_select(stmt, outer_row, outer_schema);
12418}
12419
12420fn substitute_outer_in_select(
12421 stmt: &mut SelectStatement,
12422 outer_row: &Row,
12423 outer_schema: &[ColumnSchema],
12424) {
12425 for item in &mut stmt.items {
12426 if let SelectItem::Expr { expr, .. } = item {
12427 substitute_outer_in_expr(expr, outer_row, outer_schema);
12428 }
12429 }
12430 if let Some(w) = &mut stmt.where_ {
12431 substitute_outer_in_expr(w, outer_row, outer_schema);
12432 }
12433 if let Some(gs) = &mut stmt.group_by {
12434 for g in gs {
12435 substitute_outer_in_expr(g, outer_row, outer_schema);
12436 }
12437 }
12438 if let Some(h) = &mut stmt.having {
12439 substitute_outer_in_expr(h, outer_row, outer_schema);
12440 }
12441 for o in &mut stmt.order_by {
12442 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12443 }
12444 for (_, peer) in &mut stmt.unions {
12445 substitute_outer_in_select(peer, outer_row, outer_schema);
12446 }
12447}
12448
12449fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12450 if let Expr::Column(c) = e
12451 && let Some(qual) = &c.qualifier
12452 {
12453 let composite = alloc::format!("{qual}.{}", c.name);
12454 if let Some(idx) = outer_schema
12455 .iter()
12456 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12457 {
12458 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12459 if let Ok(lit) = value_to_literal_expr(v) {
12460 *e = lit;
12461 return;
12462 }
12463 }
12464 }
12465 match e {
12466 Expr::Binary { lhs, rhs, .. } => {
12467 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12468 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12469 }
12470 Expr::Unary { expr: inner, .. } => {
12471 substitute_outer_in_expr(inner, outer_row, outer_schema);
12472 }
12473 Expr::FunctionCall { args, .. } => {
12474 for a in args {
12475 substitute_outer_in_expr(a, outer_row, outer_schema);
12476 }
12477 }
12478 Expr::Cast { expr: inner, .. } => {
12479 substitute_outer_in_expr(inner, outer_row, outer_schema);
12480 }
12481 Expr::Case {
12482 operand,
12483 branches,
12484 else_branch,
12485 } => {
12486 if let Some(op) = operand {
12487 substitute_outer_in_expr(op, outer_row, outer_schema);
12488 }
12489 for (cond, val) in branches {
12490 substitute_outer_in_expr(cond, outer_row, outer_schema);
12491 substitute_outer_in_expr(val, outer_row, outer_schema);
12492 }
12493 if let Some(e) = else_branch {
12494 substitute_outer_in_expr(e, outer_row, outer_schema);
12495 }
12496 }
12497 _ => {}
12498 }
12499}
12500
12501fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12502 let outer_alias = ctx.table_alias.unwrap_or("");
12509 substitute_in_select(stmt, row, ctx, outer_alias);
12510}
12511
12512fn substitute_in_select(
12513 stmt: &mut SelectStatement,
12514 row: &Row,
12515 ctx: &EvalContext<'_>,
12516 outer_alias: &str,
12517) {
12518 for item in &mut stmt.items {
12519 if let SelectItem::Expr { expr, .. } = item {
12520 substitute_in_expr(expr, row, ctx, outer_alias);
12521 }
12522 }
12523 if let Some(w) = &mut stmt.where_ {
12524 substitute_in_expr(w, row, ctx, outer_alias);
12525 }
12526 if let Some(gs) = &mut stmt.group_by {
12527 for g in gs {
12528 substitute_in_expr(g, row, ctx, outer_alias);
12529 }
12530 }
12531 if let Some(h) = &mut stmt.having {
12532 substitute_in_expr(h, row, ctx, outer_alias);
12533 }
12534 for o in &mut stmt.order_by {
12535 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12536 }
12537 for (_, peer) in &mut stmt.unions {
12538 substitute_in_select(peer, row, ctx, outer_alias);
12539 }
12540}
12541
12542fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12543 if let Expr::Column(c) = e
12544 && let Some(qual) = &c.qualifier
12545 {
12546 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
12550 ctx.columns
12551 .iter()
12552 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12553 } else {
12554 None
12555 }
12556 .or_else(|| {
12557 let composite = alloc::format!("{qual}.{name}", name = c.name);
12558 ctx.columns
12559 .iter()
12560 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12561 });
12562 if let Some(idx) = idx {
12563 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12564 if let Ok(lit) = value_to_literal_expr(v) {
12565 *e = lit;
12566 return;
12567 }
12568 }
12569 }
12570 match e {
12571 Expr::AggregateOrdered { call, order_by, .. } => {
12572 substitute_in_expr(call, row, ctx, outer_alias);
12573 for o in order_by.iter_mut() {
12574 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12575 }
12576 }
12577 Expr::Binary { lhs, rhs, .. } => {
12578 substitute_in_expr(lhs, row, ctx, outer_alias);
12579 substitute_in_expr(rhs, row, ctx, outer_alias);
12580 }
12581 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12582 substitute_in_expr(expr, row, ctx, outer_alias);
12583 }
12584 Expr::Like { expr, pattern, .. } => {
12585 substitute_in_expr(expr, row, ctx, outer_alias);
12586 substitute_in_expr(pattern, row, ctx, outer_alias);
12587 }
12588 Expr::FunctionCall { args, .. } => {
12589 for a in args {
12590 substitute_in_expr(a, row, ctx, outer_alias);
12591 }
12592 }
12593 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12594 Expr::WindowFunction {
12595 args,
12596 partition_by,
12597 order_by,
12598 ..
12599 } => {
12600 for a in args {
12601 substitute_in_expr(a, row, ctx, outer_alias);
12602 }
12603 for p in partition_by {
12604 substitute_in_expr(p, row, ctx, outer_alias);
12605 }
12606 for (o, _, _) in order_by {
12607 substitute_in_expr(o, row, ctx, outer_alias);
12608 }
12609 }
12610 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12611 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12612 substitute_in_select(subquery, row, ctx, outer_alias);
12613 }
12614 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12615 Expr::Array(items) => {
12616 for elem in items {
12617 substitute_in_expr(elem, row, ctx, outer_alias);
12618 }
12619 }
12620 Expr::ArraySubscript { target, index } => {
12621 substitute_in_expr(target, row, ctx, outer_alias);
12622 substitute_in_expr(index, row, ctx, outer_alias);
12623 }
12624 Expr::AnyAll { expr, array, .. } => {
12625 substitute_in_expr(expr, row, ctx, outer_alias);
12626 substitute_in_expr(array, row, ctx, outer_alias);
12627 }
12628 Expr::Case {
12629 operand,
12630 branches,
12631 else_branch,
12632 } => {
12633 if let Some(o) = operand {
12634 substitute_in_expr(o, row, ctx, outer_alias);
12635 }
12636 for (w, t) in branches {
12637 substitute_in_expr(w, row, ctx, outer_alias);
12638 substitute_in_expr(t, row, ctx, outer_alias);
12639 }
12640 if let Some(e) = else_branch {
12641 substitute_in_expr(e, row, ctx, outer_alias);
12642 }
12643 }
12644 }
12645}
12646
12647fn encode_row_key(row: &Row) -> Vec<u8> {
12651 let mut out = Vec::new();
12652 for v in &row.values {
12653 let s = alloc::format!("{v:?}|");
12654 out.extend_from_slice(s.as_bytes());
12655 }
12656 out
12657}
12658
12659fn select_has_window(stmt: &SelectStatement) -> bool {
12660 for item in &stmt.items {
12661 if let SelectItem::Expr { expr, .. } = item
12662 && expr_has_window(expr)
12663 {
12664 return true;
12665 }
12666 }
12667 false
12668}
12669
12670fn expr_has_window(e: &Expr) -> bool {
12671 match e {
12672 Expr::WindowFunction { .. } => true,
12673 Expr::AggregateOrdered { call, order_by, .. } => {
12674 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
12675 }
12676 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12677 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12678 expr_has_window(expr)
12679 }
12680 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12681 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12682 Expr::Extract { source, .. } => expr_has_window(source),
12683 Expr::ScalarSubquery(_)
12684 | Expr::Exists { .. }
12685 | Expr::InSubquery { .. }
12686 | Expr::Literal(_)
12687 | Expr::Placeholder(_)
12688 | Expr::Column(_) => false,
12689 Expr::Array(items) => items.iter().any(expr_has_window),
12690 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12691 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12692 Expr::Case {
12693 operand,
12694 branches,
12695 else_branch,
12696 } => {
12697 operand.as_deref().is_some_and(expr_has_window)
12698 || branches
12699 .iter()
12700 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12701 || else_branch.as_deref().is_some_and(expr_has_window)
12702 }
12703 }
12704}
12705
12706fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12707 if let Expr::WindowFunction { .. } = e {
12708 if !out.iter().any(|x| x == e) {
12713 out.push(e.clone());
12714 }
12715 return;
12716 }
12717 match e {
12718 Expr::WindowFunction { .. } => unreachable!(),
12720 Expr::Binary { lhs, rhs, .. } => {
12721 collect_window_nodes(lhs, out);
12722 collect_window_nodes(rhs, out);
12723 }
12724 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12725 collect_window_nodes(expr, out);
12726 }
12727 Expr::FunctionCall { args, .. } => {
12728 for a in args {
12729 collect_window_nodes(a, out);
12730 }
12731 }
12732 Expr::Like { expr, pattern, .. } => {
12733 collect_window_nodes(expr, out);
12734 collect_window_nodes(pattern, out);
12735 }
12736 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12737 _ => {}
12738 }
12739}
12740
12741fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12742 if let Expr::WindowFunction { .. } = e
12743 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12744 {
12745 *e = Expr::Column(spg_sql::ast::ColumnName {
12746 qualifier: None,
12747 name: alloc::format!("__win_{idx}"),
12748 });
12749 return;
12750 }
12751 match e {
12752 Expr::Binary { lhs, rhs, .. } => {
12753 rewrite_window_to_columns(lhs, window_nodes);
12754 rewrite_window_to_columns(rhs, window_nodes);
12755 }
12756 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12757 rewrite_window_to_columns(expr, window_nodes);
12758 }
12759 Expr::FunctionCall { args, .. } => {
12760 for a in args {
12761 rewrite_window_to_columns(a, window_nodes);
12762 }
12763 }
12764 Expr::Like { expr, pattern, .. } => {
12765 rewrite_window_to_columns(expr, window_nodes);
12766 rewrite_window_to_columns(pattern, window_nodes);
12767 }
12768 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12769 _ => {}
12770 }
12771}
12772
12773fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12777 for (x, y) in a.iter().zip(b.iter()) {
12778 let c = value_cmp(x, y);
12779 if c != core::cmp::Ordering::Equal {
12780 return c;
12781 }
12782 }
12783 a.len().cmp(&b.len())
12784}
12785
12786fn order_key_cmp(
12787 a: &[(Value, bool, Option<bool>)],
12788 b: &[(Value, bool, Option<bool>)],
12789) -> core::cmp::Ordering {
12790 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
12793 let c = order_by_value_cmp(*desc, *nf, va, vb);
12794 if c != core::cmp::Ordering::Equal {
12795 return c;
12796 }
12797 }
12798 a.len().cmp(&b.len())
12799}
12800
12801const fn value_is_integer(v: &Value) -> bool {
12807 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12808}
12809
12810const fn value_to_i64(v: &Value) -> i64 {
12814 match v {
12815 Value::SmallInt(n) => *n as i64,
12816 Value::Int(n) => *n as i64,
12817 Value::BigInt(n) => *n,
12818 _ => panic!("value_to_i64 called on non-integer Value"),
12819 }
12820}
12821
12822fn generate_series_integers(
12828 start: i64,
12829 stop: i64,
12830 step: i64,
12831 cancel: &CancelToken<'_>,
12832) -> Result<alloc::vec::Vec<Row>, EngineError> {
12833 if step == 0 {
12834 return Err(EngineError::Unsupported(
12835 "generate_series(): step argument cannot be zero".into(),
12836 ));
12837 }
12838 let mut out = alloc::vec::Vec::new();
12839 let mut cur = start;
12840 const MAX_ROWS: usize = 10_000_000;
12844 loop {
12845 cancel.check()?;
12846 if step > 0 && cur > stop {
12847 break;
12848 }
12849 if step < 0 && cur < stop {
12850 break;
12851 }
12852 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12853 if out.len() > MAX_ROWS {
12854 return Err(EngineError::Unsupported(alloc::format!(
12855 "generate_series(): exceeded {MAX_ROWS} rows; \
12856 narrow start/stop or use a larger step"
12857 )));
12858 }
12859 cur = match cur.checked_add(step) {
12860 Some(n) => n,
12861 None => break,
12862 };
12863 }
12864 Ok(out)
12865}
12866
12867fn generate_series_timestamps(
12872 start: i64,
12873 stop: i64,
12874 step: Value,
12875 cancel: &CancelToken<'_>,
12876) -> Result<alloc::vec::Vec<Row>, EngineError> {
12877 let (months, micros) = match &step {
12878 Value::Interval { months, micros } => (*months, *micros),
12879 _ => unreachable!("caller guards step.is_interval"),
12880 };
12881 if months == 0 && micros == 0 {
12882 return Err(EngineError::Unsupported(
12883 "generate_series(): INTERVAL step cannot be zero".into(),
12884 ));
12885 }
12886 let ascending = months > 0 || micros > 0;
12887 let mut out = alloc::vec::Vec::new();
12888 let mut cur = Value::Timestamp(start);
12889 const MAX_ROWS: usize = 10_000_000;
12890 loop {
12891 cancel.check()?;
12892 let cur_t = match cur {
12893 Value::Timestamp(t) => t,
12894 _ => unreachable!("loop invariant: cur is Timestamp"),
12895 };
12896 if ascending && cur_t > stop {
12897 break;
12898 }
12899 if !ascending && cur_t < stop {
12900 break;
12901 }
12902 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12903 if out.len() > MAX_ROWS {
12904 return Err(EngineError::Unsupported(alloc::format!(
12905 "generate_series(): exceeded {MAX_ROWS} rows; \
12906 narrow start/stop or use a larger step"
12907 )));
12908 }
12909 let next = eval::apply_binary_interval(
12910 spg_sql::ast::BinOp::Add,
12911 &cur,
12912 &Value::Interval { months, micros },
12913 )
12914 .map_err(EngineError::Eval)?;
12915 cur = match next {
12916 Some(v) => v,
12917 None => break,
12918 };
12919 }
12920 Ok(out)
12921}
12922
12923#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
12929 desc: bool,
12930 nulls_first: Option<bool>,
12931 a: &Value,
12932 b: &Value,
12933) -> core::cmp::Ordering {
12934 use core::cmp::Ordering;
12935 let nf = nulls_first.unwrap_or(desc);
12936 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
12937 (true, true) => Ordering::Equal,
12938 (true, false) => {
12939 if nf {
12940 Ordering::Less
12941 } else {
12942 Ordering::Greater
12943 }
12944 }
12945 (false, true) => {
12946 if nf {
12947 Ordering::Greater
12948 } else {
12949 Ordering::Less
12950 }
12951 }
12952 (false, false) => {
12953 let c = value_cmp(a, b);
12954 if desc { c.reverse() } else { c }
12955 }
12956 }
12957}
12958
12959fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12960 use core::cmp::Ordering;
12961 match (a, b) {
12962 (Value::Null, Value::Null) => Ordering::Equal,
12963 (Value::Null, _) => Ordering::Less,
12964 (_, Value::Null) => Ordering::Greater,
12965 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12966 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12967 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12968 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12969 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12970 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12971 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12972 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12973 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12976 }
12977}
12978
12979#[allow(
12985 clippy::too_many_arguments,
12986 clippy::cast_possible_truncation,
12987 clippy::cast_possible_wrap,
12988 clippy::cast_precision_loss,
12989 clippy::cast_sign_loss,
12990 clippy::doc_markdown,
12991 clippy::too_many_lines,
12992 clippy::type_complexity,
12993 clippy::match_same_arms
12994)]
12995fn compute_window_partition(
12996 name: &str,
12997 args: &[Expr],
12998 ordered: bool,
12999 frame: Option<&WindowFrame>,
13000 null_treatment: spg_sql::ast::NullTreatment,
13001 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13002 filtered_rows: &[&Row],
13003 ctx: &EvalContext<'_>,
13004 out_vals: &mut [Value],
13005) -> Result<(), EngineError> {
13006 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
13007 let lower = name.to_ascii_lowercase();
13008 match lower.as_str() {
13009 "row_number" => {
13010 for (rank, (_, _, idx)) in slice.iter().enumerate() {
13011 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
13012 }
13013 Ok(())
13014 }
13015 "rank" => {
13016 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13017 let mut current_rank: i64 = 1;
13018 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13019 if let Some(p) = prev_key
13020 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13021 {
13022 current_rank = (i + 1) as i64;
13023 }
13024 if prev_key.is_none() {
13025 current_rank = 1;
13026 }
13027 out_vals[*idx] = Value::BigInt(current_rank);
13028 prev_key = Some(okey.as_slice());
13029 }
13030 Ok(())
13031 }
13032 "dense_rank" => {
13033 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13034 let mut current_rank: i64 = 0;
13035 for (_, okey, idx) in slice {
13036 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
13037 current_rank += 1;
13038 }
13039 out_vals[*idx] = Value::BigInt(current_rank);
13040 prev_key = Some(okey.as_slice());
13041 }
13042 Ok(())
13043 }
13044 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
13045 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
13048 slice.iter().map(|_| Value::Null).collect()
13049 } else {
13050 slice
13051 .iter()
13052 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13053 .collect::<Result<_, _>>()
13054 .map_err(EngineError::Eval)?
13055 };
13056 let eff = effective_frame(frame, ordered)?;
13060 #[allow(clippy::needless_range_loop)]
13061 for i in 0..slice.len() {
13062 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13063 let mut sum: f64 = 0.0;
13064 let mut count: i64 = 0;
13065 let mut min_v: Option<f64> = None;
13066 let mut max_v: Option<f64> = None;
13067 let mut row_count: i64 = 0;
13068 if lo <= hi {
13069 for j in lo..=hi {
13070 let v = &arg_values[j];
13071 match lower.as_str() {
13072 "count_star" => row_count += 1,
13073 "count" => {
13074 if !v.is_null() {
13075 count += 1;
13076 }
13077 }
13078 _ => {
13079 if let Some(x) = value_to_f64(v) {
13080 sum += x;
13081 count += 1;
13082 min_v = Some(min_v.map_or(x, |m| m.min(x)));
13083 max_v = Some(max_v.map_or(x, |m| m.max(x)));
13084 }
13085 }
13086 }
13087 }
13088 }
13089 let value = match lower.as_str() {
13090 "count_star" => Value::BigInt(row_count),
13091 "count" => Value::BigInt(count),
13092 "sum" => Value::Float(sum),
13093 "avg" => {
13094 if count == 0 {
13095 Value::Null
13096 } else {
13097 Value::Float(sum / count as f64)
13098 }
13099 }
13100 "min" => min_v.map_or(Value::Null, Value::Float),
13101 "max" => max_v.map_or(Value::Null, Value::Float),
13102 _ => unreachable!(),
13103 };
13104 let (_, _, idx) = &slice[i];
13105 out_vals[*idx] = value;
13106 }
13107 Ok(())
13108 }
13109 "lag" | "lead" => {
13110 if args.is_empty() {
13113 return Err(EngineError::Unsupported(alloc::format!(
13114 "{lower}() requires at least one argument"
13115 )));
13116 }
13117 let offset: i64 = if args.len() >= 2 {
13118 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13119 .map_err(EngineError::Eval)?;
13120 match v {
13121 Value::SmallInt(n) => i64::from(n),
13122 Value::Int(n) => i64::from(n),
13123 Value::BigInt(n) => n,
13124 _ => {
13125 return Err(EngineError::Unsupported(alloc::format!(
13126 "{lower}() offset must be integer"
13127 )));
13128 }
13129 }
13130 } else {
13131 1
13132 };
13133 let default: Value = if args.len() >= 3 {
13134 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
13135 .map_err(EngineError::Eval)?
13136 } else {
13137 Value::Null
13138 };
13139 let values: Vec<Value> = slice
13140 .iter()
13141 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13142 .collect::<Result<_, _>>()
13143 .map_err(EngineError::Eval)?;
13144 let n = slice.len();
13145 for (i, (_, _, idx)) in slice.iter().enumerate() {
13146 let signed_offset = if lower == "lag" { -offset } else { offset };
13147 let v = if ignore_nulls {
13148 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
13152 let needed: i64 = signed_offset.abs();
13153 if needed == 0 {
13154 values[i].clone()
13155 } else {
13156 let mut j: i64 = i as i64;
13157 let mut hits: i64 = 0;
13158 let mut found: Option<Value> = None;
13159 loop {
13160 j += step;
13161 if j < 0 || j >= n as i64 {
13162 break;
13163 }
13164 #[allow(clippy::cast_sign_loss)]
13165 let v = &values[j as usize];
13166 if !v.is_null() {
13167 hits += 1;
13168 if hits == needed {
13169 found = Some(v.clone());
13170 break;
13171 }
13172 }
13173 }
13174 found.unwrap_or_else(|| default.clone())
13175 }
13176 } else {
13177 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
13178 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
13179 default.clone()
13180 } else {
13181 #[allow(clippy::cast_sign_loss)]
13182 {
13183 values[target_signed as usize].clone()
13184 }
13185 }
13186 };
13187 out_vals[*idx] = v;
13188 }
13189 Ok(())
13190 }
13191 "first_value" | "last_value" | "nth_value" => {
13192 if args.is_empty() {
13193 return Err(EngineError::Unsupported(alloc::format!(
13194 "{lower}() requires at least one argument"
13195 )));
13196 }
13197 let values: Vec<Value> = slice
13198 .iter()
13199 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13200 .collect::<Result<_, _>>()
13201 .map_err(EngineError::Eval)?;
13202 let nth: usize = if lower == "nth_value" {
13203 if args.len() < 2 {
13204 return Err(EngineError::Unsupported(
13205 "nth_value() requires (expr, n)".into(),
13206 ));
13207 }
13208 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13209 .map_err(EngineError::Eval)?;
13210 let raw = match v {
13211 Value::SmallInt(n) => i64::from(n),
13212 Value::Int(n) => i64::from(n),
13213 Value::BigInt(n) => n,
13214 _ => {
13215 return Err(EngineError::Unsupported(
13216 "nth_value() n must be integer".into(),
13217 ));
13218 }
13219 };
13220 if raw < 1 {
13221 return Err(EngineError::Unsupported(
13222 "nth_value() n must be >= 1".into(),
13223 ));
13224 }
13225 #[allow(clippy::cast_sign_loss)]
13226 {
13227 raw as usize
13228 }
13229 } else {
13230 0
13231 };
13232 let eff = effective_frame(frame, ordered)?;
13233 for i in 0..slice.len() {
13234 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13235 let (_, _, idx) = &slice[i];
13236 let v = if lo > hi {
13237 Value::Null
13238 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
13239 if lower == "first_value" {
13242 (lo..=hi)
13243 .find_map(|j| {
13244 let v = &values[j];
13245 (!v.is_null()).then(|| v.clone())
13246 })
13247 .unwrap_or(Value::Null)
13248 } else {
13249 (lo..=hi)
13250 .rev()
13251 .find_map(|j| {
13252 let v = &values[j];
13253 (!v.is_null()).then(|| v.clone())
13254 })
13255 .unwrap_or(Value::Null)
13256 }
13257 } else {
13258 match lower.as_str() {
13259 "first_value" => values[lo].clone(),
13260 "last_value" => values[hi].clone(),
13261 "nth_value" => {
13262 let pos = lo + nth - 1;
13263 if pos > hi {
13264 Value::Null
13265 } else {
13266 values[pos].clone()
13267 }
13268 }
13269 _ => unreachable!(),
13270 }
13271 };
13272 out_vals[*idx] = v;
13273 }
13274 Ok(())
13275 }
13276 "ntile" => {
13277 if args.is_empty() {
13278 return Err(EngineError::Unsupported(
13279 "ntile(n) requires an integer argument".into(),
13280 ));
13281 }
13282 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
13283 .map_err(EngineError::Eval)?;
13284 let bucket_count: i64 = match v {
13285 Value::SmallInt(n) => i64::from(n),
13286 Value::Int(n) => i64::from(n),
13287 Value::BigInt(n) => n,
13288 _ => {
13289 return Err(EngineError::Unsupported(
13290 "ntile() argument must be integer".into(),
13291 ));
13292 }
13293 };
13294 if bucket_count < 1 {
13295 return Err(EngineError::Unsupported(
13296 "ntile() argument must be >= 1".into(),
13297 ));
13298 }
13299 #[allow(clippy::cast_sign_loss)]
13300 let buckets = bucket_count as usize;
13301 let n = slice.len();
13302 let base = n / buckets;
13305 let extras = n % buckets;
13306 let mut bucket: usize = 1;
13307 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
13308 let mut buckets_with_extra_remaining = extras;
13309 for (_, _, idx) in slice {
13310 if remaining_in_bucket == 0 {
13311 bucket += 1;
13312 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
13313 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
13314 base + 1
13315 } else {
13316 base
13317 };
13318 if remaining_in_bucket == 0 {
13321 remaining_in_bucket = 1;
13322 }
13323 }
13324 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13325 remaining_in_bucket -= 1;
13326 }
13327 Ok(())
13328 }
13329 "percent_rank" => {
13330 let n = slice.len();
13333 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13334 let mut current_rank: i64 = 1;
13335 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13336 if let Some(p) = prev_key
13337 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13338 {
13339 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13340 }
13341 if prev_key.is_none() {
13342 current_rank = 1;
13343 }
13344 #[allow(clippy::cast_precision_loss)]
13345 let pr = if n <= 1 {
13346 0.0
13347 } else {
13348 (current_rank - 1) as f64 / (n - 1) as f64
13349 };
13350 out_vals[*idx] = Value::Float(pr);
13351 prev_key = Some(okey.as_slice());
13352 }
13353 Ok(())
13354 }
13355 "cume_dist" => {
13356 let n = slice.len();
13358 for i in 0..slice.len() {
13360 let peer_end = peer_group_end(slice, i);
13361 #[allow(clippy::cast_precision_loss)]
13362 let cd = (peer_end + 1) as f64 / n as f64;
13363 let (_, _, idx) = &slice[i];
13364 out_vals[*idx] = Value::Float(cd);
13365 }
13366 Ok(())
13367 }
13368 other => Err(EngineError::Unsupported(alloc::format!(
13369 "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)"
13370 ))),
13371 }
13372}
13373
13374fn effective_frame(
13381 frame: Option<&WindowFrame>,
13382 ordered: bool,
13383) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13384 match frame {
13385 None => {
13386 if ordered {
13387 Ok((
13388 FrameKind::Range,
13389 FrameBound::UnboundedPreceding,
13390 FrameBound::CurrentRow,
13391 ))
13392 } else {
13393 Ok((
13394 FrameKind::Rows,
13395 FrameBound::UnboundedPreceding,
13396 FrameBound::UnboundedFollowing,
13397 ))
13398 }
13399 }
13400 Some(fr) => {
13401 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13402 if matches!(fr.start, FrameBound::UnboundedFollowing)
13404 || matches!(end, FrameBound::UnboundedPreceding)
13405 {
13406 return Err(EngineError::Unsupported(alloc::format!(
13407 "invalid frame: start={:?} end={:?}",
13408 fr.start,
13409 end
13410 )));
13411 }
13412 if fr.kind == FrameKind::Range
13417 && (matches!(
13418 fr.start,
13419 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13420 ) || matches!(
13421 end,
13422 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13423 ))
13424 {
13425 return Err(EngineError::Unsupported(
13426 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13427 ));
13428 }
13429 Ok((fr.kind, fr.start.clone(), end))
13430 }
13431 }
13432}
13433
13434#[allow(clippy::type_complexity)]
13438fn frame_bounds_for_row(
13439 eff: &(FrameKind, FrameBound, FrameBound),
13440 i: usize,
13441 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13442) -> (usize, usize) {
13443 let (kind, start, end) = eff;
13444 let n = slice.len();
13445 let last = n.saturating_sub(1);
13446 let (mut lo, mut hi) = match kind {
13447 FrameKind::Rows => {
13448 let lo = match start {
13449 FrameBound::UnboundedPreceding => 0,
13450 FrameBound::OffsetPreceding(k) => {
13451 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13452 i.saturating_sub(k)
13453 }
13454 FrameBound::CurrentRow => i,
13455 FrameBound::OffsetFollowing(k) => {
13456 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13457 i.saturating_add(k).min(last)
13458 }
13459 FrameBound::UnboundedFollowing => last,
13460 };
13461 let hi = match end {
13462 FrameBound::UnboundedPreceding => 0,
13463 FrameBound::OffsetPreceding(k) => {
13464 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13465 i.saturating_sub(k)
13466 }
13467 FrameBound::CurrentRow => i,
13468 FrameBound::OffsetFollowing(k) => {
13469 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13470 i.saturating_add(k).min(last)
13471 }
13472 FrameBound::UnboundedFollowing => last,
13473 };
13474 (lo, hi)
13475 }
13476 FrameKind::Range => {
13477 let lo = match start {
13483 FrameBound::UnboundedPreceding => 0,
13484 FrameBound::CurrentRow => peer_group_start(slice, i),
13485 FrameBound::UnboundedFollowing => last,
13486 _ => unreachable!("offset bounds rejected for RANGE"),
13487 };
13488 let hi = match end {
13489 FrameBound::UnboundedPreceding => 0,
13490 FrameBound::CurrentRow => peer_group_end(slice, i),
13491 FrameBound::UnboundedFollowing => last,
13492 _ => unreachable!("offset bounds rejected for RANGE"),
13493 };
13494 (lo, hi)
13495 }
13496 };
13497 if hi >= n {
13498 hi = last;
13499 }
13500 if lo >= n {
13501 lo = last;
13502 }
13503 (lo, hi)
13504}
13505
13506#[allow(clippy::type_complexity)]
13510fn peer_group_start(
13511 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13512 i: usize,
13513) -> usize {
13514 let key = &slice[i].1;
13515 let mut j = i;
13516 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13517 j -= 1;
13518 }
13519 j
13520}
13521
13522#[allow(clippy::type_complexity)]
13525fn peer_group_end(
13526 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13527 i: usize,
13528) -> usize {
13529 let key = &slice[i].1;
13530 let mut j = i;
13531 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13532 j += 1;
13533 }
13534 j
13535}
13536
13537fn value_to_f64(v: &Value) -> Option<f64> {
13538 match v {
13539 Value::SmallInt(n) => Some(f64::from(*n)),
13540 Value::Int(n) => Some(f64::from(*n)),
13541 #[allow(clippy::cast_precision_loss)]
13542 Value::BigInt(n) => Some(*n as f64),
13543 Value::Float(x) => Some(*x),
13544 _ => None,
13545 }
13546}
13547
13548fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13552 let mut any = false;
13553 for item in &stmt.items {
13554 if let SelectItem::Expr { expr, .. } = item {
13555 any = any || expr_has_subquery(expr);
13556 }
13557 }
13558 if let Some(w) = &stmt.where_ {
13559 any = any || expr_has_subquery(w);
13560 }
13561 if let Some(h) = &stmt.having {
13562 any = any || expr_has_subquery(h);
13563 }
13564 for o in &stmt.order_by {
13565 any = any || expr_has_subquery(&o.expr);
13566 }
13567 for (_, peer) in &stmt.unions {
13568 any = any || expr_tree_has_subquery(peer);
13569 }
13570 any
13571}
13572
13573fn expr_has_subquery(e: &Expr) -> bool {
13574 match e {
13575 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13576 Expr::AggregateOrdered { call, order_by, .. } => {
13577 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
13578 }
13579 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13580 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13581 expr_has_subquery(expr)
13582 }
13583 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13584 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13585 Expr::Extract { source, .. } => expr_has_subquery(source),
13586 Expr::WindowFunction {
13587 args,
13588 partition_by,
13589 order_by,
13590 ..
13591 } => {
13592 args.iter().any(expr_has_subquery)
13593 || partition_by.iter().any(expr_has_subquery)
13594 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
13595 }
13596 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13597 Expr::Array(items) => items.iter().any(expr_has_subquery),
13598 Expr::ArraySubscript { target, index } => {
13599 expr_has_subquery(target) || expr_has_subquery(index)
13600 }
13601 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13602 Expr::Case {
13603 operand,
13604 branches,
13605 else_branch,
13606 } => {
13607 operand.as_deref().is_some_and(expr_has_subquery)
13608 || branches
13609 .iter()
13610 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13611 || else_branch.as_deref().is_some_and(expr_has_subquery)
13612 }
13613 }
13614}
13615
13616fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13623 let lit = match v {
13624 Value::Null => Literal::Null,
13625 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13626 Value::Int(n) => Literal::Integer(i64::from(n)),
13627 Value::BigInt(n) => Literal::Integer(n),
13628 Value::Float(x) => Literal::Float(x),
13629 Value::Text(s) | Value::Json(s) => Literal::String(s),
13630 Value::Bool(b) => Literal::Bool(b),
13631 other => {
13632 return Err(EngineError::Unsupported(alloc::format!(
13633 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13634 other.data_type()
13635 )));
13636 }
13637 };
13638 Ok(Expr::Literal(lit))
13639}
13640
13641fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13647 let lit = match v {
13648 Value::Null => Literal::Null,
13649 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13650 Value::Int(n) => Literal::Integer(i64::from(n)),
13651 Value::BigInt(n) => Literal::Integer(n),
13652 Value::Float(x) => Literal::Float(x),
13653 Value::Text(s) | Value::Json(s) => Literal::String(s),
13654 Value::Bool(b) => Literal::Bool(b),
13655 Value::Vector(xs) => Literal::Vector(xs),
13656 Value::Date(days) => {
13660 let micros = (i64::from(days)) * 86_400_000_000;
13661 Literal::String(format_timestamp_micros_as_date(micros))
13662 }
13663 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13664 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13665 other => {
13666 return Err(EngineError::Unsupported(alloc::format!(
13667 "INSERT … SELECT cannot materialise value of type {:?}; \
13668 add an explicit CAST in the inner SELECT",
13669 other.data_type()
13670 )));
13671 }
13672 };
13673 Ok(Expr::Literal(lit))
13674}
13675
13676fn format_timestamp_micros(us: i64) -> String {
13677 let days = us.div_euclid(86_400_000_000);
13679 let intra_day = us.rem_euclid(86_400_000_000);
13680 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13681 let secs = intra_day / 1_000_000;
13682 let us_rem = intra_day % 1_000_000;
13683 let h = (secs / 3600) % 24;
13684 let m = (secs / 60) % 60;
13685 let s = secs % 60;
13686 if us_rem == 0 {
13687 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13688 } else {
13689 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13690 }
13691}
13692
13693fn format_timestamp_micros_as_date(us: i64) -> String {
13694 let days = us.div_euclid(86_400_000_000);
13697 let jdn = days + 2_440_588;
13699 let (y, mo, d) = jdn_to_ymd(jdn);
13700 alloc::format!("{y:04}-{mo:02}-{d:02}")
13701}
13702
13703fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13704 let l = jdn + 68569;
13706 let n = (4 * l) / 146_097;
13707 let l = l - (146_097 * n + 3) / 4;
13708 let i = (4000 * (l + 1)) / 1_461_001;
13709 let l = l - (1461 * i) / 4 + 31;
13710 let j = (80 * l) / 2447;
13711 let day = (l - (2447 * j) / 80) as u32;
13712 let l = j / 11;
13713 let month = (j + 2 - 12 * l) as u32;
13714 let year = 100 * (n - 49) + i + l;
13715 (year, month, day)
13716}
13717
13718fn format_numeric(scaled: i128, scale: u8) -> String {
13719 if scale == 0 {
13720 return alloc::format!("{scaled}");
13721 }
13722 let abs = scaled.unsigned_abs();
13723 let divisor = 10u128.pow(u32::from(scale));
13724 let whole = abs / divisor;
13725 let frac = abs % divisor;
13726 let sign = if scaled < 0 { "-" } else { "" };
13727 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13728}
13729
13730fn rewrite_column_in_source(
13754 src: &str,
13755 old: &str,
13756 new: &str,
13757) -> Result<alloc::string::String, EngineError> {
13758 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13759 EngineError::Unsupported(alloc::format!(
13760 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13761 failed to parse for rewrite ({e})"
13762 ))
13763 })?;
13764 rewrite_column_in_expr(&mut expr, old, new);
13765 Ok(alloc::format!("{expr}"))
13766}
13767
13768fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13776 match e {
13777 Expr::AggregateOrdered { call, order_by, .. } => {
13778 rewrite_column_in_expr(call, old, new);
13779 for o in order_by.iter_mut() {
13780 rewrite_column_in_expr(&mut o.expr, old, new);
13781 }
13782 }
13783 Expr::Column(c) => {
13784 if c.name.eq_ignore_ascii_case(old) {
13785 c.name = new.to_string();
13786 }
13787 }
13788 Expr::Binary { lhs, rhs, .. } => {
13789 rewrite_column_in_expr(lhs, old, new);
13790 rewrite_column_in_expr(rhs, old, new);
13791 }
13792 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13793 rewrite_column_in_expr(expr, old, new);
13794 }
13795 Expr::FunctionCall { args, .. } => {
13796 for a in args {
13797 rewrite_column_in_expr(a, old, new);
13798 }
13799 }
13800 Expr::Like { expr, pattern, .. } => {
13801 rewrite_column_in_expr(expr, old, new);
13802 rewrite_column_in_expr(pattern, old, new);
13803 }
13804 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13805 Expr::WindowFunction {
13806 args,
13807 partition_by,
13808 order_by,
13809 ..
13810 } => {
13811 for a in args {
13812 rewrite_column_in_expr(a, old, new);
13813 }
13814 for p in partition_by {
13815 rewrite_column_in_expr(p, old, new);
13816 }
13817 for (o, _, _) in order_by {
13818 rewrite_column_in_expr(o, old, new);
13819 }
13820 }
13821 Expr::Array(items) => {
13822 for elem in items {
13823 rewrite_column_in_expr(elem, old, new);
13824 }
13825 }
13826 Expr::ArraySubscript { target, index } => {
13827 rewrite_column_in_expr(target, old, new);
13828 rewrite_column_in_expr(index, old, new);
13829 }
13830 Expr::AnyAll { expr, array, .. } => {
13831 rewrite_column_in_expr(expr, old, new);
13832 rewrite_column_in_expr(array, old, new);
13833 }
13834 Expr::Case {
13835 operand,
13836 branches,
13837 else_branch,
13838 } => {
13839 if let Some(o) = operand {
13840 rewrite_column_in_expr(o, old, new);
13841 }
13842 for (w, t) in branches {
13843 rewrite_column_in_expr(w, old, new);
13844 rewrite_column_in_expr(t, old, new);
13845 }
13846 if let Some(e) = else_branch {
13847 rewrite_column_in_expr(e, old, new);
13848 }
13849 }
13850 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13854 Expr::Literal(_) | Expr::Placeholder(_) => {}
13855 }
13856}
13857
13858pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13866 match stmt {
13867 Statement::Select(s) => substitute_select(s, params)?,
13868 Statement::Insert(ins) => {
13869 for row in &mut ins.rows {
13870 for e in row {
13871 substitute_expr(e, params)?;
13872 }
13873 }
13874 if let Some(clause) = &mut ins.on_conflict
13878 && let spg_sql::ast::OnConflictAction::Update {
13879 assignments,
13880 where_,
13881 } = &mut clause.action
13882 {
13883 for (_, e) in assignments.iter_mut() {
13884 substitute_expr(e, params)?;
13885 }
13886 if let Some(w) = where_ {
13887 substitute_expr(w, params)?;
13888 }
13889 }
13890 }
13891 Statement::Update(u) => {
13892 for (_, e) in &mut u.assignments {
13893 substitute_expr(e, params)?;
13894 }
13895 if let Some(w) = &mut u.where_ {
13896 substitute_expr(w, params)?;
13897 }
13898 }
13899 Statement::Delete(d) => {
13900 if let Some(w) = &mut d.where_ {
13901 substitute_expr(w, params)?;
13902 }
13903 }
13904 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13905 _ => {}
13908 }
13909 Ok(())
13910}
13911
13912fn walk_select_exprs_mut(
13922 s: &mut SelectStatement,
13923 f: &mut impl FnMut(&mut Expr) -> Result<(), EngineError>,
13924) -> Result<(), EngineError> {
13925 for cte in &mut s.ctes {
13926 walk_select_exprs_mut(&mut cte.body, f)?;
13927 }
13928 for item in &mut s.items {
13929 if let SelectItem::Expr { expr, .. } = item {
13930 f(expr)?;
13931 }
13932 }
13933 if let Some(from) = &mut s.from {
13934 if let Some(sub) = &mut from.primary.lateral_subquery {
13935 walk_select_exprs_mut(sub, f)?;
13936 }
13937 for j in &mut from.joins {
13938 if let Some(sub) = &mut j.table.lateral_subquery {
13939 walk_select_exprs_mut(sub, f)?;
13940 }
13941 if let Some(on) = &mut j.on {
13942 f(on)?;
13943 }
13944 }
13945 }
13946 if let Some(w) = &mut s.where_ {
13947 f(w)?;
13948 }
13949 if let Some(gs) = &mut s.group_by {
13950 for g in gs {
13951 f(g)?;
13952 }
13953 }
13954 if let Some(h) = &mut s.having {
13955 f(h)?;
13956 }
13957 for o in &mut s.order_by {
13958 f(&mut o.expr)?;
13959 }
13960 for (_, peer) in &mut s.unions {
13961 walk_select_exprs_mut(peer, f)?;
13962 }
13963 Ok(())
13964}
13965
13966fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13967 walk_select_exprs_mut(s, &mut |e| substitute_expr(e, params))?;
13968 for cte in &mut s.ctes {
13973 resolve_limit_offset_placeholders(&mut cte.body, params)?;
13974 }
13975 for (_, peer) in &mut s.unions {
13976 resolve_limit_offset_placeholders(peer, params)?;
13977 }
13978 if let Some(le) = s.limit {
13983 s.limit = Some(resolve_limit_placeholder(le, params)?);
13984 }
13985 if let Some(le) = s.offset {
13986 s.offset = Some(resolve_limit_placeholder(le, params)?);
13987 }
13988 Ok(())
13989}
13990
13991fn resolve_limit_offset_placeholders(
13994 s: &mut SelectStatement,
13995 params: &[Value],
13996) -> Result<(), EngineError> {
13997 if let Some(le) = s.limit {
13998 s.limit = Some(resolve_limit_placeholder(le, params)?);
13999 }
14000 if let Some(le) = s.offset {
14001 s.offset = Some(resolve_limit_placeholder(le, params)?);
14002 }
14003 for cte in &mut s.ctes {
14004 resolve_limit_offset_placeholders(&mut cte.body, params)?;
14005 }
14006 for (_, peer) in &mut s.unions {
14007 resolve_limit_offset_placeholders(peer, params)?;
14008 }
14009 Ok(())
14010}
14011
14012fn resolve_limit_placeholder(
14013 le: spg_sql::ast::LimitExpr,
14014 params: &[Value],
14015) -> Result<spg_sql::ast::LimitExpr, EngineError> {
14016 use spg_sql::ast::LimitExpr;
14017 match le {
14018 LimitExpr::Literal(_) => Ok(le),
14019 LimitExpr::Placeholder(n) => {
14020 let idx = usize::from(n).saturating_sub(1);
14021 let v = params.get(idx).ok_or_else(|| {
14022 EngineError::Eval(EvalError::PlaceholderOutOfRange {
14023 n,
14024 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
14025 })
14026 })?;
14027 let int = match v {
14028 Value::SmallInt(x) => Some(i64::from(*x)),
14029 Value::Int(x) => Some(i64::from(*x)),
14030 Value::BigInt(x) => Some(*x),
14031 _ => None,
14032 }
14033 .ok_or_else(|| {
14034 EngineError::Unsupported(alloc::format!(
14035 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
14036 ))
14037 })?;
14038 if int < 0 {
14039 return Err(EngineError::Unsupported(alloc::format!(
14040 "LIMIT/OFFSET ${n} bound to negative value {int}"
14041 )));
14042 }
14043 let bounded = u32::try_from(int).map_err(|_| {
14044 EngineError::Unsupported(alloc::format!(
14045 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
14046 ))
14047 })?;
14048 Ok(LimitExpr::Literal(bounded))
14049 }
14050 }
14051}
14052
14053fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
14054 if let Expr::Placeholder(n) = e {
14055 let idx = usize::from(*n).saturating_sub(1);
14056 let v = params.get(idx).ok_or_else(|| {
14057 EngineError::Eval(EvalError::PlaceholderOutOfRange {
14058 n: *n,
14059 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
14060 })
14061 })?;
14062 *e = Expr::Literal(value_to_literal(v.clone()));
14063 return Ok(());
14064 }
14065 match e {
14066 Expr::AggregateOrdered { call, order_by, .. } => {
14067 substitute_expr(call, params)?;
14068 for o in order_by.iter_mut() {
14069 substitute_expr(&mut o.expr, params)?;
14070 }
14071 }
14072 Expr::Binary { lhs, rhs, .. } => {
14073 substitute_expr(lhs, params)?;
14074 substitute_expr(rhs, params)?;
14075 }
14076 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14077 substitute_expr(expr, params)?;
14078 }
14079 Expr::FunctionCall { args, .. } => {
14080 for a in args {
14081 substitute_expr(a, params)?;
14082 }
14083 }
14084 Expr::Like { expr, pattern, .. } => {
14085 substitute_expr(expr, params)?;
14086 substitute_expr(pattern, params)?;
14087 }
14088 Expr::Extract { source, .. } => substitute_expr(source, params)?,
14089 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
14090 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
14091 Expr::InSubquery { expr, subquery, .. } => {
14092 substitute_expr(expr, params)?;
14093 substitute_select(subquery, params)?;
14094 }
14095 Expr::WindowFunction {
14096 args,
14097 partition_by,
14098 order_by,
14099 ..
14100 } => {
14101 for a in args {
14102 substitute_expr(a, params)?;
14103 }
14104 for p in partition_by {
14105 substitute_expr(p, params)?;
14106 }
14107 for (e, _, _) in order_by {
14108 substitute_expr(e, params)?;
14109 }
14110 }
14111 Expr::Literal(_) | Expr::Column(_) => {}
14112 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
14114 Expr::Array(items) => {
14115 for elem in items {
14116 substitute_expr(elem, params)?;
14117 }
14118 }
14119 Expr::ArraySubscript { target, index } => {
14120 substitute_expr(target, params)?;
14121 substitute_expr(index, params)?;
14122 }
14123 Expr::AnyAll { expr, array, .. } => {
14124 substitute_expr(expr, params)?;
14125 substitute_expr(array, params)?;
14126 }
14127 Expr::Case {
14128 operand,
14129 branches,
14130 else_branch,
14131 } => {
14132 if let Some(o) = operand {
14133 substitute_expr(o, params)?;
14134 }
14135 for (w, t) in branches {
14136 substitute_expr(w, params)?;
14137 substitute_expr(t, params)?;
14138 }
14139 if let Some(e) = else_branch {
14140 substitute_expr(e, params)?;
14141 }
14142 }
14143 }
14144 Ok(())
14145}
14146
14147fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
14165 use core::cmp::Ordering;
14166 match (a, b) {
14167 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
14168 (Value::Int(a), Value::Int(b)) => a.cmp(b),
14169 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
14170 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
14171 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
14172 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14173 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
14174 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14175 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
14176 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14177 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
14178 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
14179 (Value::Date(a), Value::Date(b)) => a.cmp(b),
14180 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
14181 (Value::SmallInt(n), Value::Float(x)) => {
14183 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14184 }
14185 (Value::Float(x), Value::SmallInt(n)) => {
14186 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14187 }
14188 (Value::Int(n), Value::Float(x)) => {
14189 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14190 }
14191 (Value::Float(x), Value::Int(n)) => {
14192 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14193 }
14194 (Value::BigInt(n), Value::Float(x)) => {
14195 #[allow(clippy::cast_precision_loss)]
14196 let nf = *n as f64;
14197 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
14198 }
14199 (Value::Float(x), Value::BigInt(n)) => {
14200 #[allow(clippy::cast_precision_loss)]
14201 let nf = *n as f64;
14202 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
14203 }
14204 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
14207 }
14208}
14209
14210fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
14217 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
14218 out.push('[');
14219 for (i, b) in bounds.iter().enumerate() {
14220 if i > 0 {
14221 out.push_str(", ");
14222 }
14223 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
14224 if needs_quote {
14225 out.push('"');
14226 for ch in b.chars() {
14227 if ch == '"' || ch == '\\' {
14228 out.push('\\');
14229 }
14230 out.push(ch);
14231 }
14232 out.push('"');
14233 } else {
14234 out.push_str(b);
14235 }
14236 }
14237 out.push(']');
14238 out
14239}
14240
14241pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
14251 match v {
14252 Value::Null => "NULL".to_string(),
14253 Value::SmallInt(n) => alloc::format!("{n}"),
14254 Value::Int(n) => alloc::format!("{n}"),
14255 Value::BigInt(n) => alloc::format!("{n}"),
14256 Value::Float(x) => alloc::format!("{x:?}"),
14257 Value::Text(s) | Value::Json(s) => s.clone(),
14258 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
14259 Value::Date(d) => eval::format_date(*d),
14260 Value::Timestamp(t) => eval::format_timestamp(*t),
14261 Value::Time(us) => eval::format_time(*us),
14263 Value::Year(y) => alloc::format!("{y:04}"),
14265 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
14267 Value::Money(c) => eval::format_money(*c),
14269 v @ Value::Range { .. } => format_range_str(v),
14271 Value::Hstore(pairs) => format_hstore_str(pairs),
14273 Value::IntArray2D(rows) => format_int_2d_text(rows),
14275 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
14276 Value::TextArray2D(rows) => format_text_2d_text(rows),
14277 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
14278 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
14279 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
14280 alloc::format!("{v:?}")
14284 }
14285 _ => alloc::format!("{v:?}"),
14289 }
14290}
14291
14292const fn is_internal_table_name(_name: &str) -> bool {
14299 false
14300}
14301
14302fn value_to_literal(v: Value) -> Literal {
14303 match v {
14304 Value::Null => Literal::Null,
14305 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14306 Value::Int(n) => Literal::Integer(i64::from(n)),
14307 Value::BigInt(n) => Literal::Integer(n),
14308 Value::Float(x) => Literal::Float(x),
14309 Value::Text(s) | Value::Json(s) => Literal::String(s),
14310 Value::Bool(b) => Literal::Bool(b),
14311 Value::Vector(v) => Literal::Vector(v),
14312 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
14313 Value::Date(d) => Literal::String(eval::format_date(d)),
14314 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14315 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14321 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14326 Value::TextArray(items) => Literal::TextArray(items),
14331 Value::IntArray(items) => Literal::IntArray(items),
14332 Value::BigIntArray(items) => Literal::BigIntArray(items),
14333 Value::Interval { months, micros } => Literal::Interval {
14334 months,
14335 micros,
14336 text: eval::format_interval(months, micros),
14337 },
14338 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14341 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14342 v => Literal::String(alloc::format!("{v:?}")),
14346 }
14347}
14348
14349fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14350 let Some(now) = now_micros else {
14351 return;
14352 };
14353 match stmt {
14354 Statement::Select(s) => rewrite_select_clock(s, now),
14355 Statement::Insert(ins) => {
14356 for row in &mut ins.rows {
14357 for e in row {
14358 rewrite_expr_clock(e, now);
14359 }
14360 }
14361 if let Some(clause) = &mut ins.on_conflict
14365 && let spg_sql::ast::OnConflictAction::Update {
14366 assignments,
14367 where_,
14368 } = &mut clause.action
14369 {
14370 for (_, e) in assignments.iter_mut() {
14371 rewrite_expr_clock(e, now);
14372 }
14373 if let Some(w) = where_ {
14374 rewrite_expr_clock(w, now);
14375 }
14376 }
14377 }
14378 Statement::Update(u) => {
14382 for (_, e) in &mut u.assignments {
14383 rewrite_expr_clock(e, now);
14384 }
14385 if let Some(w) = &mut u.where_ {
14386 rewrite_expr_clock(w, now);
14387 }
14388 }
14389 Statement::Delete(d) => {
14390 if let Some(w) = &mut d.where_ {
14391 rewrite_expr_clock(w, now);
14392 }
14393 }
14394 _ => {}
14395 }
14396}
14397
14398fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14399 let _ = walk_select_exprs_mut(s, &mut |e| {
14404 rewrite_expr_clock(e, now);
14405 Ok(())
14406 });
14407}
14408
14409fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14417 if let Some(replacement) = clock_replacement_for(e, now) {
14421 *e = replacement;
14422 return;
14423 }
14424 match e {
14425 Expr::AggregateOrdered { call, order_by, .. } => {
14426 rewrite_expr_clock(call, now);
14427 for o in order_by.iter_mut() {
14428 rewrite_expr_clock(&mut o.expr, now);
14429 }
14430 }
14431 Expr::Binary { lhs, rhs, .. } => {
14432 rewrite_expr_clock(lhs, now);
14433 rewrite_expr_clock(rhs, now);
14434 }
14435 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14436 rewrite_expr_clock(expr, now);
14437 }
14438 Expr::FunctionCall { args, .. } => {
14439 for a in args {
14440 rewrite_expr_clock(a, now);
14441 }
14442 }
14443 Expr::Like { expr, pattern, .. } => {
14444 rewrite_expr_clock(expr, now);
14445 rewrite_expr_clock(pattern, now);
14446 }
14447 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14448 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14452 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14453 Expr::InSubquery { expr, subquery, .. } => {
14454 rewrite_expr_clock(expr, now);
14455 rewrite_select_clock(subquery, now);
14456 }
14457 Expr::WindowFunction {
14460 args,
14461 partition_by,
14462 order_by,
14463 ..
14464 } => {
14465 for a in args {
14466 rewrite_expr_clock(a, now);
14467 }
14468 for p in partition_by {
14469 rewrite_expr_clock(p, now);
14470 }
14471 for (e, _, _) in order_by {
14472 rewrite_expr_clock(e, now);
14473 }
14474 }
14475 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14476 Expr::Array(items) => {
14477 for elem in items {
14478 rewrite_expr_clock(elem, now);
14479 }
14480 }
14481 Expr::ArraySubscript { target, index } => {
14482 rewrite_expr_clock(target, now);
14483 rewrite_expr_clock(index, now);
14484 }
14485 Expr::AnyAll { expr, array, .. } => {
14486 rewrite_expr_clock(expr, now);
14487 rewrite_expr_clock(array, now);
14488 }
14489 Expr::Case {
14490 operand,
14491 branches,
14492 else_branch,
14493 } => {
14494 if let Some(o) = operand {
14495 rewrite_expr_clock(o, now);
14496 }
14497 for (w, t) in branches {
14498 rewrite_expr_clock(w, now);
14499 rewrite_expr_clock(t, now);
14500 }
14501 if let Some(e) = else_branch {
14502 rewrite_expr_clock(e, now);
14503 }
14504 }
14505 }
14506}
14507
14508fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14515 let (kind, name) = match e {
14516 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14517 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14518 _ => return None,
14519 };
14520 enum ClockShape {
14528 Timestamp,
14529 Date,
14530 UnixSeconds,
14531 }
14532 let shape = match name.len() {
14533 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14534 Some(ClockShape::Timestamp)
14535 }
14536 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14537 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14538 Some(ClockShape::UnixSeconds)
14539 }
14540 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14541 _ => None,
14542 };
14543 let shape = shape?;
14544 let payload = match shape {
14545 ClockShape::Timestamp => now,
14546 ClockShape::Date => now.div_euclid(86_400_000_000),
14547 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14548 };
14549 let target = match shape {
14550 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14551 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14552 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14553 };
14554 Some(Expr::Cast {
14555 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14556 target,
14557 })
14558}
14559
14560#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14561enum ClockSite {
14562 Fn,
14563 BareIdent,
14564}
14565
14566fn expand_group_by_all(s: &mut SelectStatement) {
14577 if !s.group_by_all {
14578 for (_, peer) in &mut s.unions {
14579 expand_group_by_all(peer);
14580 }
14581 return;
14582 }
14583 let mut groups: Vec<Expr> = Vec::new();
14584 for item in &s.items {
14585 if let SelectItem::Expr { expr, .. } = item
14586 && !aggregate::contains_aggregate(expr)
14587 {
14588 groups.push(expr.clone());
14589 }
14590 }
14591 s.group_by = Some(groups);
14592 s.group_by_all = false;
14593 for (_, peer) in &mut s.unions {
14594 expand_group_by_all(peer);
14595 }
14596}
14597
14598fn resolve_order_by_position(s: &mut SelectStatement) {
14599 for order in &mut s.order_by {
14604 match &order.expr {
14605 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14606 if let Ok(idx_one_based) = usize::try_from(*n) {
14607 let idx = idx_one_based - 1;
14608 if idx < s.items.len()
14609 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14610 {
14611 order.expr = expr.clone();
14612 }
14613 }
14614 }
14615 Expr::Column(c) if c.qualifier.is_none() => {
14616 for item in &s.items {
14618 if let SelectItem::Expr {
14619 expr,
14620 alias: Some(a),
14621 } = item
14622 && a == &c.name
14623 {
14624 order.expr = expr.clone();
14625 break;
14626 }
14627 }
14628 }
14629 _ => {}
14630 }
14631 }
14632 for (_, peer) in &mut s.unions {
14633 resolve_order_by_position(peer);
14634 }
14635}
14636
14637fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14650 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14651 match keep {
14652 Some(k) if k < tagged.len() && k > 0 => {
14653 let pivot = k - 1;
14654 tagged.select_nth_unstable_by(pivot, cmp);
14655 tagged[..k].sort_by(cmp);
14656 tagged.truncate(k);
14657 }
14658 _ => {
14659 tagged.sort_by(cmp);
14660 }
14661 }
14662}
14663
14664fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14665 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14666}
14667
14668fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14672 use core::cmp::Ordering;
14673 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14674 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14675 let ord = if descs.get(i).copied().unwrap_or(false) {
14676 ord.reverse()
14677 } else {
14678 ord
14679 };
14680 if ord != Ordering::Equal {
14681 return ord;
14682 }
14683 }
14684 Ordering::Equal
14685}
14686
14687fn build_order_keys(
14690 order_by: &[OrderBy],
14691 row: &Row,
14692 ctx: &EvalContext,
14693) -> Result<Vec<f64>, EngineError> {
14694 let mut keys = Vec::with_capacity(order_by.len());
14695 for o in order_by {
14696 let v = eval::eval_expr(&o.expr, row, ctx)?;
14697 if matches!(v, Value::Null) {
14704 let nf = o.nulls_first.unwrap_or(o.desc);
14705 keys.push(if nf == o.desc {
14706 f64::INFINITY
14707 } else {
14708 f64::NEG_INFINITY
14709 });
14710 } else {
14711 keys.push(value_to_order_key(&v)?);
14712 }
14713 }
14714 Ok(keys)
14715}
14716
14717fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14721 if let Some(off) = offset {
14722 let off = off as usize;
14723 if off >= rows.len() {
14724 rows.clear();
14725 } else {
14726 rows.drain(..off);
14727 }
14728 }
14729 if let Some(n) = limit {
14730 rows.truncate(n as usize);
14731 }
14732}
14733
14734fn apply_offset_and_limit_tagged(
14745 tagged: &mut Vec<(Vec<f64>, Row)>,
14746 offset: Option<u32>,
14747 limit: Option<u32>,
14748 with_ties: bool,
14749) {
14750 if let Some(off) = offset {
14751 let off = off as usize;
14752 if off >= tagged.len() {
14753 tagged.clear();
14754 } else {
14755 tagged.drain(..off);
14756 }
14757 }
14758 if let Some(n) = limit {
14759 let n = n as usize;
14760 if with_ties && n > 0 && n < tagged.len() {
14761 let cutoff_key = tagged[n - 1].0.clone();
14762 let mut end = n;
14763 while end < tagged.len() && tagged[end].0 == cutoff_key {
14764 end += 1;
14765 }
14766 tagged.truncate(end);
14767 } else {
14768 tagged.truncate(n);
14769 }
14770 }
14771}
14772
14773fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14779 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14780 return Err(EngineError::Unsupported(alloc::string::String::from(
14781 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14782 )));
14783 }
14784 Ok(())
14785}
14786
14787fn resolve_foreign_key(
14801 local_table_name: &str,
14802 local_cols: &[ColumnSchema],
14803 fk: spg_sql::ast::ForeignKeyConstraint,
14804 catalog: &Catalog,
14805) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14806 let mut local_columns = Vec::with_capacity(fk.columns.len());
14808 for name in &fk.columns {
14809 let pos = local_cols
14810 .iter()
14811 .position(|c| c.name == *name)
14812 .ok_or_else(|| {
14813 EngineError::Unsupported(alloc::format!(
14814 "FOREIGN KEY references unknown local column {name:?}"
14815 ))
14816 })?;
14817 local_columns.push(pos);
14818 }
14819 let is_self_ref = fk.parent_table == local_table_name;
14823 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14824 (local_cols, local_table_name)
14825 } else {
14826 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14827 EngineError::Storage(StorageError::TableNotFound {
14828 name: fk.parent_table.clone(),
14829 })
14830 })?;
14831 (
14832 parent_table.schema().columns.as_slice(),
14833 fk.parent_table.as_str(),
14834 )
14835 };
14836 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14841 if fk.columns.len() != 1 {
14842 return Err(EngineError::Unsupported(
14843 "composite FOREIGN KEY without explicit parent column list is not supported \
14844 — list the parent columns explicitly"
14845 .into(),
14846 ));
14847 }
14848 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14850 .ok_or_else(|| {
14851 EngineError::Unsupported(alloc::format!(
14852 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14853 to default the FOREIGN KEY against"
14854 ))
14855 })?;
14856 alloc::vec![pos]
14857 } else {
14858 let mut out = Vec::with_capacity(fk.parent_columns.len());
14859 for name in &fk.parent_columns {
14860 let pos = parent_cols_for_lookup
14861 .iter()
14862 .position(|c| c.name == *name)
14863 .ok_or_else(|| {
14864 EngineError::Unsupported(alloc::format!(
14865 "FOREIGN KEY references unknown parent column \
14866 {name:?} on table {parent_table_str:?}"
14867 ))
14868 })?;
14869 out.push(pos);
14870 }
14871 out
14872 };
14873 if parent_columns.len() != local_columns.len() {
14874 return Err(EngineError::Unsupported(alloc::format!(
14875 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14876 local_columns.len(),
14877 parent_columns.len()
14878 )));
14879 }
14880 if !is_self_ref {
14890 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14891 let primary_parent_col = parent_columns[0];
14892 let has_btree = parent_table
14893 .schema()
14894 .columns
14895 .get(primary_parent_col)
14896 .is_some()
14897 && parent_table.indices().iter().any(|idx| {
14898 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14899 && idx.column_position == primary_parent_col
14900 && idx.partial_predicate.is_none()
14901 });
14902 if !has_btree {
14903 return Err(EngineError::Unsupported(alloc::format!(
14904 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14905 index — create one with `CREATE INDEX ... ON {} ({})` first",
14906 parent_table_str,
14907 parent_table_str,
14908 parent_table.schema().columns[primary_parent_col].name,
14909 )));
14910 }
14911 }
14912 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14913 let on_update = fk_action_sql_to_storage(fk.on_update);
14914 Ok(spg_storage::ForeignKeyConstraint {
14915 name: fk.name,
14916 local_columns,
14917 parent_table: fk.parent_table,
14918 parent_columns,
14919 on_delete,
14920 on_update,
14921 })
14922}
14923
14924fn pick_pk_index_column(
14930 catalog: &Catalog,
14931 parent_name: &str,
14932 is_self_ref: bool,
14933 local_cols: &[ColumnSchema],
14934) -> Option<usize> {
14935 if is_self_ref {
14936 let _ = local_cols;
14940 return Some(0);
14941 }
14942 let parent = catalog.get(parent_name)?;
14943 parent.indices().iter().find_map(|idx| {
14944 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14945 && idx.partial_predicate.is_none()
14946 && idx.included_columns.is_empty()
14947 && idx.expression.is_none()
14948 {
14949 Some(idx.column_position)
14950 } else {
14951 None
14952 }
14953 })
14954}
14955
14956fn resolve_on_conflict_columns(
14963 catalog: &Catalog,
14964 table_name: &str,
14965 target: &[String],
14966) -> Result<Vec<usize>, EngineError> {
14967 let table = catalog.get(table_name).ok_or_else(|| {
14968 EngineError::Storage(StorageError::TableNotFound {
14969 name: table_name.into(),
14970 })
14971 })?;
14972 if target.is_empty() {
14973 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14983 return Ok(uc.columns.clone());
14984 }
14985 let pos = table
14986 .indices()
14987 .iter()
14988 .find_map(|idx| {
14989 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14990 && idx.partial_predicate.is_none()
14991 && idx.included_columns.is_empty()
14992 && idx.expression.is_none()
14993 {
14994 Some(idx.column_position)
14995 } else {
14996 None
14997 }
14998 })
14999 .ok_or_else(|| {
15000 EngineError::Unsupported(alloc::format!(
15001 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
15002 ))
15003 })?;
15004 return Ok(alloc::vec![pos]);
15005 }
15006 let mut out = Vec::with_capacity(target.len());
15007 for name in target {
15008 let pos = table
15009 .schema()
15010 .columns
15011 .iter()
15012 .position(|c| c.name == *name)
15013 .ok_or_else(|| {
15014 EngineError::Unsupported(alloc::format!(
15015 "ON CONFLICT target column {name:?} not found on {table_name:?}"
15016 ))
15017 })?;
15018 out.push(pos);
15019 }
15020 Ok(out)
15021}
15022
15023fn on_conflict_key_exists(
15026 catalog: &Catalog,
15027 table_name: &str,
15028 column_pos: usize,
15029 key: &Value,
15030) -> bool {
15031 let Some(table) = catalog.get(table_name) else {
15032 return false;
15033 };
15034 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
15035 return false;
15036 };
15037 table.indices().iter().any(|idx| {
15038 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15039 && idx.column_position == column_pos
15040 && idx.partial_predicate.is_none()
15041 && !idx.lookup_eq(&idx_key).is_empty()
15042 })
15043}
15044
15045fn lookup_row_position_by_keys(
15051 catalog: &Catalog,
15052 table_name: &str,
15053 column_positions: &[usize],
15054 key: &[&Value],
15055) -> Option<usize> {
15056 let table = catalog.get(table_name)?;
15057 table.rows().iter().position(|r| {
15058 column_positions
15059 .iter()
15060 .enumerate()
15061 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15062 })
15063}
15064
15065fn on_conflict_keys_exist(
15070 catalog: &Catalog,
15071 table_name: &str,
15072 column_positions: &[usize],
15073 key: &[&Value],
15074) -> bool {
15075 if column_positions.len() == 1 {
15076 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
15077 }
15078 let Some(table) = catalog.get(table_name) else {
15079 return false;
15080 };
15081 table.rows().iter().any(|r| {
15082 column_positions
15083 .iter()
15084 .enumerate()
15085 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15086 })
15087}
15088
15089fn apply_on_conflict_assignments(
15102 catalog: &Catalog,
15103 table_name: &str,
15104 target_pos: usize,
15105 incoming: &[Value],
15106 assignments: &[(String, Expr)],
15107 where_: Option<&Expr>,
15108) -> Result<Option<Vec<Value>>, EngineError> {
15109 let table = catalog.get(table_name).ok_or_else(|| {
15110 EngineError::Storage(StorageError::TableNotFound {
15111 name: table_name.into(),
15112 })
15113 })?;
15114 let schema_cols = table.schema().columns.clone();
15115 let existing = table
15116 .rows()
15117 .get(target_pos)
15118 .ok_or_else(|| {
15119 EngineError::Unsupported(alloc::format!(
15120 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
15121 ))
15122 })?
15123 .clone();
15124 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
15125 if let Some(w) = where_ {
15127 let pred = w.clone();
15128 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
15129 let v = eval::eval_expr(&pred, &existing, &ctx)?;
15130 if !matches!(v, Value::Bool(true)) {
15131 return Ok(None);
15132 }
15133 }
15134 let mut new_values = existing.values.clone();
15135 for (col_name, expr) in assignments {
15136 let target_idx = schema_cols
15137 .iter()
15138 .position(|c| c.name == *col_name)
15139 .ok_or_else(|| {
15140 EngineError::Eval(EvalError::ColumnNotFound {
15141 name: col_name.clone(),
15142 })
15143 })?;
15144 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
15145 let v = eval::eval_expr(&sub, &existing, &ctx)?;
15146 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
15147 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
15148 new_values[target_idx] = coerced;
15149 }
15150 Ok(Some(new_values))
15151}
15152
15153fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
15158 use spg_sql::ast::ColumnName;
15159 match expr {
15160 Expr::Column(ColumnName { qualifier, name })
15161 if qualifier
15162 .as_deref()
15163 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
15164 {
15165 let pos = schema_cols.iter().position(|c| c.name == name);
15166 match pos {
15167 Some(p) => {
15168 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
15169 value_to_literal_expr(v)
15170 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
15171 }
15172 None => Expr::Column(ColumnName { qualifier, name }),
15173 }
15174 }
15175 Expr::Binary { op, lhs, rhs } => Expr::Binary {
15176 op,
15177 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
15178 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
15179 },
15180 Expr::Unary { op, expr } => Expr::Unary {
15181 op,
15182 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
15183 },
15184 Expr::FunctionCall { name, args } => Expr::FunctionCall {
15185 name,
15186 args: args
15187 .into_iter()
15188 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
15189 .collect(),
15190 },
15191 other => other,
15192 }
15193}
15194
15195fn enforce_uniqueness_inserts(
15218 catalog: &Catalog,
15219 child_table: &str,
15220 constraints: &[spg_storage::UniquenessConstraint],
15221 rows: &[Vec<Value>],
15222) -> Result<(), EngineError> {
15223 if constraints.is_empty() {
15224 return Ok(());
15225 }
15226 let table = catalog.get(child_table).ok_or_else(|| {
15227 EngineError::Storage(StorageError::TableNotFound {
15228 name: child_table.into(),
15229 })
15230 })?;
15231 let schema = table.schema();
15232 for uc in constraints {
15233 for (batch_idx, row_values) in rows.iter().enumerate() {
15234 let key: Vec<Value> = uc
15243 .columns
15244 .iter()
15245 .map(|&i| collated_key_cell(&row_values[i], i, schema))
15246 .collect();
15247 let has_null = key.iter().any(|v| matches!(v, Value::Null));
15248 if has_null && !uc.nulls_not_distinct {
15253 continue;
15254 }
15255 let collides_in_table = table.rows().iter().any(|prow| {
15257 uc.columns.iter().enumerate().all(|(i, &p)| {
15258 prow.values
15259 .get(p)
15260 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15261 })
15262 });
15263 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
15265 uc.columns.iter().enumerate().all(|(i, &p)| {
15266 earlier
15267 .get(p)
15268 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15269 })
15270 });
15271 if collides_in_table || collides_in_batch {
15272 let kind = if uc.is_primary_key {
15273 "PRIMARY KEY"
15274 } else {
15275 "UNIQUE"
15276 };
15277 let col_names: Vec<String> = uc
15278 .columns
15279 .iter()
15280 .map(|&i| table.schema().columns[i].name.clone())
15281 .collect();
15282 return Err(EngineError::Unsupported(alloc::format!(
15283 "{kind} violation on {child_table:?} columns {col_names:?}: \
15284 row #{batch_idx} duplicates an existing key"
15285 )));
15286 }
15287 }
15288 }
15289 Ok(())
15290}
15291
15292fn collated_key_cell(
15299 v: &spg_storage::Value,
15300 column_position: usize,
15301 schema: &spg_storage::TableSchema,
15302) -> spg_storage::Value {
15303 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
15304 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
15305 spg_storage::Value::Text(s.to_ascii_lowercase())
15306 }
15307 _ => v.clone(),
15308 }
15309}
15310
15311fn predicate_truthy(v: &spg_storage::Value) -> bool {
15319 use spg_storage::Value as V;
15320 match v {
15321 V::Bool(b) => *b,
15322 V::Int(n) => *n != 0,
15323 V::BigInt(n) => *n != 0,
15324 V::SmallInt(n) => *n != 0,
15325 _ => false,
15326 }
15327}
15328
15329fn check_existing_unique_violation(
15334 idx: &spg_storage::Index,
15335 schema: &spg_storage::TableSchema,
15336 rows: &[spg_storage::Row],
15337) -> Result<(), EngineError> {
15338 let predicate_expr = match idx.partial_predicate.as_deref() {
15339 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15340 EngineError::Unsupported(alloc::format!(
15341 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15342 ))
15343 })?),
15344 None => None,
15345 };
15346 let ctx = eval::EvalContext::new(&schema.columns, None);
15347 let key_positions = unique_key_positions(idx);
15348 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15349 for row in rows {
15350 if let Some(expr) = &predicate_expr {
15351 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15352 EngineError::Unsupported(alloc::format!(
15353 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15354 ))
15355 })?;
15356 if !predicate_truthy(&v) {
15357 continue;
15358 }
15359 }
15360 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15361 .iter()
15362 .map(|&p| {
15363 let v = row
15364 .values
15365 .get(p)
15366 .cloned()
15367 .unwrap_or(spg_storage::Value::Null);
15368 collated_key_cell(&v, p, schema)
15369 })
15370 .collect();
15371 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15372 continue;
15373 }
15374 if seen.iter().any(|other| *other == key) {
15375 return Err(EngineError::Unsupported(alloc::format!(
15376 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15377 idx.name
15378 )));
15379 }
15380 seen.push(key);
15381 }
15382 Ok(())
15383}
15384
15385fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15389 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15390 out.push(idx.column_position);
15391 out.extend_from_slice(&idx.extra_column_positions);
15392 out
15393}
15394
15395fn enforce_unique_index_inserts(
15403 catalog: &Catalog,
15404 table_name: &str,
15405 rows: &[alloc::vec::Vec<spg_storage::Value>],
15406) -> Result<(), EngineError> {
15407 let table = catalog.get(table_name).ok_or_else(|| {
15408 EngineError::Storage(StorageError::TableNotFound {
15409 name: table_name.into(),
15410 })
15411 })?;
15412 let schema = table.schema();
15413 let ctx = eval::EvalContext::new(&schema.columns, None);
15414 for idx in table.indices() {
15415 if !idx.is_unique {
15416 continue;
15417 }
15418 let predicate_expr = match idx.partial_predicate.as_deref() {
15420 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15421 EngineError::Unsupported(alloc::format!(
15422 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15423 idx.name
15424 ))
15425 })?),
15426 None => None,
15427 };
15428 let key_positions = unique_key_positions(idx);
15429 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15430 key_positions
15434 .iter()
15435 .map(|&p| {
15436 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15437 collated_key_cell(&v, p, schema)
15438 })
15439 .collect()
15440 };
15441 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15445 let Some(expr) = &predicate_expr else {
15446 return Ok(true);
15447 };
15448 let tmp_row = spg_storage::Row {
15449 values: values.to_vec(),
15450 };
15451 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15452 EngineError::Unsupported(alloc::format!(
15453 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15454 idx.name
15455 ))
15456 })?;
15457 Ok(predicate_truthy(&v))
15458 };
15459 for (batch_idx, row_values) in rows.iter().enumerate() {
15460 if !participates(row_values)? {
15461 continue;
15462 }
15463 let key = key_of(row_values);
15464 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15465 continue;
15466 }
15467 for prow in table.rows() {
15469 if !participates(&prow.values)? {
15470 continue;
15471 }
15472 if key_of(&prow.values) == key {
15473 return Err(EngineError::Unsupported(alloc::format!(
15474 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15475 row #{batch_idx} duplicates an existing key",
15476 idx.name
15477 )));
15478 }
15479 }
15480 for earlier in &rows[..batch_idx] {
15482 if !participates(earlier)? {
15483 continue;
15484 }
15485 if key_of(earlier) == key {
15486 return Err(EngineError::Unsupported(alloc::format!(
15487 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15488 row #{batch_idx} duplicates an earlier row in the same batch",
15489 idx.name
15490 )));
15491 }
15492 }
15493 }
15494 }
15495 Ok(())
15496}
15497
15498fn any_column_changed(
15506 filter_cols: &[String],
15507 schema_cols: &[ColumnSchema],
15508 old_row: &Row,
15509 new_row: &Row,
15510) -> bool {
15511 for col_name in filter_cols {
15512 let Some(pos) = schema_cols
15513 .iter()
15514 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15515 else {
15516 continue;
15517 };
15518 let old_v = old_row.values.get(pos);
15519 let new_v = new_row.values.get(pos);
15520 if old_v != new_v {
15521 return true;
15522 }
15523 }
15524 false
15525}
15526
15527fn enforce_check_constraints(
15532 catalog: &Catalog,
15533 table_name: &str,
15534 rows: &[alloc::vec::Vec<spg_storage::Value>],
15535) -> Result<(), EngineError> {
15536 let table = catalog.get(table_name).ok_or_else(|| {
15537 EngineError::Storage(StorageError::TableNotFound {
15538 name: table_name.into(),
15539 })
15540 })?;
15541 let schema = table.schema();
15542 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15546 alloc::vec::Vec::new();
15547 for (idx, col) in schema.columns.iter().enumerate() {
15548 let Some(dname) = &col.user_domain_type else {
15549 continue;
15550 };
15551 let Some(dom) = catalog.domain_types().get(dname) else {
15552 continue;
15553 };
15554 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15555 alloc::vec::Vec::with_capacity(dom.checks.len());
15556 for src in &dom.checks {
15557 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15558 EngineError::Unsupported(alloc::format!(
15559 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15560 col.name
15561 ))
15562 })?;
15563 parsed_for_col.push(expr);
15564 }
15565 if !parsed_for_col.is_empty() {
15566 domain_checks_per_col.push((idx, parsed_for_col));
15567 }
15568 }
15569 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15570 return Ok(());
15571 }
15572 let ctx = eval::EvalContext::new(&schema.columns, None);
15573 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15574 for (i, src) in schema.checks.iter().enumerate() {
15575 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15576 EngineError::Unsupported(alloc::format!(
15577 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15578 ))
15579 })?;
15580 parsed.push((i, expr));
15581 }
15582 for (batch_idx, row_values) in rows.iter().enumerate() {
15583 let tmp_row = spg_storage::Row {
15584 values: row_values.clone(),
15585 };
15586 for (i, expr) in &parsed {
15587 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15588 EngineError::Unsupported(alloc::format!(
15589 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15590 ))
15591 })?;
15592 if matches!(v, spg_storage::Value::Bool(false)) {
15594 return Err(EngineError::Unsupported(alloc::format!(
15595 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15596 schema.checks[*i]
15597 )));
15598 }
15599 }
15600 for (col_idx, checks) in &domain_checks_per_col {
15606 let cell = row_values
15607 .get(*col_idx)
15608 .cloned()
15609 .unwrap_or(spg_storage::Value::Null);
15610 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15611 "value",
15612 schema.columns[*col_idx].ty,
15613 schema.columns[*col_idx].nullable,
15614 )];
15615 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15616 let synth_row = spg_storage::Row {
15617 values: alloc::vec![cell],
15618 };
15619 for (ci, expr) in checks.iter().enumerate() {
15620 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15621 EngineError::Unsupported(alloc::format!(
15622 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15623 schema.columns[*col_idx].name
15624 ))
15625 })?;
15626 if matches!(v, spg_storage::Value::Bool(false)) {
15627 return Err(EngineError::Unsupported(alloc::format!(
15628 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15629 schema.columns[*col_idx].name
15630 )));
15631 }
15632 }
15633 }
15634 }
15635 Ok(())
15636}
15637
15638fn enforce_fk_inserts(
15639 catalog: &Catalog,
15640 child_table: &str,
15641 fks: &[spg_storage::ForeignKeyConstraint],
15642 rows: &[Vec<Value>],
15643) -> Result<(), EngineError> {
15644 for fk in fks {
15645 let parent_is_self = fk.parent_table == child_table;
15646 let parent = if parent_is_self {
15647 catalog.get(child_table).ok_or_else(|| {
15650 EngineError::Storage(StorageError::TableNotFound {
15651 name: child_table.into(),
15652 })
15653 })?
15654 } else {
15655 catalog.get(&fk.parent_table).ok_or_else(|| {
15656 EngineError::Storage(StorageError::TableNotFound {
15657 name: fk.parent_table.clone(),
15658 })
15659 })?
15660 };
15661 for (batch_idx, row_values) in rows.iter().enumerate() {
15662 if fk.local_columns.len() == 1 {
15666 let v = &row_values[fk.local_columns[0]];
15667 if matches!(v, Value::Null) {
15668 continue;
15669 }
15670 let parent_col = fk.parent_columns[0];
15671 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15672 EngineError::Unsupported(alloc::format!(
15673 "FOREIGN KEY column value of type {:?} is not index-eligible",
15674 v.data_type()
15675 ))
15676 })?;
15677 let present_committed = parent.indices().iter().any(|idx| {
15678 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15679 && idx.column_position == parent_col
15680 && idx.partial_predicate.is_none()
15681 && !idx.lookup_eq(&key).is_empty()
15682 });
15683 let present_in_batch = parent_is_self
15687 && rows[..batch_idx]
15688 .iter()
15689 .any(|earlier| earlier.get(parent_col) == Some(v));
15690 if !(present_committed || present_in_batch) {
15691 return Err(EngineError::Unsupported(alloc::format!(
15692 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15693 fk.parent_table,
15694 parent
15695 .schema()
15696 .columns
15697 .get(parent_col)
15698 .map_or("?", |c| c.name.as_str()),
15699 v,
15700 )));
15701 }
15702 } else {
15703 if fk
15707 .local_columns
15708 .iter()
15709 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15710 {
15711 continue;
15712 }
15713 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15714 let parent_match_committed = parent.rows().iter().any(|prow| {
15715 fk.parent_columns
15716 .iter()
15717 .enumerate()
15718 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15719 });
15720 let parent_match_in_batch = parent_is_self
15721 && rows[..batch_idx].iter().any(|earlier| {
15722 fk.parent_columns
15723 .iter()
15724 .enumerate()
15725 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15726 });
15727 if !(parent_match_committed || parent_match_in_batch) {
15728 return Err(EngineError::Unsupported(alloc::format!(
15729 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15730 fk.parent_table,
15731 )));
15732 }
15733 }
15734 }
15735 }
15736 Ok(())
15737}
15738
15739#[derive(Debug, Clone)]
15743struct FkChildStep {
15744 child_table: String,
15745 action: FkChildAction,
15746}
15747
15748#[derive(Debug, Clone)]
15749enum FkChildAction {
15750 Delete { positions: Vec<usize> },
15752 SetNull {
15756 positions: Vec<usize>,
15757 columns: Vec<usize>,
15758 },
15759 SetDefault {
15763 positions: Vec<usize>,
15764 columns: Vec<usize>,
15765 defaults: Vec<Value>,
15766 },
15767}
15768
15769fn plan_fk_parent_deletions(
15785 catalog: &Catalog,
15786 parent_table_name: &str,
15787 to_delete_positions: &[usize],
15788 to_delete_rows: &[Vec<Value>],
15789) -> Result<Vec<FkChildStep>, EngineError> {
15790 use alloc::collections::{BTreeMap, BTreeSet};
15791 if to_delete_rows.is_empty() {
15792 return Ok(Vec::new());
15793 }
15794 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15795 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15797 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15798 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15799 for &p in to_delete_positions {
15800 visited.insert((parent_table_name.to_string(), p));
15801 }
15802 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15803 .iter()
15804 .map(|r| (parent_table_name.to_string(), r.clone()))
15805 .collect();
15806 while let Some((cur_parent, parent_row)) = work.pop() {
15807 for child_name in catalog.table_names() {
15808 let child = catalog
15809 .get(&child_name)
15810 .expect("table_names → catalog.get round-trip is total");
15811 for fk in &child.schema().foreign_keys {
15812 if fk.parent_table != cur_parent {
15813 continue;
15814 }
15815 let parent_key: Vec<&Value> = fk
15816 .parent_columns
15817 .iter()
15818 .map(|&pi| &parent_row[pi])
15819 .collect();
15820 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15821 continue;
15822 }
15823 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15824 if child_name == cur_parent
15825 && visited.contains(&(child_name.clone(), child_row_idx))
15826 {
15827 continue;
15828 }
15829 let matches_key = fk
15830 .local_columns
15831 .iter()
15832 .enumerate()
15833 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15834 if !matches_key {
15835 continue;
15836 }
15837 match fk.on_delete {
15838 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15839 return Err(EngineError::Unsupported(alloc::format!(
15840 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15841 restricted by FK from {child_name:?}.{:?}",
15842 fk.local_columns,
15843 )));
15844 }
15845 spg_storage::FkAction::Cascade => {
15846 if visited.insert((child_name.clone(), child_row_idx)) {
15847 delete_plan
15848 .entry(child_name.clone())
15849 .or_default()
15850 .insert(child_row_idx);
15851 work.push((child_name.clone(), child_row.values.clone()));
15852 }
15853 }
15854 spg_storage::FkAction::SetNull => {
15855 for &li in &fk.local_columns {
15857 let col = child.schema().columns.get(li).ok_or_else(|| {
15858 EngineError::Unsupported(alloc::format!(
15859 "FK local column {li} missing in {child_name:?}"
15860 ))
15861 })?;
15862 if !col.nullable {
15863 return Err(EngineError::Unsupported(alloc::format!(
15864 "FOREIGN KEY ON DELETE SET NULL: column \
15865 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15866 col.name,
15867 )));
15868 }
15869 }
15870 let entry = setnull_plan.entry(child_name.clone()).or_default();
15871 for &li in &fk.local_columns {
15872 entry.insert((child_row_idx, li));
15873 }
15874 }
15875 spg_storage::FkAction::SetDefault => {
15876 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15878 for &li in &fk.local_columns {
15879 let col = child.schema().columns.get(li).ok_or_else(|| {
15880 EngineError::Unsupported(alloc::format!(
15881 "FK local column {li} missing in {child_name:?}"
15882 ))
15883 })?;
15884 let default = col.default.clone().ok_or_else(|| {
15885 EngineError::Unsupported(alloc::format!(
15886 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15887 {child_name:?}.{:?} has no DEFAULT declared",
15888 col.name,
15889 ))
15890 })?;
15891 entry.insert((child_row_idx, li), default);
15892 }
15893 }
15894 }
15895 }
15896 }
15897 }
15898 }
15899 let mut steps: Vec<FkChildStep> = Vec::new();
15907 for (child_table, entries) in setnull_plan {
15908 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15909 steps.push(FkChildStep {
15910 child_table,
15911 action: FkChildAction::SetNull { positions, columns },
15912 });
15913 }
15914 for (child_table, entries) in setdefault_plan {
15915 let mut positions = Vec::with_capacity(entries.len());
15916 let mut columns = Vec::with_capacity(entries.len());
15917 let mut defaults = Vec::with_capacity(entries.len());
15918 for ((p, c), v) in entries {
15919 positions.push(p);
15920 columns.push(c);
15921 defaults.push(v);
15922 }
15923 steps.push(FkChildStep {
15924 child_table,
15925 action: FkChildAction::SetDefault {
15926 positions,
15927 columns,
15928 defaults,
15929 },
15930 });
15931 }
15932 for (child_table, positions) in delete_plan {
15933 steps.push(FkChildStep {
15934 child_table,
15935 action: FkChildAction::Delete {
15936 positions: positions.into_iter().collect(),
15937 },
15938 });
15939 }
15940 Ok(steps)
15941}
15942
15943fn plan_fk_parent_updates(
15960 catalog: &Catalog,
15961 parent_table_name: &str,
15962 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15963) -> Result<Vec<FkChildStep>, EngineError> {
15964 use alloc::collections::BTreeMap;
15965 if plan_with_old.is_empty() {
15966 return Ok(Vec::new());
15967 }
15968 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15973 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15974 BTreeMap::new();
15975 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15976 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15978
15979 for child_name in catalog.table_names() {
15980 let child = catalog
15981 .get(&child_name)
15982 .expect("table_names → catalog.get total");
15983 for fk in &child.schema().foreign_keys {
15984 if fk.parent_table != parent_table_name {
15985 continue;
15986 }
15987 for (_pos, old_row, new_row) in plan_with_old {
15988 let key_changed = fk
15990 .parent_columns
15991 .iter()
15992 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15993 if !key_changed {
15994 continue;
15995 }
15996 let old_key: Vec<&Value> =
15998 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15999 if old_key.iter().any(|v| matches!(v, Value::Null)) {
16000 continue;
16002 }
16003 let new_key: Vec<&Value> =
16004 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
16005 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
16006 if child_name == parent_table_name
16009 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
16010 {
16011 continue;
16012 }
16013 let matches_key = fk
16014 .local_columns
16015 .iter()
16016 .enumerate()
16017 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
16018 if !matches_key {
16019 continue;
16020 }
16021 match fk.on_update {
16022 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
16023 return Err(EngineError::Unsupported(alloc::format!(
16024 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
16025 restricted by FK from {child_name:?}.{:?}",
16026 fk.local_columns,
16027 )));
16028 }
16029 spg_storage::FkAction::Cascade => {
16030 let entry = cascade_plan.entry(child_name.clone()).or_default();
16032 for (i, &li) in fk.local_columns.iter().enumerate() {
16033 entry.insert((child_row_idx, li), new_key[i].clone());
16034 }
16035 }
16036 spg_storage::FkAction::SetNull => {
16037 for &li in &fk.local_columns {
16038 let col = child.schema().columns.get(li).ok_or_else(|| {
16039 EngineError::Unsupported(alloc::format!(
16040 "FK local column {li} missing in {child_name:?}"
16041 ))
16042 })?;
16043 if !col.nullable {
16044 return Err(EngineError::Unsupported(alloc::format!(
16045 "FOREIGN KEY ON UPDATE SET NULL: column \
16046 {child_name:?}.{:?} is NOT NULL",
16047 col.name,
16048 )));
16049 }
16050 }
16051 let entry = setnull_plan.entry(child_name.clone()).or_default();
16052 for &li in &fk.local_columns {
16053 entry.insert((child_row_idx, li));
16054 }
16055 }
16056 spg_storage::FkAction::SetDefault => {
16057 let entry = setdefault_plan.entry(child_name.clone()).or_default();
16058 for &li in &fk.local_columns {
16059 let col = child.schema().columns.get(li).ok_or_else(|| {
16060 EngineError::Unsupported(alloc::format!(
16061 "FK local column {li} missing in {child_name:?}"
16062 ))
16063 })?;
16064 let default = col.default.clone().ok_or_else(|| {
16065 EngineError::Unsupported(alloc::format!(
16066 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
16067 {child_name:?}.{:?} has no DEFAULT",
16068 col.name,
16069 ))
16070 })?;
16071 entry.insert((child_row_idx, li), default);
16072 }
16073 }
16074 }
16075 }
16076 }
16077 }
16078 }
16079 let mut steps: Vec<FkChildStep> = Vec::new();
16082 for (child_table, entries) in cascade_plan {
16083 let mut positions = Vec::with_capacity(entries.len());
16084 let mut columns = Vec::with_capacity(entries.len());
16085 let mut defaults = Vec::with_capacity(entries.len());
16086 for ((p, c), v) in entries {
16087 positions.push(p);
16088 columns.push(c);
16089 defaults.push(v);
16090 }
16091 steps.push(FkChildStep {
16096 child_table,
16097 action: FkChildAction::SetDefault {
16098 positions,
16099 columns,
16100 defaults,
16101 },
16102 });
16103 }
16104 for (child_table, entries) in setnull_plan {
16105 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16106 steps.push(FkChildStep {
16107 child_table,
16108 action: FkChildAction::SetNull { positions, columns },
16109 });
16110 }
16111 for (child_table, entries) in setdefault_plan {
16112 let mut positions = Vec::with_capacity(entries.len());
16113 let mut columns = Vec::with_capacity(entries.len());
16114 let mut defaults = Vec::with_capacity(entries.len());
16115 for ((p, c), v) in entries {
16116 positions.push(p);
16117 columns.push(c);
16118 defaults.push(v);
16119 }
16120 steps.push(FkChildStep {
16121 child_table,
16122 action: FkChildAction::SetDefault {
16123 positions,
16124 columns,
16125 defaults,
16126 },
16127 });
16128 }
16129 let _ = delete_plan; Ok(steps)
16131}
16132
16133fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
16137 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
16138 EngineError::Storage(StorageError::TableNotFound {
16139 name: step.child_table.clone(),
16140 })
16141 })?;
16142 match &step.action {
16143 FkChildAction::Delete { positions } => {
16144 let _ = child.delete_rows(positions);
16145 }
16146 FkChildAction::SetNull { positions, columns } => {
16147 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
16148 }
16149 FkChildAction::SetDefault {
16150 positions,
16151 columns,
16152 defaults,
16153 } => {
16154 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
16155 }
16156 }
16157 Ok(())
16158}
16159
16160fn apply_per_cell_writes(
16166 child: &mut spg_storage::Table,
16167 positions: &[usize],
16168 columns: &[usize],
16169 mut value_for: impl FnMut(usize) -> Value,
16170) -> Result<(), EngineError> {
16171 use alloc::collections::BTreeMap;
16172 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
16173 for i in 0..positions.len() {
16174 by_row
16175 .entry(positions[i])
16176 .or_default()
16177 .push((columns[i], value_for(i)));
16178 }
16179 for (pos, mutations) in by_row {
16180 let mut new_values = child.rows()[pos].values.clone();
16181 for (col, v) in mutations {
16182 if let Some(slot) = new_values.get_mut(col) {
16183 *slot = v;
16184 }
16185 }
16186 child
16187 .update_row(pos, new_values)
16188 .map_err(EngineError::Storage)?;
16189 }
16190 Ok(())
16191}
16192
16193fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
16194 match a {
16195 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
16196 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
16197 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
16198 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
16199 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
16200 }
16201}
16202
16203fn resolve_column_default_free(
16209 col: &ColumnSchema,
16210 clock_fn: Option<ClockFn>,
16211) -> Result<Value, EngineError> {
16212 if let Some(rt) = &col.runtime_default {
16213 return eval_runtime_default_free(rt, col.ty, clock_fn);
16214 }
16215 Ok(col.default.clone().unwrap_or(Value::Null))
16216}
16217
16218fn eval_runtime_default_free(
16219 rt: &str,
16220 ty: DataType,
16221 clock_fn: Option<ClockFn>,
16222) -> Result<Value, EngineError> {
16223 let s = rt.trim().to_ascii_lowercase();
16224 let with_no_parens = s.trim_end_matches("()");
16230 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
16231 if with_no_parens.ends_with(')') {
16232 &with_no_parens[..open_idx]
16233 } else {
16234 with_no_parens
16235 }
16236 } else {
16237 with_no_parens
16238 };
16239 let now_us = match clock_fn {
16240 Some(f) => f(),
16241 None => 0,
16242 };
16243 let v = match canonical {
16244 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
16245 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
16246 "current_time" | "localtime" => Value::Timestamp(now_us),
16247 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
16253 other => {
16254 return Err(EngineError::Unsupported(alloc::format!(
16255 "runtime DEFAULT expression {other:?} not supported \
16256 (v7.17.0 whitelist: now() / current_timestamp / \
16257 current_date / current_time / localtimestamp / \
16258 localtime / gen_random_uuid() / \
16259 uuid_generate_v4())"
16260 )));
16261 }
16262 };
16263 coerce_value(v, ty, "DEFAULT", 0)
16264}
16265
16266fn is_runtime_default_expr(expr: &Expr) -> bool {
16272 match expr {
16273 Expr::FunctionCall { .. } => true,
16274 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
16275 _ => false,
16276 }
16277}
16278
16279fn canonicalize_set_value(
16292 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16293 col_idx: usize,
16294 col_name: &str,
16295 value: Value,
16296) -> Result<Value, EngineError> {
16297 let Some(variants) = lookup.get(&col_idx) else {
16298 return Ok(value);
16299 };
16300 match value {
16301 Value::Null => Ok(Value::Null),
16302 Value::Text(s) => {
16303 if s.is_empty() {
16304 return Ok(Value::Text(alloc::string::String::new()));
16305 }
16306 let mut present = alloc::vec![false; variants.len()];
16309 for raw in s.split(',') {
16310 let tok = raw.trim();
16311 if tok.is_empty() {
16312 continue;
16313 }
16314 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
16315 EngineError::Unsupported(alloc::format!(
16316 "column {col_name:?}: invalid SET token {tok:?}; \
16317 allowed: {variants:?}"
16318 ))
16319 })?;
16320 present[idx] = true;
16321 }
16322 let mut out = alloc::string::String::new();
16324 let mut first = true;
16325 for (i, keep) in present.iter().enumerate() {
16326 if !keep {
16327 continue;
16328 }
16329 if !first {
16330 out.push(',');
16331 }
16332 first = false;
16333 out.push_str(&variants[i]);
16334 }
16335 Ok(Value::Text(out))
16336 }
16337 other => Err(EngineError::Unsupported(alloc::format!(
16338 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16339 other.data_type()
16340 ))),
16341 }
16342}
16343
16344fn enforce_enum_label(
16345 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16346 col_idx: usize,
16347 col_name: &str,
16348 value: &Value,
16349) -> Result<(), EngineError> {
16350 if let Some(labels) = lookup.get(&col_idx) {
16351 match value {
16352 Value::Null => Ok(()),
16353 Value::Text(s) => {
16354 if labels.iter().any(|l| l == s) {
16355 Ok(())
16356 } else {
16357 Err(EngineError::Unsupported(alloc::format!(
16358 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16359 )))
16360 }
16361 }
16362 other => Err(EngineError::Unsupported(alloc::format!(
16363 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16364 other.data_type()
16365 ))),
16366 }
16367 } else {
16368 Ok(())
16369 }
16370}
16371
16372fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16373 let ty = column_type_to_data_type(c.ty);
16374 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16375 if let Some(name) = c.user_type_ref {
16382 schema.user_enum_type = Some(name);
16383 }
16384 if let Some(expr) = c.on_update_runtime {
16387 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16388 }
16389 schema.collation = match c.collation {
16393 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16394 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16395 };
16396 schema.is_unsigned = c.is_unsigned;
16399 schema.inline_enum_variants = c.inline_enum_variants;
16403 schema.inline_set_variants = c.inline_set_variants;
16407 if let Some(default_expr) = c.default {
16408 if is_runtime_default_expr(&default_expr) {
16414 let display = alloc::format!("{default_expr}");
16415 schema = schema.with_runtime_default(display);
16416 } else {
16417 let raw = literal_expr_to_value(default_expr)?;
16418 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16419 schema = schema.with_default(coerced);
16420 }
16421 }
16422 if c.auto_increment {
16423 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16425 return Err(EngineError::Unsupported(alloc::format!(
16426 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16427 )));
16428 }
16429 schema = schema.with_auto_increment();
16430 }
16431 Ok(schema)
16432}
16433
16434fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16439 let s = s.trim();
16440 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16441 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16443 if cleaned.len() % 2 != 0 {
16444 return Err("odd-length hex literal");
16445 }
16446 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16447 let cleaned_bytes = cleaned.as_bytes();
16448 for i in (0..cleaned_bytes.len()).step_by(2) {
16449 let hi = hex_nibble(cleaned_bytes[i])?;
16450 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16451 out.push((hi << 4) | lo);
16452 }
16453 return Ok(out);
16454 }
16455 let bytes = s.as_bytes();
16458 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16459 let mut i = 0;
16460 while i < bytes.len() {
16461 let b = bytes[i];
16462 if b == b'\\' && i + 1 < bytes.len() {
16463 let n = bytes[i + 1];
16464 if n == b'\\' {
16465 out.push(b'\\');
16466 i += 2;
16467 continue;
16468 }
16469 if n.is_ascii_digit()
16470 && i + 3 < bytes.len()
16471 && bytes[i + 2].is_ascii_digit()
16472 && bytes[i + 3].is_ascii_digit()
16473 {
16474 let oct = |x: u8| (x - b'0') as u32;
16475 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16476 if v <= 0xFF {
16477 out.push(v as u8);
16478 i += 4;
16479 continue;
16480 }
16481 }
16482 }
16483 out.push(b);
16484 i += 1;
16485 }
16486 Ok(out)
16487}
16488
16489fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16490 match b {
16491 b'0'..=b'9' => Ok(b - b'0'),
16492 b'a'..=b'f' => Ok(b - b'a' + 10),
16493 b'A'..=b'F' => Ok(b - b'A' + 10),
16494 _ => Err("invalid hex digit"),
16495 }
16496}
16497
16498fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16512 let mut has_text = false;
16513 let mut has_bigint = false;
16514 let mut has_int = false;
16515 for v in &items {
16516 match v {
16517 Value::Null => {}
16518 Value::Text(_) | Value::Json(_) => has_text = true,
16519 Value::BigInt(_) => has_bigint = true,
16520 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16521 _ => has_text = true,
16522 }
16523 }
16524 if has_text || (!has_bigint && !has_int) {
16525 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16526 .into_iter()
16527 .map(|v| match v {
16528 Value::Null => None,
16529 Value::Text(s) | Value::Json(s) => Some(s),
16530 other => Some(alloc::format!("{other:?}")),
16531 })
16532 .collect();
16533 return Value::TextArray(out);
16534 }
16535 if has_bigint {
16536 let out: alloc::vec::Vec<Option<i64>> = items
16537 .into_iter()
16538 .map(|v| match v {
16539 Value::Null => None,
16540 Value::Int(n) => Some(i64::from(n)),
16541 Value::SmallInt(n) => Some(i64::from(n)),
16542 Value::BigInt(n) => Some(n),
16543 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16544 })
16545 .collect();
16546 return Value::BigIntArray(out);
16547 }
16548 let out: alloc::vec::Vec<Option<i32>> = items
16549 .into_iter()
16550 .map(|v| match v {
16551 Value::Null => None,
16552 Value::Int(n) => Some(n),
16553 Value::SmallInt(n) => Some(i32::from(n)),
16554 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16555 })
16556 .collect();
16557 Value::IntArray(out)
16558}
16559
16560fn decode_text_array_literal(
16561 s: &str,
16562) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16563 let trimmed = s.trim();
16564 let inner = trimmed
16565 .strip_prefix('{')
16566 .and_then(|x| x.strip_suffix('}'))
16567 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16568 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16569 if inner.trim().is_empty() {
16570 return Ok(out);
16571 }
16572 let bytes = inner.as_bytes();
16573 let mut i = 0;
16574 while i <= bytes.len() {
16575 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16577 i += 1;
16578 }
16579 if i < bytes.len() && bytes[i] == b'"' {
16581 i += 1; let mut buf = alloc::string::String::new();
16583 while i < bytes.len() && bytes[i] != b'"' {
16584 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16585 buf.push(bytes[i + 1] as char);
16586 i += 2;
16587 } else {
16588 buf.push(bytes[i] as char);
16589 i += 1;
16590 }
16591 }
16592 if i >= bytes.len() {
16593 return Err("unterminated quoted element");
16594 }
16595 i += 1; out.push(Some(buf));
16597 } else {
16598 let start = i;
16600 while i < bytes.len() && bytes[i] != b',' {
16601 i += 1;
16602 }
16603 let raw = inner[start..i].trim();
16604 if raw.eq_ignore_ascii_case("NULL") {
16605 out.push(None);
16606 } else {
16607 out.push(Some(alloc::string::ToString::to_string(raw)));
16608 }
16609 }
16610 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16612 i += 1;
16613 }
16614 if i >= bytes.len() {
16615 break;
16616 }
16617 if bytes[i] != b',' {
16618 return Err("expected ',' between TEXT[] elements");
16619 }
16620 i += 1;
16621 }
16622 Ok(out)
16623}
16624
16625fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16630 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16631 out.push('{');
16632 for (i, item) in items.iter().enumerate() {
16633 if i > 0 {
16634 out.push(',');
16635 }
16636 match item {
16637 None => out.push_str("NULL"),
16638 Some(s) => {
16639 let needs_quote = s.is_empty()
16640 || s.eq_ignore_ascii_case("NULL")
16641 || s.chars()
16642 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16643 if needs_quote {
16644 out.push('"');
16645 for c in s.chars() {
16646 if c == '"' || c == '\\' {
16647 out.push('\\');
16648 }
16649 out.push(c);
16650 }
16651 out.push('"');
16652 } else {
16653 out.push_str(s);
16654 }
16655 }
16656 }
16657 }
16658 out.push('}');
16659 out
16660}
16661
16662fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16666 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16667 out.push_str("\\x");
16668 for byte in b {
16669 let hi = byte >> 4;
16670 let lo = byte & 0x0F;
16671 out.push(hex_digit(hi));
16672 out.push(hex_digit(lo));
16673 }
16674 out
16675}
16676
16677const fn hex_digit(n: u8) -> char {
16678 match n {
16679 0..=9 => (b'0' + n) as char,
16680 10..=15 => (b'a' + n - 10) as char,
16681 _ => '?',
16682 }
16683}
16684
16685fn parse_hstore_str(
16697 s: &str,
16698) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16699 let bytes = s.as_bytes();
16700 let mut i = 0;
16701 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16702 let skip_ws = |bytes: &[u8], i: &mut usize| {
16703 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16704 *i += 1;
16705 }
16706 };
16707 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16708 if *i >= bytes.len() {
16709 return None;
16710 }
16711 if bytes[*i] == b'"' {
16712 *i += 1;
16713 let mut out = alloc::string::String::new();
16714 while *i < bytes.len() {
16715 match bytes[*i] {
16716 b'"' => {
16717 *i += 1;
16718 return Some(out);
16719 }
16720 b'\\' if *i + 1 < bytes.len() => {
16721 out.push(bytes[*i + 1] as char);
16722 *i += 2;
16723 }
16724 c => {
16725 out.push(c as char);
16726 *i += 1;
16727 }
16728 }
16729 }
16730 None
16731 } else {
16732 let start = *i;
16733 while *i < bytes.len()
16734 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16735 {
16736 *i += 1;
16737 }
16738 if *i == start {
16739 return None;
16740 }
16741 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16742 }
16743 };
16744 skip_ws(bytes, &mut i);
16745 while i < bytes.len() {
16746 let key = parse_token(bytes, &mut i)?;
16747 skip_ws(bytes, &mut i);
16748 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16749 return None;
16750 }
16751 i += 2;
16752 skip_ws(bytes, &mut i);
16753 let val_token = if i + 4 <= bytes.len()
16755 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16756 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16757 {
16758 i += 4;
16759 None
16760 } else {
16761 Some(parse_token(bytes, &mut i)?)
16762 };
16763 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16765 out[pos] = (key, val_token);
16766 } else {
16767 out.push((key, val_token));
16768 }
16769 skip_ws(bytes, &mut i);
16770 if i >= bytes.len() {
16771 break;
16772 }
16773 if bytes[i] == b',' {
16774 i += 1;
16775 skip_ws(bytes, &mut i);
16776 continue;
16777 }
16778 return None;
16779 }
16780 Some(out)
16781}
16782
16783fn format_hstore_str(
16787 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16788) -> alloc::string::String {
16789 let mut out = alloc::string::String::new();
16790 for (i, (k, v)) in pairs.iter().enumerate() {
16791 if i > 0 {
16792 out.push_str(", ");
16793 }
16794 out.push('"');
16795 out.push_str(k);
16796 out.push_str("\"=>");
16797 match v {
16798 None => out.push_str("NULL"),
16799 Some(val) => {
16800 out.push('"');
16801 out.push_str(val);
16802 out.push('"');
16803 }
16804 }
16805 }
16806 out
16807}
16808
16809pub fn format_hstore_text(
16812 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16813) -> alloc::string::String {
16814 format_hstore_str(pairs)
16815}
16816
16817fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16822 let s = s.trim();
16823 let outer = s
16824 .strip_prefix('{')
16825 .and_then(|x| x.strip_suffix('}'))
16826 .ok_or("missing outer '{...}' braces")?;
16827 let trimmed = outer.trim();
16828 if trimmed.is_empty() {
16829 return Ok(Vec::new());
16830 }
16831 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16832 let mut i = 0;
16833 let bytes = trimmed.as_bytes();
16834 while i < bytes.len() {
16835 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16836 i += 1;
16837 }
16838 if i >= bytes.len() {
16839 break;
16840 }
16841 if bytes[i] != b'{' {
16842 return Err("expected '{' opening a row");
16843 }
16844 i += 1;
16845 let row_start = i;
16846 let mut depth = 1;
16847 while i < bytes.len() && depth > 0 {
16848 match bytes[i] {
16849 b'{' => depth += 1,
16850 b'}' => depth -= 1,
16851 _ => {}
16852 }
16853 if depth > 0 {
16854 i += 1;
16855 }
16856 }
16857 if depth != 0 {
16858 return Err("unbalanced '{...}' in row");
16859 }
16860 let row_text = &trimmed[row_start..i];
16861 i += 1;
16862 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16863 Vec::new()
16864 } else {
16865 row_text.split(',').map(|t| t.trim().to_string()).collect()
16866 };
16867 rows.push(cells);
16868 }
16869 if let Some(first) = rows.first() {
16870 let cols = first.len();
16871 for r in &rows {
16872 if r.len() != cols {
16873 return Err("ragged 2D array (rows have different column counts)");
16874 }
16875 }
16876 }
16877 Ok(rows)
16878}
16879
16880fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16881 let raw = split_2d_literal(s)?;
16882 raw.into_iter()
16883 .map(|row| {
16884 row.into_iter()
16885 .map(|cell| {
16886 if cell.eq_ignore_ascii_case("NULL") {
16887 Ok(None)
16888 } else {
16889 cell.parse::<i32>()
16890 .map(Some)
16891 .map_err(|_| "invalid int element")
16892 }
16893 })
16894 .collect()
16895 })
16896 .collect()
16897}
16898
16899fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16900 let raw = split_2d_literal(s)?;
16901 raw.into_iter()
16902 .map(|row| {
16903 row.into_iter()
16904 .map(|cell| {
16905 if cell.eq_ignore_ascii_case("NULL") {
16906 Ok(None)
16907 } else {
16908 cell.parse::<i64>()
16909 .map(Some)
16910 .map_err(|_| "invalid bigint element")
16911 }
16912 })
16913 .collect()
16914 })
16915 .collect()
16916}
16917
16918fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16919 let raw = split_2d_literal(s)?;
16920 Ok(raw
16921 .into_iter()
16922 .map(|row| {
16923 row.into_iter()
16924 .map(|cell| {
16925 if cell.eq_ignore_ascii_case("NULL") {
16926 None
16927 } else {
16928 Some(cell.trim_matches('"').to_string())
16929 }
16930 })
16931 .collect()
16932 })
16933 .collect())
16934}
16935
16936fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16937 let mut out = alloc::string::String::from("{");
16938 for (i, row) in rows.iter().enumerate() {
16939 if i > 0 {
16940 out.push(',');
16941 }
16942 out.push('{');
16943 for (j, cell) in row.iter().enumerate() {
16944 if j > 0 {
16945 out.push(',');
16946 }
16947 match cell {
16948 None => out.push_str("NULL"),
16949 Some(n) => out.push_str(&alloc::format!("{n}")),
16950 }
16951 }
16952 out.push('}');
16953 }
16954 out.push('}');
16955 out
16956}
16957
16958fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16959 let mut out = alloc::string::String::from("{");
16960 for (i, row) in rows.iter().enumerate() {
16961 if i > 0 {
16962 out.push(',');
16963 }
16964 out.push('{');
16965 for (j, cell) in row.iter().enumerate() {
16966 if j > 0 {
16967 out.push(',');
16968 }
16969 match cell {
16970 None => out.push_str("NULL"),
16971 Some(n) => out.push_str(&alloc::format!("{n}")),
16972 }
16973 }
16974 out.push('}');
16975 }
16976 out.push('}');
16977 out
16978}
16979
16980fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16981 let mut out = alloc::string::String::from("{");
16982 for (i, row) in rows.iter().enumerate() {
16983 if i > 0 {
16984 out.push(',');
16985 }
16986 out.push('{');
16987 for (j, cell) in row.iter().enumerate() {
16988 if j > 0 {
16989 out.push(',');
16990 }
16991 match cell {
16992 None => out.push_str("NULL"),
16993 Some(s) => out.push_str(s),
16994 }
16995 }
16996 out.push('}');
16997 }
16998 out.push('}');
16999 out
17000}
17001
17002pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
17005 format_int_2d_text(rows)
17006}
17007pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
17008 format_bigint_2d_text(rows)
17009}
17010pub fn format_text_2d_text_pub(
17011 rows: &[Vec<Option<alloc::string::String>>],
17012) -> alloc::string::String {
17013 format_text_2d_text(rows)
17014}
17015
17016fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17021 let s = s.trim();
17022 if s.eq_ignore_ascii_case("empty") {
17023 return Some(Value::Range {
17024 kind,
17025 lower: None,
17026 upper: None,
17027 lower_inc: false,
17028 upper_inc: false,
17029 empty: true,
17030 });
17031 }
17032 let bytes = s.as_bytes();
17033 if bytes.len() < 3 {
17034 return None;
17035 }
17036 let lower_inc = match bytes[0] {
17037 b'[' => true,
17038 b'(' => false,
17039 _ => return None,
17040 };
17041 let upper_inc = match bytes[bytes.len() - 1] {
17042 b']' => true,
17043 b')' => false,
17044 _ => return None,
17045 };
17046 let inner = &s[1..s.len() - 1];
17047 let (lo_text, up_text) = inner.split_once(',')?;
17048 let lower = if lo_text.is_empty() {
17049 None
17050 } else {
17051 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
17052 };
17053 let upper = if up_text.is_empty() {
17054 None
17055 } else {
17056 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
17057 };
17058 Some(Value::Range {
17059 kind,
17060 lower,
17061 upper,
17062 lower_inc,
17063 upper_inc,
17064 empty: false,
17065 })
17066}
17067
17068fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17071 let text = text.trim().trim_matches('"');
17072 use spg_storage::RangeKind as K;
17073 match kind {
17074 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
17075 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
17076 K::Num => {
17077 let dot = text.find('.');
17080 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
17081 let digits: alloc::string::String = text
17082 .chars()
17083 .filter(|c| *c == '-' || c.is_ascii_digit())
17084 .collect();
17085 let scaled: i128 = digits.parse().ok()?;
17086 Some(Value::Numeric { scaled, scale })
17087 }
17088 K::Ts | K::TsTz => {
17089 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
17094 }
17095 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
17096 }
17097}
17098
17099pub fn format_range_text(v: &Value) -> alloc::string::String {
17103 format_range_str(v)
17104}
17105
17106fn format_range_str(v: &Value) -> alloc::string::String {
17107 let Value::Range {
17108 lower,
17109 upper,
17110 lower_inc,
17111 upper_inc,
17112 empty,
17113 ..
17114 } = v
17115 else {
17116 return alloc::string::String::new();
17117 };
17118 if *empty {
17119 return "empty".into();
17120 }
17121 let mut out = alloc::string::String::new();
17122 out.push(if *lower_inc { '[' } else { '(' });
17123 if let Some(l) = lower {
17124 out.push_str(&format_range_element(l));
17125 }
17126 out.push(',');
17127 if let Some(u) = upper {
17128 out.push_str(&format_range_element(u));
17129 }
17130 out.push(if *upper_inc { ']' } else { ')' });
17131 out
17132}
17133
17134fn format_range_element(v: &Value) -> alloc::string::String {
17135 match v {
17136 Value::Int(n) => alloc::format!("{n}"),
17137 Value::BigInt(n) => alloc::format!("{n}"),
17138 Value::Date(d) => crate::eval::format_date(*d),
17139 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
17140 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
17141 other => alloc::format!("{other:?}"),
17142 }
17143}
17144
17145fn parse_money_str(s: &str) -> Option<i64> {
17156 let s = s.trim();
17157 let (neg, rest) = match s.strip_prefix('-') {
17158 Some(r) => (true, r.trim_start()),
17159 None => (false, s),
17160 };
17161 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
17162 let (int_part, frac_part) = match rest.split_once('.') {
17163 Some((i, f)) => (i, Some(f)),
17164 None => (rest, None),
17165 };
17166 if int_part.is_empty() {
17167 return None;
17168 }
17169 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
17171 for b in int_part.bytes() {
17172 match b {
17173 b',' => {}
17174 b'0'..=b'9' => int_digits.push(b as char),
17175 _ => return None,
17176 }
17177 }
17178 if int_digits.is_empty() {
17179 return None;
17180 }
17181 let dollars: i64 = int_digits.parse().ok()?;
17182 let cents: i64 = match frac_part {
17183 None => 0,
17184 Some(f) => {
17185 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
17186 return None;
17187 }
17188 let padded = if f.len() == 1 {
17189 alloc::format!("{f}0")
17190 } else {
17191 f.to_string()
17192 };
17193 padded.parse().ok()?
17194 }
17195 };
17196 let total = dollars.checked_mul(100)?.checked_add(cents)?;
17197 Some(if neg { -total } else { total })
17198}
17199
17200fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
17211 let s = s.trim();
17212 let bytes = s.as_bytes();
17216 let sign_pos = bytes
17217 .iter()
17218 .enumerate()
17219 .rev()
17220 .find(|&(_, &b)| b == b'+' || b == b'-')
17221 .map(|(i, _)| i)?;
17222 if sign_pos == 0 {
17223 return None; }
17225 let time_part = &s[..sign_pos];
17226 let offset_part = &s[sign_pos..];
17227 let us = parse_time_str(time_part)?;
17228 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
17229 let offset_body = &offset_part[1..];
17230 let (hh_str, mm_str) = match offset_body.split_once(':') {
17231 Some((h, m)) => (h, m),
17232 None => (offset_body, "0"),
17233 };
17234 let hh: i32 = hh_str.parse().ok()?;
17235 let mm: i32 = mm_str.parse().ok()?;
17236 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
17237 return None;
17238 }
17239 let total = sign * (hh * 3600 + mm * 60);
17240 if total.abs() > 50_400 {
17241 return None;
17242 }
17243 Some((us, total))
17244}
17245
17246fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
17251 if n == 0 || (1901..=2155).contains(&n) {
17252 return Ok(Value::Year(n as u16));
17255 }
17256 Err(EngineError::Eval(EvalError::TypeMismatch {
17257 detail: alloc::format!(
17258 "year value out of range: {n} (column `{col_name}`; \
17259 MySQL accepts 0 or 1901..=2155)"
17260 ),
17261 }))
17262}
17263
17264fn parse_time_str(s: &str) -> Option<i64> {
17276 let s = s.trim();
17277 let (hms, frac) = match s.split_once('.') {
17278 Some((h, f)) => (h, Some(f)),
17279 None => (s, None),
17280 };
17281 let mut parts = hms.split(':');
17282 let hh: u32 = parts.next()?.parse().ok()?;
17283 let mm: u32 = parts.next()?.parse().ok()?;
17284 let ss: u32 = parts.next()?.parse().ok()?;
17285 if parts.next().is_some() {
17286 return None;
17287 }
17288 if hh > 23 || mm > 59 || ss > 59 {
17289 return None;
17290 }
17291 let frac_us: i64 = match frac {
17292 None => 0,
17293 Some(f) => {
17294 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
17295 return None;
17296 }
17297 let mut padded = alloc::string::String::with_capacity(6);
17299 padded.push_str(f);
17300 while padded.len() < 6 {
17301 padded.push('0');
17302 }
17303 padded.parse().ok()?
17304 }
17305 };
17306 Some(
17307 i64::from(hh) * 3_600_000_000
17308 + i64::from(mm) * 60_000_000
17309 + i64::from(ss) * 1_000_000
17310 + frac_us,
17311 )
17312}
17313
17314const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
17315 match t {
17316 ColumnTypeName::SmallInt => DataType::SmallInt,
17317 ColumnTypeName::Int => DataType::Int,
17318 ColumnTypeName::BigInt => DataType::BigInt,
17319 ColumnTypeName::Float => DataType::Float,
17320 ColumnTypeName::Text => DataType::Text,
17321 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17322 ColumnTypeName::Char(n) => DataType::Char(n),
17323 ColumnTypeName::Bool => DataType::Bool,
17324 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17325 dim,
17326 encoding: match encoding {
17327 SqlVecEncoding::F32 => VecEncoding::F32,
17328 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17329 SqlVecEncoding::F16 => VecEncoding::F16,
17330 },
17331 },
17332 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17333 ColumnTypeName::Date => DataType::Date,
17334 ColumnTypeName::Timestamp => DataType::Timestamp,
17335 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17336 ColumnTypeName::Json => DataType::Json,
17337 ColumnTypeName::Jsonb => DataType::Jsonb,
17338 ColumnTypeName::Bytes => DataType::Bytes,
17339 ColumnTypeName::TextArray => DataType::TextArray,
17340 ColumnTypeName::IntArray => DataType::IntArray,
17341 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17342 ColumnTypeName::TsVector => DataType::TsVector,
17343 ColumnTypeName::TsQuery => DataType::TsQuery,
17344 ColumnTypeName::Uuid => DataType::Uuid,
17345 ColumnTypeName::Time => DataType::Time,
17346 ColumnTypeName::Year => DataType::Year,
17347 ColumnTypeName::TimeTz => DataType::TimeTz,
17348 ColumnTypeName::Money => DataType::Money,
17349 ColumnTypeName::Range(k) => DataType::Range(match k {
17350 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17351 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17352 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17353 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17354 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17355 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17356 }),
17357 ColumnTypeName::Hstore => DataType::Hstore,
17358 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17359 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17360 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17361 }
17362}
17363
17364fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17368 match expr {
17369 Expr::Literal(l) => Ok(literal_to_value(l)),
17370 Expr::Cast { expr, target } => {
17371 let inner_value = literal_expr_to_value(*expr)?;
17372 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17373 }
17374 Expr::Unary {
17375 op: UnOp::Neg,
17376 expr,
17377 } => match *expr {
17378 Expr::Literal(Literal::Integer(n)) => {
17379 let neg = n.checked_neg().ok_or_else(|| {
17382 EngineError::Unsupported("integer literal overflow on negation".into())
17383 })?;
17384 Ok(int_value_for(neg))
17385 }
17386 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17387 other => Err(EngineError::Unsupported(alloc::format!(
17388 "unary minus over non-literal expression: {other:?}"
17389 ))),
17390 },
17391 Expr::Array(items) => {
17399 let mut materialised: alloc::vec::Vec<Value> =
17400 alloc::vec::Vec::with_capacity(items.len());
17401 for elem in items {
17402 materialised.push(literal_expr_to_value(elem)?);
17403 }
17404 Ok(array_literal_widen(materialised))
17405 }
17406 other => {
17419 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17420 let ctx = EvalContext::new(&empty_schema, None);
17421 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17422 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17423 }
17424 }
17425}
17426
17427fn literal_to_value(l: Literal) -> Value {
17428 match l {
17429 Literal::Integer(n) => int_value_for(n),
17430 Literal::Float(x) => Value::Float(x),
17431 Literal::String(s) => Value::Text(s),
17432 Literal::Bool(b) => Value::Bool(b),
17433 Literal::Null => Value::Null,
17434 Literal::Vector(v) => Value::Vector(v),
17435 Literal::TextArray(items) => Value::TextArray(items),
17436 Literal::IntArray(items) => Value::IntArray(items),
17437 Literal::BigIntArray(items) => Value::BigIntArray(items),
17438 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17439 }
17440}
17441
17442fn int_value_for(n: i64) -> Value {
17446 if let Ok(small) = i32::try_from(n) {
17447 Value::Int(small)
17448 } else {
17449 Value::BigInt(n)
17450 }
17451}
17452
17453#[allow(clippy::too_many_lines)]
17459fn check_unsigned_range(
17464 v: &Value,
17465 schema: &ColumnSchema,
17466 position: usize,
17467) -> Result<(), EngineError> {
17468 if !schema.is_unsigned {
17469 return Ok(());
17470 }
17471 let n = match v {
17472 Value::SmallInt(x) => i64::from(*x),
17473 Value::Int(x) => i64::from(*x),
17474 Value::BigInt(x) => *x,
17475 _ => return Ok(()), };
17477 if n < 0 {
17478 return Err(EngineError::Unsupported(alloc::format!(
17479 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17480 schema.name
17481 )));
17482 }
17483 Ok(())
17484}
17485
17486fn coerce_value(
17487 v: Value,
17488 expected: DataType,
17489 col_name: &str,
17490 position: usize,
17491) -> Result<Value, EngineError> {
17492 if v.is_null() {
17493 return Ok(Value::Null);
17494 }
17495 let actual = v.data_type().expect("non-null");
17496 if actual == expected {
17497 return Ok(v);
17498 }
17499 let coerced = match (v, expected) {
17500 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17501 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17502 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17503 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17504 i128::from(n),
17505 precision,
17506 scale,
17507 col_name,
17508 )?),
17509 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17510 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17511 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17512 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17513 i128::from(n),
17514 precision,
17515 scale,
17516 col_name,
17517 )?),
17518 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17519 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17520 #[allow(clippy::cast_precision_loss)]
17521 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17522 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17523 i128::from(n),
17524 precision,
17525 scale,
17526 col_name,
17527 )?),
17528 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17529 Some(numeric_from_float(x, precision, scale, col_name)?)
17530 }
17531 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17542 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17543 return Err(EngineError::Eval(EvalError::TypeMismatch {
17544 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17545 }));
17546 };
17547 Some(numeric_rescale(
17548 mantissa, src_scale, precision, scale, col_name,
17549 )?)
17550 }
17551 (Value::Text(s), DataType::Date) => {
17553 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17554 EngineError::Eval(EvalError::TypeMismatch {
17555 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17556 })
17557 })?;
17558 Some(Value::Date(d))
17559 }
17560 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17567 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17568 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17569 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17570 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17571 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17572 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17573 _ => None,
17574 },
17575 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17584 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17585 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17586 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17590 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17591 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17599 (Value::Text(s), DataType::Bytes) => {
17606 let bytes = decode_bytea_literal(&s).map_err(|e| {
17607 EngineError::Eval(EvalError::TypeMismatch {
17608 detail: alloc::format!(
17609 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17610 ),
17611 })
17612 })?;
17613 Some(Value::Bytes(bytes))
17614 }
17615 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17619 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17627 Some(b) => Some(Value::Uuid(b)),
17628 None => {
17629 return Err(EngineError::Eval(EvalError::TypeMismatch {
17630 detail: alloc::format!(
17631 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17632 ),
17633 }));
17634 }
17635 },
17636 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17641 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17647 Some(us) => Some(Value::Time(us)),
17648 None => {
17649 return Err(EngineError::Eval(EvalError::TypeMismatch {
17650 detail: alloc::format!(
17651 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17652 ),
17653 }));
17654 }
17655 },
17656 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17658 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17663 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17664 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17665 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17669 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17670 Err(_) => {
17671 return Err(EngineError::Eval(EvalError::TypeMismatch {
17672 detail: alloc::format!(
17673 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17674 ),
17675 }));
17676 }
17677 },
17678 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17680 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17684 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17685 None => {
17686 return Err(EngineError::Eval(EvalError::TypeMismatch {
17687 detail: alloc::format!(
17688 "invalid input syntax for type time with time zone: \
17689 {s:?} (column `{col_name}`)"
17690 ),
17691 }));
17692 }
17693 },
17694 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17696 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17697 }
17698 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17702 Some(c) => Some(Value::Money(c)),
17703 None => {
17704 return Err(EngineError::Eval(EvalError::TypeMismatch {
17705 detail: alloc::format!(
17706 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17707 ),
17708 }));
17709 }
17710 },
17711 (Value::SmallInt(n), DataType::Money) => {
17715 Some(Value::Money(i64::from(n).saturating_mul(100)))
17716 }
17717 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17718 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17719 (Value::Float(x), DataType::Money) => {
17720 let scaled = x * 100.0;
17723 let cents = if scaled >= 0.0 {
17724 (scaled + 0.5) as i64
17725 } else {
17726 (scaled - 0.5) as i64
17727 };
17728 Some(Value::Money(cents))
17729 }
17730 (Value::Numeric { scaled, scale }, DataType::Money) => {
17731 let cents = if scale == 2 {
17734 scaled
17735 } else if scale < 2 {
17736 let mult = 10_i128.pow(u32::from(2 - scale));
17737 scaled.saturating_mul(mult)
17738 } else {
17739 let div = 10_i128.pow(u32::from(scale - 2));
17740 let half = div / 2;
17741 let bias = if scaled >= 0 { half } else { -half };
17742 (scaled + bias) / div
17743 };
17744 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17745 }
17746 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17748 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17752 Some(v) => Some(v),
17753 None => {
17754 return Err(EngineError::Eval(EvalError::TypeMismatch {
17755 detail: alloc::format!(
17756 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17757 ),
17758 }));
17759 }
17760 },
17761 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17763 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17765 Some(pairs) => Some(Value::Hstore(pairs)),
17766 None => {
17767 return Err(EngineError::Eval(EvalError::TypeMismatch {
17768 detail: alloc::format!(
17769 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17770 ),
17771 }));
17772 }
17773 },
17774 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17776 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17779 Ok(m) => Some(Value::IntArray2D(m)),
17780 Err(e) => {
17781 return Err(EngineError::Eval(EvalError::TypeMismatch {
17782 detail: alloc::format!(
17783 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17784 ),
17785 }));
17786 }
17787 },
17788 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17789 Ok(m) => Some(Value::BigIntArray2D(m)),
17790 Err(e) => {
17791 return Err(EngineError::Eval(EvalError::TypeMismatch {
17792 detail: alloc::format!(
17793 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17794 ),
17795 }));
17796 }
17797 },
17798 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17799 Ok(m) => Some(Value::TextArray2D(m)),
17800 Err(e) => {
17801 return Err(EngineError::Eval(EvalError::TypeMismatch {
17802 detail: alloc::format!(
17803 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17804 ),
17805 }));
17806 }
17807 },
17808 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17810 (Value::BigIntArray2D(rows), DataType::Text) => {
17811 Some(Value::Text(format_bigint_2d_text(&rows)))
17812 }
17813 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17814 (Value::Text(s), DataType::TextArray) => {
17819 let arr = decode_text_array_literal(&s).map_err(|e| {
17820 EngineError::Eval(EvalError::TypeMismatch {
17821 detail: alloc::format!(
17822 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17823 ),
17824 })
17825 })?;
17826 Some(Value::TextArray(arr))
17827 }
17828 (Value::Text(s), DataType::IntArray) => {
17834 let arr = decode_text_array_literal(&s).map_err(|e| {
17835 EngineError::Eval(EvalError::TypeMismatch {
17836 detail: alloc::format!(
17837 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17838 ),
17839 })
17840 })?;
17841 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17842 for elem in arr {
17843 match elem {
17844 None => out.push(None),
17845 Some(t) => {
17846 let n: i32 = t.parse().map_err(|_| {
17847 EngineError::Eval(EvalError::TypeMismatch {
17848 detail: alloc::format!(
17849 "cannot parse {t:?} as INT element for `{col_name}`"
17850 ),
17851 })
17852 })?;
17853 out.push(Some(n));
17854 }
17855 }
17856 }
17857 Some(Value::IntArray(out))
17858 }
17859 (Value::Text(s), DataType::BigIntArray) => {
17860 let arr = decode_text_array_literal(&s).map_err(|e| {
17861 EngineError::Eval(EvalError::TypeMismatch {
17862 detail: alloc::format!(
17863 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17864 ),
17865 })
17866 })?;
17867 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17868 for elem in arr {
17869 match elem {
17870 None => out.push(None),
17871 Some(t) => {
17872 let n: i64 = t.parse().map_err(|_| {
17873 EngineError::Eval(EvalError::TypeMismatch {
17874 detail: alloc::format!(
17875 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17876 ),
17877 })
17878 })?;
17879 out.push(Some(n));
17880 }
17881 }
17882 }
17883 Some(Value::BigIntArray(out))
17884 }
17885 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17889 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17898 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17899 EngineError::Eval(EvalError::TypeMismatch {
17900 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17901 })
17902 })?;
17903 if parsed.len() != dim as usize {
17904 return Err(EngineError::Eval(EvalError::TypeMismatch {
17905 detail: alloc::format!(
17906 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17907 parsed.len()
17908 ),
17909 }));
17910 }
17911 Some(match encoding {
17912 VecEncoding::F32 => Value::Vector(parsed),
17913 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17914 VecEncoding::F16 => {
17915 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17916 }
17917 })
17918 }
17919 (Value::Text(s), DataType::TsVector) => {
17929 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17930 EngineError::Eval(EvalError::TypeMismatch {
17931 detail: alloc::format!(
17932 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17933 ),
17934 })
17935 })?;
17936 Some(Value::TsVector(lexs))
17937 }
17938 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17939 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17940 EngineError::Eval(EvalError::TypeMismatch {
17941 detail: alloc::format!(
17942 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17943 ),
17944 })
17945 })?;
17946 Some(Value::Timestamp(t))
17947 }
17948 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17951 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17952 }
17953 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17957 (Value::Timestamp(t), DataType::Date) => {
17958 let days = t.div_euclid(86_400_000_000);
17959 i32::try_from(days).ok().map(Value::Date)
17960 }
17961 (
17962 Value::Numeric {
17963 scaled,
17964 scale: src_scale,
17965 },
17966 DataType::Numeric { precision, scale },
17967 ) => Some(numeric_rescale(
17968 scaled, src_scale, precision, scale, col_name,
17969 )?),
17970 #[allow(clippy::cast_precision_loss)]
17971 (Value::Numeric { scaled, scale }, DataType::Float) => {
17972 let mut div = 1.0_f64;
17973 for _ in 0..scale {
17974 div *= 10.0;
17975 }
17976 Some(Value::Float((scaled as f64) / div))
17977 }
17978 (Value::Numeric { scaled, scale }, DataType::Int) => {
17979 let truncated = numeric_truncate_to_integer(scaled, scale);
17980 i32::try_from(truncated).ok().map(Value::Int)
17981 }
17982 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17983 let truncated = numeric_truncate_to_integer(scaled, scale);
17984 i64::try_from(truncated).ok().map(Value::BigInt)
17985 }
17986 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17987 let truncated = numeric_truncate_to_integer(scaled, scale);
17988 i16::try_from(truncated).ok().map(Value::SmallInt)
17989 }
17990 (Value::Text(s), DataType::Varchar(max)) => {
17992 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17993 Some(Value::Text(s))
17994 } else {
17995 return Err(EngineError::Unsupported(alloc::format!(
17996 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17997 {} chars",
17998 s.chars().count()
17999 )));
18000 }
18001 }
18002 (
18010 Value::Vector(v),
18011 DataType::Vector {
18012 dim,
18013 encoding: VecEncoding::Sq8,
18014 },
18015 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
18016 (
18021 Value::Vector(v),
18022 DataType::Vector {
18023 dim,
18024 encoding: VecEncoding::F16,
18025 },
18026 ) if v.len() == dim as usize => Some(Value::HalfVector(
18027 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
18028 )),
18029 (Value::Text(s), DataType::Char(size)) => {
18033 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
18034 if len > size {
18035 return Err(EngineError::Unsupported(alloc::format!(
18036 "value for CHAR({size}) column `{col_name}` exceeds length: \
18037 {len} chars"
18038 )));
18039 }
18040 let need = (size - len) as usize;
18041 let mut padded = s;
18042 padded.reserve(need);
18043 for _ in 0..need {
18044 padded.push(' ');
18045 }
18046 Some(Value::Text(padded))
18047 }
18048 _ => None,
18049 };
18050 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
18051 column: col_name.into(),
18052 expected,
18053 actual,
18054 position,
18055 }))
18056}
18057
18058fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
18064 use core::fmt::Write;
18065 let mut out = alloc::string::String::from("(");
18066 for (i, a) in args.iter().enumerate() {
18067 if i > 0 {
18068 out.push_str(", ");
18069 }
18070 match a.mode {
18071 spg_sql::ast::FunctionArgMode::In => {}
18072 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
18073 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
18074 }
18075 if let Some(n) = &a.name {
18076 out.push_str(n);
18077 out.push(' ');
18078 }
18079 match &a.ty {
18080 spg_sql::ast::FunctionArgType::Typed(t) => {
18081 let _ = write!(out, "{t}");
18082 }
18083 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
18084 }
18085 }
18086 out.push(')');
18087 out
18088}
18089
18090fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
18099 match expr {
18100 spg_sql::ast::Expr::FunctionCall { name, args } => {
18101 name.eq_ignore_ascii_case("unnest") && args.len() == 1
18102 }
18103 _ => false,
18104 }
18105}
18106
18107fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
18111 match expr {
18112 spg_sql::ast::Expr::FunctionCall { name, args }
18113 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
18114 {
18115 Some(&args[0])
18116 }
18117 _ => None,
18118 }
18119}
18120
18121fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
18126 match v {
18127 Value::Null => Ok(Vec::new()),
18128 Value::TextArray(items) => Ok(items
18129 .iter()
18130 .map(|opt| {
18131 opt.as_ref()
18132 .map(|s| Value::Text(s.clone()))
18133 .unwrap_or(Value::Null)
18134 })
18135 .collect()),
18136 Value::IntArray(items) => Ok(items
18137 .iter()
18138 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
18139 .collect()),
18140 Value::BigIntArray(items) => Ok(items
18141 .iter()
18142 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
18143 .collect()),
18144 other => Err(EngineError::Eval(EvalError::TypeMismatch {
18145 detail: alloc::format!(
18146 "unnest() expects an array argument, got {:?}",
18147 other.data_type()
18148 ),
18149 })),
18150 }
18151}
18152
18153#[cfg(test)]
18154mod tests {
18155 use super::*;
18156 use alloc::vec;
18157
18158 fn unwrap_command_ok(r: &QueryResult) -> usize {
18159 match r {
18160 QueryResult::CommandOk { affected, .. } => *affected,
18161 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
18162 }
18163 }
18164
18165 #[test]
18166 fn update_seek_positions_engages_on_indexed_eq() {
18167 let mut e = Engine::new();
18168 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
18169 .unwrap();
18170 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
18171 for i in 0..100 {
18172 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
18173 .unwrap();
18174 }
18175 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
18176 .expect("parse");
18177 let Statement::Update(u) = stmt else {
18178 panic!("expected Update, got {stmt:?}");
18179 };
18180 let w = u.where_.as_ref().expect("where");
18181 let table = e.catalog().get("b").unwrap();
18182 let schema_cols = table.schema().columns.clone();
18183 let Expr::Binary { lhs, op, rhs } = w else {
18185 panic!("WHERE not Binary: {w:?}");
18186 };
18187 assert_eq!(*op, BinOp::Eq, "op not Eq");
18188 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
18189 assert!(
18190 pair.is_some(),
18191 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
18192 );
18193 let (col_pos, value) = pair.unwrap();
18194 assert!(
18195 table.index_on(col_pos).is_some(),
18196 "no index on col {col_pos}"
18197 );
18198 assert!(
18199 IndexKey::from_value(&value).is_some(),
18200 "IndexKey::from_value None for {value:?}"
18201 );
18202 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
18203 assert_eq!(positions, Some(vec![42]), "seek did not engage");
18204 }
18205
18206 #[test]
18207 fn create_table_registers_schema() {
18208 let mut e = Engine::new();
18209 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
18210 .unwrap();
18211 assert_eq!(e.catalog().table_count(), 1);
18212 let t = e.catalog().get("foo").unwrap();
18213 assert_eq!(t.schema().columns.len(), 2);
18214 assert_eq!(t.schema().columns[0].ty, DataType::Int);
18215 assert!(!t.schema().columns[0].nullable);
18216 assert_eq!(t.schema().columns[1].ty, DataType::Text);
18217 }
18218
18219 #[test]
18220 fn create_table_vector_default_is_f32_encoded() {
18221 let mut e = Engine::new();
18222 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
18223 let t = e.catalog().get("t").unwrap();
18224 assert_eq!(
18225 t.schema().columns[0].ty,
18226 DataType::Vector {
18227 dim: 8,
18228 encoding: VecEncoding::F32,
18229 },
18230 );
18231 }
18232
18233 #[test]
18234 fn create_table_vector_using_sq8_succeeds() {
18235 let mut e = Engine::new();
18239 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
18240 let t = e.catalog().get("t").unwrap();
18241 assert_eq!(
18242 t.schema().columns[0].ty,
18243 DataType::Vector {
18244 dim: 8,
18245 encoding: VecEncoding::Sq8,
18246 },
18247 );
18248 }
18249
18250 #[test]
18251 fn insert_into_sq8_column_quantises_f32_payload() {
18252 let mut e = Engine::new();
18259 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18260 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18261 .unwrap();
18262 let t = e.catalog().get("t").unwrap();
18263 assert_eq!(t.rows().len(), 1);
18264 match &t.rows()[0].values[0] {
18265 Value::Sq8Vector(q) => {
18266 assert_eq!(q.bytes.len(), 4);
18267 assert!((q.min - 0.0).abs() < 1e-6);
18269 assert!((q.max - 1.0).abs() < 1e-6);
18270 }
18271 other => panic!("expected Sq8Vector cell, got {other:?}"),
18272 }
18273 }
18274
18275 #[test]
18276 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
18277 let mut e = Engine::new();
18284 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18285 .unwrap();
18286 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18287 .unwrap();
18288 let t = e.catalog().get("t").unwrap();
18289 assert_eq!(t.rows().len(), 1);
18290 match &t.rows()[0].values[0] {
18291 Value::HalfVector(h) => {
18292 assert_eq!(h.dim(), 4);
18293 let back = h.to_f32_vec();
18294 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
18295 for (g, e) in back.iter().zip(expected.iter()) {
18296 assert!(
18297 (g - e).abs() < 1e-6,
18298 "{g} vs {e} should be exact on f16 grid"
18299 );
18300 }
18301 }
18302 other => panic!("expected HalfVector cell, got {other:?}"),
18303 }
18304 }
18305
18306 #[test]
18307 fn alter_index_rebuild_in_place_succeeds() {
18308 let mut e = Engine::new();
18313 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18314 .unwrap();
18315 for i in 0..8_i32 {
18316 #[allow(clippy::cast_precision_loss)]
18317 let base = (i as f32) * 0.1;
18318 e.execute(&alloc::format!(
18319 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
18320 b1 = base + 0.01,
18321 b2 = base + 0.02,
18322 ))
18323 .unwrap();
18324 }
18325 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18326 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18327 assert_eq!(
18329 e.catalog().get("t").unwrap().schema().columns[1].ty,
18330 DataType::Vector {
18331 dim: 3,
18332 encoding: VecEncoding::F32,
18333 },
18334 );
18335 }
18336
18337 #[test]
18338 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18339 let mut e = Engine::new();
18344 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18345 .unwrap();
18346 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18347 .unwrap();
18348 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18349 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18350 .unwrap();
18351 let t = e.catalog().get("t").unwrap();
18352 assert_eq!(
18353 t.schema().columns[1].ty,
18354 DataType::Vector {
18355 dim: 4,
18356 encoding: VecEncoding::Sq8,
18357 },
18358 );
18359 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18360 }
18361
18362 #[test]
18363 fn alter_index_rebuild_unknown_index_errors() {
18364 let mut e = Engine::new();
18365 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18366 assert!(
18367 matches!(
18368 &err,
18369 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18370 ),
18371 "got: {err}"
18372 );
18373 }
18374
18375 #[test]
18376 fn alter_index_rebuild_on_btree_index_errors() {
18377 let mut e = Engine::new();
18380 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18381 e.execute("INSERT INTO t VALUES (1)").unwrap();
18382 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18383 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18384 assert!(
18385 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18386 "got: {err}"
18387 );
18388 }
18389
18390 #[test]
18391 fn prepared_insert_substitutes_placeholders() {
18392 let mut e = Engine::new();
18398 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18399 .unwrap();
18400 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18401 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18402 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18403 .unwrap();
18404 }
18405 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18407 let QueryResult::Rows { rows, .. } = rows_result else {
18408 panic!("expected Rows")
18409 };
18410 assert_eq!(rows.len(), 3);
18411 }
18412
18413 #[test]
18414 fn prepared_select_with_placeholder_filters_rows() {
18415 let mut e = Engine::new();
18416 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18417 .unwrap();
18418 for i in 0..10_i32 {
18419 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18420 .unwrap();
18421 }
18422 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18423 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18424 else {
18425 panic!("expected Rows")
18426 };
18427 assert_eq!(rows.len(), 1);
18429 assert_eq!(rows[0].values[0], Value::Int(5));
18430 }
18431
18432 #[test]
18433 fn prepared_too_few_params_errors() {
18434 let mut e = Engine::new();
18435 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18436 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18437 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18438 assert!(
18439 matches!(
18440 &err,
18441 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18442 ),
18443 "got: {err}"
18444 );
18445 }
18446
18447 #[test]
18448 fn bytea_cast_round_trips_text_input() {
18449 let e = Engine::new();
18452 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18453 let QueryResult::Rows { rows, .. } = r else {
18454 panic!("expected Rows")
18455 };
18456 assert_eq!(rows.len(), 1);
18457 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18458 }
18459
18460 #[test]
18461 fn bytea_cast_pg_escape_hex_form() {
18462 let e = Engine::new();
18466 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18467 let QueryResult::Rows { rows, .. } = r else {
18468 panic!("expected Rows")
18469 };
18470 assert_eq!(
18471 rows[0].values[0],
18472 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18473 );
18474 }
18475
18476 #[test]
18477 fn bytea_cast_chains_through_octet_length() {
18478 let e = Engine::new();
18482 let r = e
18483 .execute_readonly("SELECT octet_length('hello'::bytea)")
18484 .unwrap();
18485 let QueryResult::Rows { rows, .. } = r else {
18486 panic!("expected Rows")
18487 };
18488 match &rows[0].values[0] {
18489 Value::Int(n) => assert_eq!(*n, 5),
18490 Value::BigInt(n) => assert_eq!(*n, 5),
18491 other => panic!("expected integer length, got {other:?}"),
18492 }
18493 }
18494
18495 #[test]
18496 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18497 let mut e = Engine::new();
18503 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18504 .unwrap();
18505 for i in 0..10_i32 {
18506 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18507 .unwrap();
18508 }
18509 let snapshot = e.clone_snapshot();
18510 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18511 let QueryResult::Rows { rows, .. } =
18512 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18513 .unwrap()
18514 else {
18515 panic!("expected Rows")
18516 };
18517 assert_eq!(rows.len(), 1);
18518 assert_eq!(rows[0].values[0], Value::Int(5));
18519 }
18520
18521 #[test]
18522 fn readonly_prepared_on_snapshot_rejects_writes() {
18523 let mut e = Engine::new();
18527 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18528 let snapshot = e.clone_snapshot();
18529 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18530 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18531 .unwrap_err();
18532 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18533 }
18534
18535 #[test]
18536 fn readonly_prepared_on_snapshot_frozen_view() {
18537 let mut e = Engine::new();
18543 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18544 e.execute("INSERT INTO t VALUES (1)").unwrap();
18545 let snapshot = e.clone_snapshot();
18546 e.execute("INSERT INTO t VALUES (2)").unwrap();
18547 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18548 let QueryResult::Rows { rows, .. } =
18549 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18550 .unwrap()
18551 else {
18552 panic!("expected Rows")
18553 };
18554 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18555 }
18556
18557 #[test]
18558 fn describe_prepared_on_snapshot_resolves_columns() {
18559 let mut e = Engine::new();
18564 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18565 .unwrap();
18566 let snapshot = e.clone_snapshot();
18567 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18568 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18569 assert_eq!(cols.len(), 2);
18570 assert_eq!(cols[0].name, "id");
18571 assert_eq!(cols[0].ty, DataType::Int);
18572 assert_eq!(cols[1].name, "name");
18573 assert_eq!(cols[1].ty, DataType::Text);
18574 }
18575
18576 #[test]
18577 fn insert_into_half_column_dim_mismatch_errors() {
18578 let mut e = Engine::new();
18579 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18580 .unwrap();
18581 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18582 assert!(matches!(
18583 &err,
18584 EngineError::Storage(StorageError::TypeMismatch { .. })
18585 ));
18586 }
18587
18588 #[test]
18589 fn insert_into_sq8_column_dim_mismatch_errors() {
18590 let mut e = Engine::new();
18595 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18596 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18597 assert!(
18598 matches!(
18599 &err,
18600 EngineError::Storage(StorageError::TypeMismatch { .. })
18601 ),
18602 "got: {err}",
18603 );
18604 }
18605
18606 #[test]
18607 fn create_table_duplicate_errors() {
18608 let mut e = Engine::new();
18609 e.execute("CREATE TABLE foo (a INT)").unwrap();
18610 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18611 assert!(matches!(
18612 err,
18613 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18614 ));
18615 }
18616
18617 #[test]
18618 fn insert_into_unknown_table_errors() {
18619 let mut e = Engine::new();
18620 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18621 assert!(matches!(
18622 err,
18623 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18624 ));
18625 }
18626
18627 #[test]
18628 fn insert_happy_path_reports_one_affected() {
18629 let mut e = Engine::new();
18630 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18631 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18632 assert_eq!(unwrap_command_ok(&r), 1);
18633 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18634 }
18635
18636 #[test]
18637 fn insert_arity_mismatch_propagates() {
18638 let mut e = Engine::new();
18639 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18640 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18641 assert!(matches!(
18642 err,
18643 EngineError::Storage(StorageError::ArityMismatch { .. })
18644 ));
18645 }
18646
18647 #[test]
18648 fn insert_negative_integer_via_unary_minus() {
18649 let mut e = Engine::new();
18650 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18651 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18652 let rows = e.catalog().get("foo").unwrap().rows();
18653 assert_eq!(rows[0].values[0], Value::Int(-7));
18654 }
18655
18656 #[test]
18657 fn insert_expression_evaluated_against_empty_context() {
18658 let mut e = Engine::new();
18663 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18664 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18665 let rows = e.catalog().get("foo").unwrap().rows();
18666 assert_eq!(rows[0].values[0], Value::Int(3));
18667 }
18668
18669 #[test]
18670 fn select_star_returns_all_rows_in_insertion_order() {
18671 let mut e = Engine::new();
18672 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18673 .unwrap();
18674 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18675 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18676 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18677
18678 let r = e.execute("SELECT * FROM foo").unwrap();
18679 let QueryResult::Rows { columns, rows } = r else {
18680 panic!("expected Rows")
18681 };
18682 assert_eq!(columns.len(), 2);
18683 assert_eq!(columns[0].name, "a");
18684 assert_eq!(rows.len(), 3);
18685 assert_eq!(
18686 rows[1].values,
18687 vec![Value::Int(2), Value::Text("two".into())]
18688 );
18689 }
18690
18691 #[test]
18692 fn select_star_on_empty_table_returns_zero_rows() {
18693 let mut e = Engine::new();
18694 e.execute("CREATE TABLE foo (a INT)").unwrap();
18695 let r = e.execute("SELECT * FROM foo").unwrap();
18696 match r {
18697 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18698 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18699 }
18700 }
18701
18702 fn make_three_row_users(e: &mut Engine) {
18705 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18706 .unwrap();
18707 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18708 .unwrap();
18709 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18710 .unwrap();
18711 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18712 .unwrap();
18713 }
18714
18715 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18716 match r {
18717 QueryResult::Rows { columns, rows } => (columns, rows),
18718 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18719 }
18720 }
18721
18722 #[test]
18723 fn where_filter_passes_only_true_rows() {
18724 let mut e = Engine::new();
18725 make_three_row_users(&mut e);
18726 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18727 let (_, rows) = unwrap_rows(r);
18728 assert_eq!(rows.len(), 2);
18729 assert_eq!(rows[0].values[0], Value::Int(2));
18730 assert_eq!(rows[1].values[0], Value::Int(3));
18731 }
18732
18733 #[test]
18734 fn where_with_null_result_filters_out_row() {
18735 let mut e = Engine::new();
18736 make_three_row_users(&mut e);
18737 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18739 let (_, rows) = unwrap_rows(r);
18740 assert_eq!(rows.len(), 1);
18741 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18742 }
18743
18744 #[test]
18745 fn projection_named_columns() {
18746 let mut e = Engine::new();
18747 make_three_row_users(&mut e);
18748 let r = e.execute("SELECT name, score FROM users").unwrap();
18749 let (cols, rows) = unwrap_rows(r);
18750 assert_eq!(cols.len(), 2);
18751 assert_eq!(cols[0].name, "name");
18752 assert_eq!(cols[1].name, "score");
18753 assert_eq!(rows.len(), 3);
18754 assert_eq!(
18755 rows[0].values,
18756 vec![Value::Text("alice".into()), Value::Int(90)]
18757 );
18758 }
18759
18760 #[test]
18761 fn projection_with_column_alias() {
18762 let mut e = Engine::new();
18763 make_three_row_users(&mut e);
18764 let r = e
18765 .execute("SELECT name AS who FROM users WHERE id = 1")
18766 .unwrap();
18767 let (cols, rows) = unwrap_rows(r);
18768 assert_eq!(cols[0].name, "who");
18769 assert_eq!(rows.len(), 1);
18770 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18771 }
18772
18773 #[test]
18774 fn qualified_column_with_table_alias_resolves() {
18775 let mut e = Engine::new();
18776 make_three_row_users(&mut e);
18777 let r = e
18778 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18779 .unwrap();
18780 let (cols, rows) = unwrap_rows(r);
18781 assert_eq!(cols.len(), 2);
18782 assert_eq!(rows.len(), 2);
18783 }
18784
18785 #[test]
18786 fn qualified_column_with_wrong_alias_errors() {
18787 let mut e = Engine::new();
18788 make_three_row_users(&mut e);
18789 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18790 assert!(matches!(
18791 err,
18792 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18793 ));
18794 }
18795
18796 #[test]
18797 fn select_unknown_column_errors_in_projection() {
18798 let mut e = Engine::new();
18799 make_three_row_users(&mut e);
18800 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18801 assert!(matches!(
18802 err,
18803 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18804 ));
18805 }
18806
18807 #[test]
18808 fn where_unknown_column_errors() {
18809 let mut e = Engine::new();
18810 make_three_row_users(&mut e);
18811 let err = e
18812 .execute("SELECT * FROM users WHERE ghost = 1")
18813 .unwrap_err();
18814 assert!(matches!(
18815 err,
18816 EngineError::Eval(EvalError::ColumnNotFound { .. })
18817 ));
18818 }
18819
18820 #[test]
18821 fn expression_projection_evaluates_and_renders() {
18822 let mut e = Engine::new();
18825 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18826 e.execute("INSERT INTO t VALUES (3)").unwrap();
18827 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18828 assert_eq!(rows.len(), 1);
18829 assert_eq!(rows[0].values[0], Value::Int(3));
18832 }
18833
18834 #[test]
18835 fn select_unknown_table_errors() {
18836 let mut e = Engine::new();
18837 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18838 assert!(matches!(
18839 err,
18840 EngineError::Storage(StorageError::TableNotFound { .. })
18841 ));
18842 }
18843
18844 #[test]
18845 fn invalid_sql_returns_parse_error() {
18846 let mut e = Engine::new();
18849 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18850 assert!(matches!(err, EngineError::Parse(_)));
18851 }
18852
18853 #[test]
18856 fn create_index_registers_on_table() {
18857 let mut e = Engine::new();
18858 make_three_row_users(&mut e);
18859 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18860 let t = e.catalog().get("users").unwrap();
18861 assert_eq!(t.indices().len(), 1);
18862 assert_eq!(t.indices()[0].name, "by_name");
18863 }
18864
18865 #[test]
18866 fn create_index_on_unknown_table_errors() {
18867 let mut e = Engine::new();
18868 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18869 assert!(matches!(
18870 err,
18871 EngineError::Storage(StorageError::TableNotFound { .. })
18872 ));
18873 }
18874
18875 #[test]
18876 fn create_index_on_unknown_column_errors() {
18877 let mut e = Engine::new();
18878 make_three_row_users(&mut e);
18879 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18880 assert!(matches!(
18881 err,
18882 EngineError::Storage(StorageError::ColumnNotFound { .. })
18883 ));
18884 }
18885
18886 #[test]
18887 fn select_eq_uses_index_returns_same_rows_as_scan() {
18888 let mut without = Engine::new();
18892 make_three_row_users(&mut without);
18893 let mut with = Engine::new();
18894 make_three_row_users(&mut with);
18895 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18896
18897 let q = "SELECT * FROM users WHERE id = 2";
18898 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18899 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18900 assert_eq!(no_idx_rows, idx_rows);
18901 assert_eq!(idx_rows.len(), 1);
18902 }
18903
18904 #[test]
18905 fn select_eq_with_no_matching_index_value_returns_empty() {
18906 let mut e = Engine::new();
18907 make_three_row_users(&mut e);
18908 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18909 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18910 assert_eq!(rows.len(), 0);
18911 }
18912
18913 #[test]
18916 fn begin_sets_in_transaction_flag() {
18917 let mut e = Engine::new();
18918 assert!(!e.in_transaction());
18919 e.execute("BEGIN").unwrap();
18920 assert!(e.in_transaction());
18921 }
18922
18923 #[test]
18924 fn double_begin_errors() {
18925 let mut e = Engine::new();
18926 e.execute("BEGIN").unwrap();
18927 let err = e.execute("BEGIN").unwrap_err();
18928 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18929 }
18930
18931 #[test]
18932 fn commit_without_begin_errors() {
18933 let mut e = Engine::new();
18934 let err = e.execute("COMMIT").unwrap_err();
18935 assert_eq!(err, EngineError::NoActiveTransaction);
18936 }
18937
18938 #[test]
18939 fn rollback_without_begin_errors() {
18940 let mut e = Engine::new();
18941 let err = e.execute("ROLLBACK").unwrap_err();
18942 assert_eq!(err, EngineError::NoActiveTransaction);
18943 }
18944
18945 #[test]
18946 fn commit_applies_shadow_to_committed_catalog() {
18947 let mut e = Engine::new();
18948 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18949 e.execute("BEGIN").unwrap();
18950 e.execute("INSERT INTO t VALUES (1)").unwrap();
18951 e.execute("INSERT INTO t VALUES (2)").unwrap();
18952 e.execute("COMMIT").unwrap();
18953 assert!(!e.in_transaction());
18954 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18955 }
18956
18957 #[test]
18958 fn rollback_discards_shadow() {
18959 let mut e = Engine::new();
18960 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18961 e.execute("BEGIN").unwrap();
18962 e.execute("INSERT INTO t VALUES (1)").unwrap();
18963 e.execute("INSERT INTO t VALUES (2)").unwrap();
18964 e.execute("ROLLBACK").unwrap();
18965 assert!(!e.in_transaction());
18966 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18967 }
18968
18969 #[test]
18970 fn select_during_tx_sees_uncommitted_writes_own_session() {
18971 let mut e = Engine::new();
18974 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18975 e.execute("BEGIN").unwrap();
18976 e.execute("INSERT INTO t VALUES (42)").unwrap();
18977 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18978 assert_eq!(rows.len(), 1);
18979 assert_eq!(rows[0].values[0], Value::Int(42));
18980 }
18981
18982 #[test]
18983 fn snapshot_with_no_users_is_bare_catalog_format() {
18984 let mut e = Engine::new();
18985 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18986 let bytes = e.snapshot();
18987 assert_eq!(
18988 &bytes[..8],
18989 b"SPGDB001",
18990 "must be the bare v3.x catalog magic"
18991 );
18992 let e2 = Engine::restore_envelope(&bytes).unwrap();
18993 assert!(e2.users().is_empty());
18994 assert_eq!(e2.catalog().table_count(), 1);
18995 }
18996
18997 #[test]
18998 fn snapshot_with_users_round_trips_both_via_envelope() {
18999 let mut e = Engine::new();
19000 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19001 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
19002 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
19003 .unwrap();
19004 let bytes = e.snapshot();
19005 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
19006 let e2 = Engine::restore_envelope(&bytes).unwrap();
19007 assert_eq!(e2.users().len(), 2);
19008 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
19009 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
19010 assert_eq!(e2.verify_user("alice", "wrong"), None);
19011 assert_eq!(e2.catalog().table_count(), 1);
19012 }
19013
19014 #[test]
19015 fn ddl_inside_tx_also_rolled_back() {
19016 let mut e = Engine::new();
19017 e.execute("BEGIN").unwrap();
19018 e.execute("CREATE TABLE t (v INT)").unwrap();
19019 e.execute("SELECT * FROM t").unwrap();
19021 e.execute("ROLLBACK").unwrap();
19022 let err = e.execute("SELECT * FROM t").unwrap_err();
19024 assert!(matches!(
19025 err,
19026 EngineError::Storage(StorageError::TableNotFound { .. })
19027 ));
19028 }
19029
19030 #[test]
19033 fn create_publication_lands_in_catalog() {
19034 let mut e = Engine::new();
19035 assert!(e.publications().is_empty());
19036 e.execute("CREATE PUBLICATION pub_a").unwrap();
19037 assert_eq!(e.publications().len(), 1);
19038 assert!(e.publications().contains("pub_a"));
19039 }
19040
19041 #[test]
19042 fn create_publication_duplicate_errors() {
19043 let mut e = Engine::new();
19044 e.execute("CREATE PUBLICATION pub_a").unwrap();
19045 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
19046 assert!(
19047 alloc::format!("{err:?}").contains("DuplicateName"),
19048 "got {err:?}"
19049 );
19050 }
19051
19052 #[test]
19053 fn drop_publication_silent_when_absent() {
19054 let mut e = Engine::new();
19055 let r = e.execute("DROP PUBLICATION nope").unwrap();
19058 match r {
19059 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19060 other => panic!("expected CommandOk, got {other:?}"),
19061 }
19062 }
19063
19064 #[test]
19065 fn drop_publication_present_reports_one_affected() {
19066 let mut e = Engine::new();
19067 e.execute("CREATE PUBLICATION pub_a").unwrap();
19068 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
19069 match r {
19070 QueryResult::CommandOk {
19071 affected,
19072 modified_catalog,
19073 } => {
19074 assert_eq!(affected, 1);
19075 assert!(modified_catalog);
19076 }
19077 other => panic!("expected CommandOk, got {other:?}"),
19078 }
19079 assert!(e.publications().is_empty());
19080 }
19081
19082 #[test]
19083 fn publications_persist_across_snapshot_restore() {
19084 let mut e = Engine::new();
19089 e.execute("CREATE PUBLICATION pub_a").unwrap();
19090 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
19091 .unwrap();
19092 let snap = e.snapshot();
19093 let e2 = Engine::restore_envelope(&snap).unwrap();
19094 assert_eq!(e2.publications().len(), 2);
19095 assert!(e2.publications().contains("pub_a"));
19096 assert!(e2.publications().contains("pub_b"));
19097 }
19098
19099 #[test]
19100 fn create_publication_allowed_inside_transaction() {
19101 let mut e = Engine::new();
19105 e.execute("BEGIN").unwrap();
19106 e.execute("CREATE PUBLICATION pub_a").unwrap();
19107 e.execute("COMMIT").unwrap();
19108 assert!(e.publications().contains("pub_a"));
19109 }
19110
19111 #[test]
19114 fn create_publication_for_table_list_lands_with_scope() {
19115 let mut e = Engine::new();
19116 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19117 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
19118 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
19119 .unwrap();
19120 let scope = e.publications().get("pub_a").cloned();
19121 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
19122 panic!("expected ForTables scope, got {scope:?}")
19123 };
19124 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19125 }
19126
19127 #[test]
19128 fn create_publication_all_tables_except_lands_with_scope() {
19129 let mut e = Engine::new();
19130 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
19131 .unwrap();
19132 let scope = e.publications().get("pub_a").cloned();
19133 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
19134 panic!("expected AllTablesExcept scope, got {scope:?}")
19135 };
19136 assert_eq!(ts, alloc::vec!["t3".to_string()]);
19137 }
19138
19139 #[test]
19140 fn show_publications_empty_returns_zero_rows() {
19141 let e = Engine::new();
19142 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19143 let QueryResult::Rows { rows, columns } = r else {
19144 panic!()
19145 };
19146 assert!(rows.is_empty());
19147 assert_eq!(columns.len(), 3);
19148 assert_eq!(columns[0].name, "name");
19149 assert_eq!(columns[1].name, "scope");
19150 assert_eq!(columns[2].name, "table_count");
19151 }
19152
19153 #[test]
19154 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
19155 let mut e = Engine::new();
19156 e.execute("CREATE PUBLICATION z_pub").unwrap();
19157 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
19158 .unwrap();
19159 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
19160 .unwrap();
19161 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19162 let QueryResult::Rows { rows, .. } = r else {
19163 panic!()
19164 };
19165 assert_eq!(rows.len(), 3);
19166 let names: Vec<&str> = rows
19168 .iter()
19169 .map(|r| {
19170 if let Value::Text(s) = &r.values[0] {
19171 s.as_str()
19172 } else {
19173 panic!()
19174 }
19175 })
19176 .collect();
19177 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
19178 match &rows[0].values[1] {
19180 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
19181 other => panic!("expected Text, got {other:?}"),
19182 }
19183 assert_eq!(rows[0].values[2], Value::Int(2));
19184 match &rows[1].values[1] {
19186 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
19187 other => panic!("expected Text, got {other:?}"),
19188 }
19189 assert_eq!(rows[1].values[2], Value::Int(1));
19190 match &rows[2].values[1] {
19192 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
19193 other => panic!("expected Text, got {other:?}"),
19194 }
19195 assert_eq!(rows[2].values[2], Value::Null);
19196 }
19197
19198 #[test]
19199 fn for_list_scopes_persist_across_snapshot() {
19200 let mut e = Engine::new();
19203 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
19204 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
19205 .unwrap();
19206 let snap = e.snapshot();
19207 let e2 = Engine::restore_envelope(&snap).unwrap();
19208 assert_eq!(e2.publications().len(), 2);
19209 let p1 = e2.publications().get("p1").cloned();
19210 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
19211 panic!("p1 scope lost: {p1:?}")
19212 };
19213 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19214 let p2 = e2.publications().get("p2").cloned();
19215 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
19216 panic!("p2 scope lost: {p2:?}")
19217 };
19218 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
19219 }
19220
19221 #[test]
19224 fn create_subscription_lands_in_catalog_with_defaults() {
19225 let mut e = Engine::new();
19226 e.execute(
19227 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
19228 )
19229 .unwrap();
19230 let s = e.subscriptions().get("sub_a").cloned().expect("present");
19231 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
19232 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
19233 assert!(s.enabled);
19234 assert_eq!(s.last_received_pos, 0);
19235 }
19236
19237 #[test]
19238 fn create_subscription_duplicate_name_errors() {
19239 let mut e = Engine::new();
19240 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
19241 .unwrap();
19242 let err = e
19243 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
19244 .unwrap_err();
19245 assert!(
19246 alloc::format!("{err:?}").contains("DuplicateName"),
19247 "got {err:?}"
19248 );
19249 }
19250
19251 #[test]
19252 fn drop_subscription_silent_when_absent() {
19253 let mut e = Engine::new();
19254 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
19255 match r {
19256 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19257 other => panic!("expected CommandOk, got {other:?}"),
19258 }
19259 }
19260
19261 #[test]
19262 fn subscription_advance_updates_last_pos_monotone() {
19263 let mut e = Engine::new();
19264 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19265 .unwrap();
19266 assert!(e.subscription_advance("s", 100));
19267 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19268 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19270 assert!(e.subscription_advance("s", 200));
19271 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
19272 assert!(!e.subscription_advance("missing", 1));
19273 }
19274
19275 #[test]
19276 fn show_subscriptions_returns_rows_ordered_by_name() {
19277 let mut e = Engine::new();
19278 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
19279 .unwrap();
19280 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
19281 .unwrap();
19282 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
19283 let QueryResult::Rows { rows, columns } = r else {
19284 panic!()
19285 };
19286 assert_eq!(rows.len(), 2);
19287 assert_eq!(columns.len(), 5);
19288 assert_eq!(columns[0].name, "name");
19289 assert_eq!(columns[4].name, "last_received_pos");
19290 let names: Vec<&str> = rows
19292 .iter()
19293 .map(|r| {
19294 if let Value::Text(s) = &r.values[0] {
19295 s.as_str()
19296 } else {
19297 panic!()
19298 }
19299 })
19300 .collect();
19301 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
19302 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
19304 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
19305 assert_eq!(rows[0].values[3], Value::Bool(true));
19306 assert_eq!(rows[0].values[4], Value::BigInt(0));
19307 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
19309 }
19310
19311 #[test]
19312 fn subscriptions_persist_across_snapshot_envelope_v4() {
19313 let mut e = Engine::new();
19314 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
19315 .unwrap();
19316 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
19317 .unwrap();
19318 e.subscription_advance("s2", 42);
19319 let snap = e.snapshot();
19320 let e2 = Engine::restore_envelope(&snap).unwrap();
19321 assert_eq!(e2.subscriptions().len(), 2);
19322 let s1 = e2.subscriptions().get("s1").unwrap();
19323 assert_eq!(s1.conn_str, "h=A");
19324 assert_eq!(
19325 s1.publications,
19326 alloc::vec!["p1".to_string(), "p2".to_string()]
19327 );
19328 assert_eq!(s1.last_received_pos, 0);
19329 let s2 = e2.subscriptions().get("s2").unwrap();
19330 assert_eq!(s2.last_received_pos, 42);
19331 }
19332
19333 #[test]
19334 fn v3_envelope_loads_with_empty_subscriptions() {
19335 let mut e = Engine::new();
19339 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19340 let catalog = e.catalog.serialize();
19341 let users = crate::users::serialize_users(&e.users);
19342 let pubs = e.publications.serialize();
19343 let mut buf = Vec::new();
19344 buf.extend_from_slice(b"SPGENV01");
19345 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19347 buf.extend_from_slice(&catalog);
19348 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19349 buf.extend_from_slice(&users);
19350 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19351 buf.extend_from_slice(&pubs);
19352 let crc = spg_crypto::crc32::crc32(&buf);
19353 buf.extend_from_slice(&crc.to_le_bytes());
19354
19355 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19356 assert!(e2.subscriptions().is_empty());
19357 assert!(e2.publications().contains("pub_legacy"));
19358 }
19359
19360 #[test]
19361 fn create_subscription_allowed_inside_transaction() {
19362 let mut e = Engine::new();
19363 e.execute("BEGIN").unwrap();
19364 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19365 .unwrap();
19366 e.execute("COMMIT").unwrap();
19367 assert!(e.subscriptions().contains("s"));
19368 }
19369
19370 #[test]
19372 fn analyze_populates_histogram_bounds() {
19373 let mut e = Engine::new();
19374 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19375 .unwrap();
19376 for i in 0..50 {
19377 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19378 .unwrap();
19379 }
19380 e.execute("ANALYZE t").unwrap();
19381 let stats = e.statistics();
19382 let id_stats = stats.get("t", "id").unwrap();
19383 assert!(id_stats.histogram_bounds.len() >= 2);
19384 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19385 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19386 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19387 assert_eq!(id_stats.n_distinct, 50);
19388 }
19389
19390 #[test]
19391 fn reanalyze_overwrites_prior_stats() {
19392 let mut e = Engine::new();
19393 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19394 for i in 0..10 {
19395 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19396 .unwrap();
19397 }
19398 e.execute("ANALYZE t").unwrap();
19399 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19400 assert_eq!(n1, 10);
19401 for i in 10..30 {
19402 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19403 .unwrap();
19404 }
19405 e.execute("ANALYZE t").unwrap();
19406 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19407 assert_eq!(n2, 30);
19408 }
19409
19410 #[test]
19411 fn analyze_unknown_table_errors() {
19412 let mut e = Engine::new();
19413 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19414 assert!(matches!(
19415 err,
19416 EngineError::Storage(StorageError::TableNotFound { .. })
19417 ));
19418 }
19419
19420 #[test]
19421 fn bare_analyze_covers_all_user_tables() {
19422 let mut e = Engine::new();
19423 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19424 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19425 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19426 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19427 let r = e.execute("ANALYZE").unwrap();
19428 match r {
19429 QueryResult::CommandOk {
19430 affected,
19431 modified_catalog,
19432 } => {
19433 assert_eq!(affected, 2);
19434 assert!(modified_catalog);
19435 }
19436 other => panic!("expected CommandOk, got {other:?}"),
19437 }
19438 assert!(e.statistics().get("t1", "id").is_some());
19439 assert!(e.statistics().get("t2", "name").is_some());
19440 }
19441
19442 #[test]
19443 fn select_from_spg_statistic_returns_rows_per_column() {
19444 let mut e = Engine::new();
19445 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19446 .unwrap();
19447 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19448 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19449 e.execute("ANALYZE t").unwrap();
19450 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19451 let QueryResult::Rows { rows, columns } = r else {
19452 panic!()
19453 };
19454 assert_eq!(columns.len(), 6);
19456 assert_eq!(columns[0].name, "table_name");
19457 assert_eq!(columns[4].name, "histogram_bounds");
19458 assert_eq!(columns[5].name, "cold_row_count");
19459 assert_eq!(rows.len(), 2, "one row per column of t");
19460 match (&rows[0].values[0], &rows[0].values[1]) {
19462 (Value::Text(t), Value::Text(c)) => {
19463 assert_eq!(t, "t");
19464 assert_eq!(c, "id");
19466 }
19467 _ => panic!(),
19468 }
19469 }
19470
19471 #[test]
19472 fn analyze_skips_vector_columns() {
19473 let mut e = Engine::new();
19476 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19477 .unwrap();
19478 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19479 e.execute("ANALYZE t").unwrap();
19480 assert!(e.statistics().get("t", "id").is_some());
19481 assert!(e.statistics().get("t", "v").is_none());
19482 }
19483
19484 #[test]
19485 fn statistics_persist_across_envelope_v5_round_trip() {
19486 let mut e = Engine::new();
19487 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19488 for i in 0..20 {
19489 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19490 .unwrap();
19491 }
19492 e.execute("ANALYZE").unwrap();
19493 let snap = e.snapshot();
19494 let e2 = Engine::restore_envelope(&snap).unwrap();
19495 let s = e2.statistics().get("t", "id").unwrap();
19496 assert_eq!(s.n_distinct, 20);
19497 }
19498
19499 #[test]
19502 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19503 let mut e = Engine::new();
19507 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19508 for i in 0..9 {
19509 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19510 .unwrap();
19511 }
19512 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19513 e.execute("INSERT INTO t VALUES (9)").unwrap();
19514 let needs = e.tables_needing_analyze();
19515 assert_eq!(needs, alloc::vec!["t".to_string()]);
19516 }
19517
19518 #[test]
19519 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19520 let mut e = Engine::new();
19526 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19527 for i in 0..1000 {
19528 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19529 .unwrap();
19530 }
19531 e.execute("ANALYZE t").unwrap();
19532 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19533 for i in 1000..1050 {
19534 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19535 .unwrap();
19536 }
19537 assert!(
19538 e.tables_needing_analyze().is_empty(),
19539 "50 inserts < threshold of ~105"
19540 );
19541 for i in 1050..1200 {
19542 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19543 .unwrap();
19544 }
19545 assert_eq!(
19546 e.tables_needing_analyze(),
19547 alloc::vec!["t".to_string()],
19548 "200 inserts > 0.1 × 1200 threshold"
19549 );
19550 }
19551
19552 #[test]
19553 fn auto_analyze_threshold_resets_after_analyze() {
19554 let mut e = Engine::new();
19555 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19556 for i in 0..200 {
19557 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19558 .unwrap();
19559 }
19560 assert!(!e.tables_needing_analyze().is_empty());
19561 e.execute("ANALYZE").unwrap();
19562 assert!(
19563 e.tables_needing_analyze().is_empty(),
19564 "ANALYZE must reset the counter"
19565 );
19566 }
19567
19568 #[test]
19569 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19570 let mut e = Engine::new();
19571 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19572 .unwrap();
19573 for i in 0..50 {
19574 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19575 .unwrap();
19576 }
19577 e.execute("ANALYZE t").unwrap();
19578 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19581 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19582 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19583 }
19584
19585 #[test]
19586 fn v4_envelope_loads_with_empty_statistics() {
19587 let mut e = Engine::new();
19591 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19592 .unwrap();
19593 let catalog = e.catalog.serialize();
19594 let users = crate::users::serialize_users(&e.users);
19595 let pubs = e.publications.serialize();
19596 let subs = e.subscriptions.serialize();
19597 let mut buf = Vec::new();
19598 buf.extend_from_slice(b"SPGENV01");
19599 buf.push(4u8);
19600 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19601 buf.extend_from_slice(&catalog);
19602 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19603 buf.extend_from_slice(&users);
19604 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19605 buf.extend_from_slice(&pubs);
19606 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19607 buf.extend_from_slice(&subs);
19608 let crc = spg_crypto::crc32::crc32(&buf);
19609 buf.extend_from_slice(&crc.to_le_bytes());
19610 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19611 assert!(e2.statistics().is_empty());
19612 }
19613
19614 #[test]
19615 fn v1_v2_envelope_loads_with_empty_publications() {
19616 let mut e = Engine::new();
19623 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19626 .unwrap();
19627
19628 let catalog = e.catalog.serialize();
19630 let users = crate::users::serialize_users(&e.users);
19631 let mut buf = Vec::new();
19632 buf.extend_from_slice(b"SPGENV01");
19633 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19635 buf.extend_from_slice(&catalog);
19636 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19637 buf.extend_from_slice(&users);
19638 let crc = spg_crypto::crc32::crc32(&buf);
19639 buf.extend_from_slice(&crc.to_le_bytes());
19640
19641 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19642 assert!(e2.publications().is_empty());
19643 }
19644}