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 substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13913 for item in &mut s.items {
13914 if let SelectItem::Expr { expr, .. } = item {
13915 substitute_expr(expr, params)?;
13916 }
13917 }
13918 if let Some(w) = &mut s.where_ {
13919 substitute_expr(w, params)?;
13920 }
13921 if let Some(gs) = &mut s.group_by {
13922 for g in gs {
13923 substitute_expr(g, params)?;
13924 }
13925 }
13926 if let Some(h) = &mut s.having {
13927 substitute_expr(h, params)?;
13928 }
13929 for o in &mut s.order_by {
13930 substitute_expr(&mut o.expr, params)?;
13931 }
13932 for (_, peer) in &mut s.unions {
13933 substitute_select(peer, params)?;
13934 }
13935 if let Some(le) = s.limit {
13940 s.limit = Some(resolve_limit_placeholder(le, params)?);
13941 }
13942 if let Some(le) = s.offset {
13943 s.offset = Some(resolve_limit_placeholder(le, params)?);
13944 }
13945 Ok(())
13946}
13947
13948fn resolve_limit_placeholder(
13949 le: spg_sql::ast::LimitExpr,
13950 params: &[Value],
13951) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13952 use spg_sql::ast::LimitExpr;
13953 match le {
13954 LimitExpr::Literal(_) => Ok(le),
13955 LimitExpr::Placeholder(n) => {
13956 let idx = usize::from(n).saturating_sub(1);
13957 let v = params.get(idx).ok_or_else(|| {
13958 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13959 n,
13960 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13961 })
13962 })?;
13963 let int = match v {
13964 Value::SmallInt(x) => Some(i64::from(*x)),
13965 Value::Int(x) => Some(i64::from(*x)),
13966 Value::BigInt(x) => Some(*x),
13967 _ => None,
13968 }
13969 .ok_or_else(|| {
13970 EngineError::Unsupported(alloc::format!(
13971 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13972 ))
13973 })?;
13974 if int < 0 {
13975 return Err(EngineError::Unsupported(alloc::format!(
13976 "LIMIT/OFFSET ${n} bound to negative value {int}"
13977 )));
13978 }
13979 let bounded = u32::try_from(int).map_err(|_| {
13980 EngineError::Unsupported(alloc::format!(
13981 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13982 ))
13983 })?;
13984 Ok(LimitExpr::Literal(bounded))
13985 }
13986 }
13987}
13988
13989fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13990 if let Expr::Placeholder(n) = e {
13991 let idx = usize::from(*n).saturating_sub(1);
13992 let v = params.get(idx).ok_or_else(|| {
13993 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13994 n: *n,
13995 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13996 })
13997 })?;
13998 *e = Expr::Literal(value_to_literal(v.clone()));
13999 return Ok(());
14000 }
14001 match e {
14002 Expr::AggregateOrdered { call, order_by, .. } => {
14003 substitute_expr(call, params)?;
14004 for o in order_by.iter_mut() {
14005 substitute_expr(&mut o.expr, params)?;
14006 }
14007 }
14008 Expr::Binary { lhs, rhs, .. } => {
14009 substitute_expr(lhs, params)?;
14010 substitute_expr(rhs, params)?;
14011 }
14012 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14013 substitute_expr(expr, params)?;
14014 }
14015 Expr::FunctionCall { args, .. } => {
14016 for a in args {
14017 substitute_expr(a, params)?;
14018 }
14019 }
14020 Expr::Like { expr, pattern, .. } => {
14021 substitute_expr(expr, params)?;
14022 substitute_expr(pattern, params)?;
14023 }
14024 Expr::Extract { source, .. } => substitute_expr(source, params)?,
14025 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
14026 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
14027 Expr::InSubquery { expr, subquery, .. } => {
14028 substitute_expr(expr, params)?;
14029 substitute_select(subquery, params)?;
14030 }
14031 Expr::WindowFunction {
14032 args,
14033 partition_by,
14034 order_by,
14035 ..
14036 } => {
14037 for a in args {
14038 substitute_expr(a, params)?;
14039 }
14040 for p in partition_by {
14041 substitute_expr(p, params)?;
14042 }
14043 for (e, _, _) in order_by {
14044 substitute_expr(e, params)?;
14045 }
14046 }
14047 Expr::Literal(_) | Expr::Column(_) => {}
14048 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
14050 Expr::Array(items) => {
14051 for elem in items {
14052 substitute_expr(elem, params)?;
14053 }
14054 }
14055 Expr::ArraySubscript { target, index } => {
14056 substitute_expr(target, params)?;
14057 substitute_expr(index, params)?;
14058 }
14059 Expr::AnyAll { expr, array, .. } => {
14060 substitute_expr(expr, params)?;
14061 substitute_expr(array, params)?;
14062 }
14063 Expr::Case {
14064 operand,
14065 branches,
14066 else_branch,
14067 } => {
14068 if let Some(o) = operand {
14069 substitute_expr(o, params)?;
14070 }
14071 for (w, t) in branches {
14072 substitute_expr(w, params)?;
14073 substitute_expr(t, params)?;
14074 }
14075 if let Some(e) = else_branch {
14076 substitute_expr(e, params)?;
14077 }
14078 }
14079 }
14080 Ok(())
14081}
14082
14083fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
14101 use core::cmp::Ordering;
14102 match (a, b) {
14103 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
14104 (Value::Int(a), Value::Int(b)) => a.cmp(b),
14105 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
14106 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
14107 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
14108 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14109 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
14110 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14111 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
14112 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14113 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
14114 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
14115 (Value::Date(a), Value::Date(b)) => a.cmp(b),
14116 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
14117 (Value::SmallInt(n), Value::Float(x)) => {
14119 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14120 }
14121 (Value::Float(x), Value::SmallInt(n)) => {
14122 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14123 }
14124 (Value::Int(n), Value::Float(x)) => {
14125 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14126 }
14127 (Value::Float(x), Value::Int(n)) => {
14128 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14129 }
14130 (Value::BigInt(n), Value::Float(x)) => {
14131 #[allow(clippy::cast_precision_loss)]
14132 let nf = *n as f64;
14133 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
14134 }
14135 (Value::Float(x), Value::BigInt(n)) => {
14136 #[allow(clippy::cast_precision_loss)]
14137 let nf = *n as f64;
14138 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
14139 }
14140 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
14143 }
14144}
14145
14146fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
14153 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
14154 out.push('[');
14155 for (i, b) in bounds.iter().enumerate() {
14156 if i > 0 {
14157 out.push_str(", ");
14158 }
14159 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
14160 if needs_quote {
14161 out.push('"');
14162 for ch in b.chars() {
14163 if ch == '"' || ch == '\\' {
14164 out.push('\\');
14165 }
14166 out.push(ch);
14167 }
14168 out.push('"');
14169 } else {
14170 out.push_str(b);
14171 }
14172 }
14173 out.push(']');
14174 out
14175}
14176
14177pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
14187 match v {
14188 Value::Null => "NULL".to_string(),
14189 Value::SmallInt(n) => alloc::format!("{n}"),
14190 Value::Int(n) => alloc::format!("{n}"),
14191 Value::BigInt(n) => alloc::format!("{n}"),
14192 Value::Float(x) => alloc::format!("{x:?}"),
14193 Value::Text(s) | Value::Json(s) => s.clone(),
14194 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
14195 Value::Date(d) => eval::format_date(*d),
14196 Value::Timestamp(t) => eval::format_timestamp(*t),
14197 Value::Time(us) => eval::format_time(*us),
14199 Value::Year(y) => alloc::format!("{y:04}"),
14201 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
14203 Value::Money(c) => eval::format_money(*c),
14205 v @ Value::Range { .. } => format_range_str(v),
14207 Value::Hstore(pairs) => format_hstore_str(pairs),
14209 Value::IntArray2D(rows) => format_int_2d_text(rows),
14211 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
14212 Value::TextArray2D(rows) => format_text_2d_text(rows),
14213 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
14214 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
14215 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
14216 alloc::format!("{v:?}")
14220 }
14221 _ => alloc::format!("{v:?}"),
14225 }
14226}
14227
14228const fn is_internal_table_name(_name: &str) -> bool {
14235 false
14236}
14237
14238fn value_to_literal(v: Value) -> Literal {
14239 match v {
14240 Value::Null => Literal::Null,
14241 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14242 Value::Int(n) => Literal::Integer(i64::from(n)),
14243 Value::BigInt(n) => Literal::Integer(n),
14244 Value::Float(x) => Literal::Float(x),
14245 Value::Text(s) | Value::Json(s) => Literal::String(s),
14246 Value::Bool(b) => Literal::Bool(b),
14247 Value::Vector(v) => Literal::Vector(v),
14248 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
14249 Value::Date(d) => Literal::String(eval::format_date(d)),
14250 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14251 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14257 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14262 Value::TextArray(items) => Literal::TextArray(items),
14267 Value::IntArray(items) => Literal::IntArray(items),
14268 Value::BigIntArray(items) => Literal::BigIntArray(items),
14269 Value::Interval { months, micros } => Literal::Interval {
14270 months,
14271 micros,
14272 text: eval::format_interval(months, micros),
14273 },
14274 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14277 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14278 v => Literal::String(alloc::format!("{v:?}")),
14282 }
14283}
14284
14285fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14286 let Some(now) = now_micros else {
14287 return;
14288 };
14289 match stmt {
14290 Statement::Select(s) => rewrite_select_clock(s, now),
14291 Statement::Insert(ins) => {
14292 for row in &mut ins.rows {
14293 for e in row {
14294 rewrite_expr_clock(e, now);
14295 }
14296 }
14297 if let Some(clause) = &mut ins.on_conflict
14301 && let spg_sql::ast::OnConflictAction::Update {
14302 assignments,
14303 where_,
14304 } = &mut clause.action
14305 {
14306 for (_, e) in assignments.iter_mut() {
14307 rewrite_expr_clock(e, now);
14308 }
14309 if let Some(w) = where_ {
14310 rewrite_expr_clock(w, now);
14311 }
14312 }
14313 }
14314 Statement::Update(u) => {
14318 for (_, e) in &mut u.assignments {
14319 rewrite_expr_clock(e, now);
14320 }
14321 if let Some(w) = &mut u.where_ {
14322 rewrite_expr_clock(w, now);
14323 }
14324 }
14325 Statement::Delete(d) => {
14326 if let Some(w) = &mut d.where_ {
14327 rewrite_expr_clock(w, now);
14328 }
14329 }
14330 _ => {}
14331 }
14332}
14333
14334fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14335 for item in &mut s.items {
14336 if let SelectItem::Expr { expr, .. } = item {
14337 rewrite_expr_clock(expr, now);
14338 }
14339 }
14340 if let Some(w) = &mut s.where_ {
14341 rewrite_expr_clock(w, now);
14342 }
14343 if let Some(gs) = &mut s.group_by {
14344 for g in gs {
14345 rewrite_expr_clock(g, now);
14346 }
14347 }
14348 if let Some(h) = &mut s.having {
14349 rewrite_expr_clock(h, now);
14350 }
14351 for o in &mut s.order_by {
14352 rewrite_expr_clock(&mut o.expr, now);
14353 }
14354 for (_, peer) in &mut s.unions {
14355 rewrite_select_clock(peer, now);
14356 }
14357}
14358
14359fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14367 if let Some(replacement) = clock_replacement_for(e, now) {
14371 *e = replacement;
14372 return;
14373 }
14374 match e {
14375 Expr::AggregateOrdered { call, order_by, .. } => {
14376 rewrite_expr_clock(call, now);
14377 for o in order_by.iter_mut() {
14378 rewrite_expr_clock(&mut o.expr, now);
14379 }
14380 }
14381 Expr::Binary { lhs, rhs, .. } => {
14382 rewrite_expr_clock(lhs, now);
14383 rewrite_expr_clock(rhs, now);
14384 }
14385 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14386 rewrite_expr_clock(expr, now);
14387 }
14388 Expr::FunctionCall { args, .. } => {
14389 for a in args {
14390 rewrite_expr_clock(a, now);
14391 }
14392 }
14393 Expr::Like { expr, pattern, .. } => {
14394 rewrite_expr_clock(expr, now);
14395 rewrite_expr_clock(pattern, now);
14396 }
14397 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14398 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14402 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14403 Expr::InSubquery { expr, subquery, .. } => {
14404 rewrite_expr_clock(expr, now);
14405 rewrite_select_clock(subquery, now);
14406 }
14407 Expr::WindowFunction {
14410 args,
14411 partition_by,
14412 order_by,
14413 ..
14414 } => {
14415 for a in args {
14416 rewrite_expr_clock(a, now);
14417 }
14418 for p in partition_by {
14419 rewrite_expr_clock(p, now);
14420 }
14421 for (e, _, _) in order_by {
14422 rewrite_expr_clock(e, now);
14423 }
14424 }
14425 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14426 Expr::Array(items) => {
14427 for elem in items {
14428 rewrite_expr_clock(elem, now);
14429 }
14430 }
14431 Expr::ArraySubscript { target, index } => {
14432 rewrite_expr_clock(target, now);
14433 rewrite_expr_clock(index, now);
14434 }
14435 Expr::AnyAll { expr, array, .. } => {
14436 rewrite_expr_clock(expr, now);
14437 rewrite_expr_clock(array, now);
14438 }
14439 Expr::Case {
14440 operand,
14441 branches,
14442 else_branch,
14443 } => {
14444 if let Some(o) = operand {
14445 rewrite_expr_clock(o, now);
14446 }
14447 for (w, t) in branches {
14448 rewrite_expr_clock(w, now);
14449 rewrite_expr_clock(t, now);
14450 }
14451 if let Some(e) = else_branch {
14452 rewrite_expr_clock(e, now);
14453 }
14454 }
14455 }
14456}
14457
14458fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14465 let (kind, name) = match e {
14466 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14467 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14468 _ => return None,
14469 };
14470 enum ClockShape {
14478 Timestamp,
14479 Date,
14480 UnixSeconds,
14481 }
14482 let shape = match name.len() {
14483 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14484 Some(ClockShape::Timestamp)
14485 }
14486 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14487 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14488 Some(ClockShape::UnixSeconds)
14489 }
14490 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14491 _ => None,
14492 };
14493 let shape = shape?;
14494 let payload = match shape {
14495 ClockShape::Timestamp => now,
14496 ClockShape::Date => now.div_euclid(86_400_000_000),
14497 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14498 };
14499 let target = match shape {
14500 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14501 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14502 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14503 };
14504 Some(Expr::Cast {
14505 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14506 target,
14507 })
14508}
14509
14510#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14511enum ClockSite {
14512 Fn,
14513 BareIdent,
14514}
14515
14516fn expand_group_by_all(s: &mut SelectStatement) {
14527 if !s.group_by_all {
14528 for (_, peer) in &mut s.unions {
14529 expand_group_by_all(peer);
14530 }
14531 return;
14532 }
14533 let mut groups: Vec<Expr> = Vec::new();
14534 for item in &s.items {
14535 if let SelectItem::Expr { expr, .. } = item
14536 && !aggregate::contains_aggregate(expr)
14537 {
14538 groups.push(expr.clone());
14539 }
14540 }
14541 s.group_by = Some(groups);
14542 s.group_by_all = false;
14543 for (_, peer) in &mut s.unions {
14544 expand_group_by_all(peer);
14545 }
14546}
14547
14548fn resolve_order_by_position(s: &mut SelectStatement) {
14549 for order in &mut s.order_by {
14554 match &order.expr {
14555 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14556 if let Ok(idx_one_based) = usize::try_from(*n) {
14557 let idx = idx_one_based - 1;
14558 if idx < s.items.len()
14559 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14560 {
14561 order.expr = expr.clone();
14562 }
14563 }
14564 }
14565 Expr::Column(c) if c.qualifier.is_none() => {
14566 for item in &s.items {
14568 if let SelectItem::Expr {
14569 expr,
14570 alias: Some(a),
14571 } = item
14572 && a == &c.name
14573 {
14574 order.expr = expr.clone();
14575 break;
14576 }
14577 }
14578 }
14579 _ => {}
14580 }
14581 }
14582 for (_, peer) in &mut s.unions {
14583 resolve_order_by_position(peer);
14584 }
14585}
14586
14587fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14600 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14601 match keep {
14602 Some(k) if k < tagged.len() && k > 0 => {
14603 let pivot = k - 1;
14604 tagged.select_nth_unstable_by(pivot, cmp);
14605 tagged[..k].sort_by(cmp);
14606 tagged.truncate(k);
14607 }
14608 _ => {
14609 tagged.sort_by(cmp);
14610 }
14611 }
14612}
14613
14614fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14615 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14616}
14617
14618fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14622 use core::cmp::Ordering;
14623 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14624 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14625 let ord = if descs.get(i).copied().unwrap_or(false) {
14626 ord.reverse()
14627 } else {
14628 ord
14629 };
14630 if ord != Ordering::Equal {
14631 return ord;
14632 }
14633 }
14634 Ordering::Equal
14635}
14636
14637fn build_order_keys(
14640 order_by: &[OrderBy],
14641 row: &Row,
14642 ctx: &EvalContext,
14643) -> Result<Vec<f64>, EngineError> {
14644 let mut keys = Vec::with_capacity(order_by.len());
14645 for o in order_by {
14646 let v = eval::eval_expr(&o.expr, row, ctx)?;
14647 if matches!(v, Value::Null) {
14654 let nf = o.nulls_first.unwrap_or(o.desc);
14655 keys.push(if nf == o.desc {
14656 f64::INFINITY
14657 } else {
14658 f64::NEG_INFINITY
14659 });
14660 } else {
14661 keys.push(value_to_order_key(&v)?);
14662 }
14663 }
14664 Ok(keys)
14665}
14666
14667fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14671 if let Some(off) = offset {
14672 let off = off as usize;
14673 if off >= rows.len() {
14674 rows.clear();
14675 } else {
14676 rows.drain(..off);
14677 }
14678 }
14679 if let Some(n) = limit {
14680 rows.truncate(n as usize);
14681 }
14682}
14683
14684fn apply_offset_and_limit_tagged(
14695 tagged: &mut Vec<(Vec<f64>, Row)>,
14696 offset: Option<u32>,
14697 limit: Option<u32>,
14698 with_ties: bool,
14699) {
14700 if let Some(off) = offset {
14701 let off = off as usize;
14702 if off >= tagged.len() {
14703 tagged.clear();
14704 } else {
14705 tagged.drain(..off);
14706 }
14707 }
14708 if let Some(n) = limit {
14709 let n = n as usize;
14710 if with_ties && n > 0 && n < tagged.len() {
14711 let cutoff_key = tagged[n - 1].0.clone();
14712 let mut end = n;
14713 while end < tagged.len() && tagged[end].0 == cutoff_key {
14714 end += 1;
14715 }
14716 tagged.truncate(end);
14717 } else {
14718 tagged.truncate(n);
14719 }
14720 }
14721}
14722
14723fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14729 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14730 return Err(EngineError::Unsupported(alloc::string::String::from(
14731 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14732 )));
14733 }
14734 Ok(())
14735}
14736
14737fn resolve_foreign_key(
14751 local_table_name: &str,
14752 local_cols: &[ColumnSchema],
14753 fk: spg_sql::ast::ForeignKeyConstraint,
14754 catalog: &Catalog,
14755) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14756 let mut local_columns = Vec::with_capacity(fk.columns.len());
14758 for name in &fk.columns {
14759 let pos = local_cols
14760 .iter()
14761 .position(|c| c.name == *name)
14762 .ok_or_else(|| {
14763 EngineError::Unsupported(alloc::format!(
14764 "FOREIGN KEY references unknown local column {name:?}"
14765 ))
14766 })?;
14767 local_columns.push(pos);
14768 }
14769 let is_self_ref = fk.parent_table == local_table_name;
14773 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14774 (local_cols, local_table_name)
14775 } else {
14776 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14777 EngineError::Storage(StorageError::TableNotFound {
14778 name: fk.parent_table.clone(),
14779 })
14780 })?;
14781 (
14782 parent_table.schema().columns.as_slice(),
14783 fk.parent_table.as_str(),
14784 )
14785 };
14786 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14791 if fk.columns.len() != 1 {
14792 return Err(EngineError::Unsupported(
14793 "composite FOREIGN KEY without explicit parent column list is not supported \
14794 — list the parent columns explicitly"
14795 .into(),
14796 ));
14797 }
14798 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14800 .ok_or_else(|| {
14801 EngineError::Unsupported(alloc::format!(
14802 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14803 to default the FOREIGN KEY against"
14804 ))
14805 })?;
14806 alloc::vec![pos]
14807 } else {
14808 let mut out = Vec::with_capacity(fk.parent_columns.len());
14809 for name in &fk.parent_columns {
14810 let pos = parent_cols_for_lookup
14811 .iter()
14812 .position(|c| c.name == *name)
14813 .ok_or_else(|| {
14814 EngineError::Unsupported(alloc::format!(
14815 "FOREIGN KEY references unknown parent column \
14816 {name:?} on table {parent_table_str:?}"
14817 ))
14818 })?;
14819 out.push(pos);
14820 }
14821 out
14822 };
14823 if parent_columns.len() != local_columns.len() {
14824 return Err(EngineError::Unsupported(alloc::format!(
14825 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14826 local_columns.len(),
14827 parent_columns.len()
14828 )));
14829 }
14830 if !is_self_ref {
14840 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14841 let primary_parent_col = parent_columns[0];
14842 let has_btree = parent_table
14843 .schema()
14844 .columns
14845 .get(primary_parent_col)
14846 .is_some()
14847 && parent_table.indices().iter().any(|idx| {
14848 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14849 && idx.column_position == primary_parent_col
14850 && idx.partial_predicate.is_none()
14851 });
14852 if !has_btree {
14853 return Err(EngineError::Unsupported(alloc::format!(
14854 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14855 index — create one with `CREATE INDEX ... ON {} ({})` first",
14856 parent_table_str,
14857 parent_table_str,
14858 parent_table.schema().columns[primary_parent_col].name,
14859 )));
14860 }
14861 }
14862 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14863 let on_update = fk_action_sql_to_storage(fk.on_update);
14864 Ok(spg_storage::ForeignKeyConstraint {
14865 name: fk.name,
14866 local_columns,
14867 parent_table: fk.parent_table,
14868 parent_columns,
14869 on_delete,
14870 on_update,
14871 })
14872}
14873
14874fn pick_pk_index_column(
14880 catalog: &Catalog,
14881 parent_name: &str,
14882 is_self_ref: bool,
14883 local_cols: &[ColumnSchema],
14884) -> Option<usize> {
14885 if is_self_ref {
14886 let _ = local_cols;
14890 return Some(0);
14891 }
14892 let parent = catalog.get(parent_name)?;
14893 parent.indices().iter().find_map(|idx| {
14894 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14895 && idx.partial_predicate.is_none()
14896 && idx.included_columns.is_empty()
14897 && idx.expression.is_none()
14898 {
14899 Some(idx.column_position)
14900 } else {
14901 None
14902 }
14903 })
14904}
14905
14906fn resolve_on_conflict_columns(
14913 catalog: &Catalog,
14914 table_name: &str,
14915 target: &[String],
14916) -> Result<Vec<usize>, EngineError> {
14917 let table = catalog.get(table_name).ok_or_else(|| {
14918 EngineError::Storage(StorageError::TableNotFound {
14919 name: table_name.into(),
14920 })
14921 })?;
14922 if target.is_empty() {
14923 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14933 return Ok(uc.columns.clone());
14934 }
14935 let pos = table
14936 .indices()
14937 .iter()
14938 .find_map(|idx| {
14939 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14940 && idx.partial_predicate.is_none()
14941 && idx.included_columns.is_empty()
14942 && idx.expression.is_none()
14943 {
14944 Some(idx.column_position)
14945 } else {
14946 None
14947 }
14948 })
14949 .ok_or_else(|| {
14950 EngineError::Unsupported(alloc::format!(
14951 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14952 ))
14953 })?;
14954 return Ok(alloc::vec![pos]);
14955 }
14956 let mut out = Vec::with_capacity(target.len());
14957 for name in target {
14958 let pos = table
14959 .schema()
14960 .columns
14961 .iter()
14962 .position(|c| c.name == *name)
14963 .ok_or_else(|| {
14964 EngineError::Unsupported(alloc::format!(
14965 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14966 ))
14967 })?;
14968 out.push(pos);
14969 }
14970 Ok(out)
14971}
14972
14973fn on_conflict_key_exists(
14976 catalog: &Catalog,
14977 table_name: &str,
14978 column_pos: usize,
14979 key: &Value,
14980) -> bool {
14981 let Some(table) = catalog.get(table_name) else {
14982 return false;
14983 };
14984 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14985 return false;
14986 };
14987 table.indices().iter().any(|idx| {
14988 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14989 && idx.column_position == column_pos
14990 && idx.partial_predicate.is_none()
14991 && !idx.lookup_eq(&idx_key).is_empty()
14992 })
14993}
14994
14995fn lookup_row_position_by_keys(
15001 catalog: &Catalog,
15002 table_name: &str,
15003 column_positions: &[usize],
15004 key: &[&Value],
15005) -> Option<usize> {
15006 let table = catalog.get(table_name)?;
15007 table.rows().iter().position(|r| {
15008 column_positions
15009 .iter()
15010 .enumerate()
15011 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15012 })
15013}
15014
15015fn on_conflict_keys_exist(
15020 catalog: &Catalog,
15021 table_name: &str,
15022 column_positions: &[usize],
15023 key: &[&Value],
15024) -> bool {
15025 if column_positions.len() == 1 {
15026 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
15027 }
15028 let Some(table) = catalog.get(table_name) else {
15029 return false;
15030 };
15031 table.rows().iter().any(|r| {
15032 column_positions
15033 .iter()
15034 .enumerate()
15035 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15036 })
15037}
15038
15039fn apply_on_conflict_assignments(
15052 catalog: &Catalog,
15053 table_name: &str,
15054 target_pos: usize,
15055 incoming: &[Value],
15056 assignments: &[(String, Expr)],
15057 where_: Option<&Expr>,
15058) -> Result<Option<Vec<Value>>, EngineError> {
15059 let table = catalog.get(table_name).ok_or_else(|| {
15060 EngineError::Storage(StorageError::TableNotFound {
15061 name: table_name.into(),
15062 })
15063 })?;
15064 let schema_cols = table.schema().columns.clone();
15065 let existing = table
15066 .rows()
15067 .get(target_pos)
15068 .ok_or_else(|| {
15069 EngineError::Unsupported(alloc::format!(
15070 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
15071 ))
15072 })?
15073 .clone();
15074 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
15075 if let Some(w) = where_ {
15077 let pred = w.clone();
15078 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
15079 let v = eval::eval_expr(&pred, &existing, &ctx)?;
15080 if !matches!(v, Value::Bool(true)) {
15081 return Ok(None);
15082 }
15083 }
15084 let mut new_values = existing.values.clone();
15085 for (col_name, expr) in assignments {
15086 let target_idx = schema_cols
15087 .iter()
15088 .position(|c| c.name == *col_name)
15089 .ok_or_else(|| {
15090 EngineError::Eval(EvalError::ColumnNotFound {
15091 name: col_name.clone(),
15092 })
15093 })?;
15094 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
15095 let v = eval::eval_expr(&sub, &existing, &ctx)?;
15096 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
15097 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
15098 new_values[target_idx] = coerced;
15099 }
15100 Ok(Some(new_values))
15101}
15102
15103fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
15108 use spg_sql::ast::ColumnName;
15109 match expr {
15110 Expr::Column(ColumnName { qualifier, name })
15111 if qualifier
15112 .as_deref()
15113 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
15114 {
15115 let pos = schema_cols.iter().position(|c| c.name == name);
15116 match pos {
15117 Some(p) => {
15118 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
15119 value_to_literal_expr(v)
15120 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
15121 }
15122 None => Expr::Column(ColumnName { qualifier, name }),
15123 }
15124 }
15125 Expr::Binary { op, lhs, rhs } => Expr::Binary {
15126 op,
15127 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
15128 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
15129 },
15130 Expr::Unary { op, expr } => Expr::Unary {
15131 op,
15132 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
15133 },
15134 Expr::FunctionCall { name, args } => Expr::FunctionCall {
15135 name,
15136 args: args
15137 .into_iter()
15138 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
15139 .collect(),
15140 },
15141 other => other,
15142 }
15143}
15144
15145fn enforce_uniqueness_inserts(
15168 catalog: &Catalog,
15169 child_table: &str,
15170 constraints: &[spg_storage::UniquenessConstraint],
15171 rows: &[Vec<Value>],
15172) -> Result<(), EngineError> {
15173 if constraints.is_empty() {
15174 return Ok(());
15175 }
15176 let table = catalog.get(child_table).ok_or_else(|| {
15177 EngineError::Storage(StorageError::TableNotFound {
15178 name: child_table.into(),
15179 })
15180 })?;
15181 let schema = table.schema();
15182 for uc in constraints {
15183 for (batch_idx, row_values) in rows.iter().enumerate() {
15184 let key: Vec<Value> = uc
15193 .columns
15194 .iter()
15195 .map(|&i| collated_key_cell(&row_values[i], i, schema))
15196 .collect();
15197 let has_null = key.iter().any(|v| matches!(v, Value::Null));
15198 if has_null && !uc.nulls_not_distinct {
15203 continue;
15204 }
15205 let collides_in_table = table.rows().iter().any(|prow| {
15207 uc.columns.iter().enumerate().all(|(i, &p)| {
15208 prow.values
15209 .get(p)
15210 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15211 })
15212 });
15213 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
15215 uc.columns.iter().enumerate().all(|(i, &p)| {
15216 earlier
15217 .get(p)
15218 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15219 })
15220 });
15221 if collides_in_table || collides_in_batch {
15222 let kind = if uc.is_primary_key {
15223 "PRIMARY KEY"
15224 } else {
15225 "UNIQUE"
15226 };
15227 let col_names: Vec<String> = uc
15228 .columns
15229 .iter()
15230 .map(|&i| table.schema().columns[i].name.clone())
15231 .collect();
15232 return Err(EngineError::Unsupported(alloc::format!(
15233 "{kind} violation on {child_table:?} columns {col_names:?}: \
15234 row #{batch_idx} duplicates an existing key"
15235 )));
15236 }
15237 }
15238 }
15239 Ok(())
15240}
15241
15242fn collated_key_cell(
15249 v: &spg_storage::Value,
15250 column_position: usize,
15251 schema: &spg_storage::TableSchema,
15252) -> spg_storage::Value {
15253 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
15254 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
15255 spg_storage::Value::Text(s.to_ascii_lowercase())
15256 }
15257 _ => v.clone(),
15258 }
15259}
15260
15261fn predicate_truthy(v: &spg_storage::Value) -> bool {
15269 use spg_storage::Value as V;
15270 match v {
15271 V::Bool(b) => *b,
15272 V::Int(n) => *n != 0,
15273 V::BigInt(n) => *n != 0,
15274 V::SmallInt(n) => *n != 0,
15275 _ => false,
15276 }
15277}
15278
15279fn check_existing_unique_violation(
15284 idx: &spg_storage::Index,
15285 schema: &spg_storage::TableSchema,
15286 rows: &[spg_storage::Row],
15287) -> Result<(), EngineError> {
15288 let predicate_expr = match idx.partial_predicate.as_deref() {
15289 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15290 EngineError::Unsupported(alloc::format!(
15291 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15292 ))
15293 })?),
15294 None => None,
15295 };
15296 let ctx = eval::EvalContext::new(&schema.columns, None);
15297 let key_positions = unique_key_positions(idx);
15298 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15299 for row in rows {
15300 if let Some(expr) = &predicate_expr {
15301 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15302 EngineError::Unsupported(alloc::format!(
15303 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15304 ))
15305 })?;
15306 if !predicate_truthy(&v) {
15307 continue;
15308 }
15309 }
15310 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15311 .iter()
15312 .map(|&p| {
15313 let v = row
15314 .values
15315 .get(p)
15316 .cloned()
15317 .unwrap_or(spg_storage::Value::Null);
15318 collated_key_cell(&v, p, schema)
15319 })
15320 .collect();
15321 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15322 continue;
15323 }
15324 if seen.iter().any(|other| *other == key) {
15325 return Err(EngineError::Unsupported(alloc::format!(
15326 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15327 idx.name
15328 )));
15329 }
15330 seen.push(key);
15331 }
15332 Ok(())
15333}
15334
15335fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15339 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15340 out.push(idx.column_position);
15341 out.extend_from_slice(&idx.extra_column_positions);
15342 out
15343}
15344
15345fn enforce_unique_index_inserts(
15353 catalog: &Catalog,
15354 table_name: &str,
15355 rows: &[alloc::vec::Vec<spg_storage::Value>],
15356) -> Result<(), EngineError> {
15357 let table = catalog.get(table_name).ok_or_else(|| {
15358 EngineError::Storage(StorageError::TableNotFound {
15359 name: table_name.into(),
15360 })
15361 })?;
15362 let schema = table.schema();
15363 let ctx = eval::EvalContext::new(&schema.columns, None);
15364 for idx in table.indices() {
15365 if !idx.is_unique {
15366 continue;
15367 }
15368 let predicate_expr = match idx.partial_predicate.as_deref() {
15370 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15371 EngineError::Unsupported(alloc::format!(
15372 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15373 idx.name
15374 ))
15375 })?),
15376 None => None,
15377 };
15378 let key_positions = unique_key_positions(idx);
15379 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15380 key_positions
15384 .iter()
15385 .map(|&p| {
15386 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15387 collated_key_cell(&v, p, schema)
15388 })
15389 .collect()
15390 };
15391 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15395 let Some(expr) = &predicate_expr else {
15396 return Ok(true);
15397 };
15398 let tmp_row = spg_storage::Row {
15399 values: values.to_vec(),
15400 };
15401 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15402 EngineError::Unsupported(alloc::format!(
15403 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15404 idx.name
15405 ))
15406 })?;
15407 Ok(predicate_truthy(&v))
15408 };
15409 for (batch_idx, row_values) in rows.iter().enumerate() {
15410 if !participates(row_values)? {
15411 continue;
15412 }
15413 let key = key_of(row_values);
15414 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15415 continue;
15416 }
15417 for prow in table.rows() {
15419 if !participates(&prow.values)? {
15420 continue;
15421 }
15422 if key_of(&prow.values) == key {
15423 return Err(EngineError::Unsupported(alloc::format!(
15424 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15425 row #{batch_idx} duplicates an existing key",
15426 idx.name
15427 )));
15428 }
15429 }
15430 for earlier in &rows[..batch_idx] {
15432 if !participates(earlier)? {
15433 continue;
15434 }
15435 if key_of(earlier) == key {
15436 return Err(EngineError::Unsupported(alloc::format!(
15437 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15438 row #{batch_idx} duplicates an earlier row in the same batch",
15439 idx.name
15440 )));
15441 }
15442 }
15443 }
15444 }
15445 Ok(())
15446}
15447
15448fn any_column_changed(
15456 filter_cols: &[String],
15457 schema_cols: &[ColumnSchema],
15458 old_row: &Row,
15459 new_row: &Row,
15460) -> bool {
15461 for col_name in filter_cols {
15462 let Some(pos) = schema_cols
15463 .iter()
15464 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15465 else {
15466 continue;
15467 };
15468 let old_v = old_row.values.get(pos);
15469 let new_v = new_row.values.get(pos);
15470 if old_v != new_v {
15471 return true;
15472 }
15473 }
15474 false
15475}
15476
15477fn enforce_check_constraints(
15482 catalog: &Catalog,
15483 table_name: &str,
15484 rows: &[alloc::vec::Vec<spg_storage::Value>],
15485) -> Result<(), EngineError> {
15486 let table = catalog.get(table_name).ok_or_else(|| {
15487 EngineError::Storage(StorageError::TableNotFound {
15488 name: table_name.into(),
15489 })
15490 })?;
15491 let schema = table.schema();
15492 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15496 alloc::vec::Vec::new();
15497 for (idx, col) in schema.columns.iter().enumerate() {
15498 let Some(dname) = &col.user_domain_type else {
15499 continue;
15500 };
15501 let Some(dom) = catalog.domain_types().get(dname) else {
15502 continue;
15503 };
15504 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15505 alloc::vec::Vec::with_capacity(dom.checks.len());
15506 for src in &dom.checks {
15507 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15508 EngineError::Unsupported(alloc::format!(
15509 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15510 col.name
15511 ))
15512 })?;
15513 parsed_for_col.push(expr);
15514 }
15515 if !parsed_for_col.is_empty() {
15516 domain_checks_per_col.push((idx, parsed_for_col));
15517 }
15518 }
15519 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15520 return Ok(());
15521 }
15522 let ctx = eval::EvalContext::new(&schema.columns, None);
15523 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15524 for (i, src) in schema.checks.iter().enumerate() {
15525 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15526 EngineError::Unsupported(alloc::format!(
15527 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15528 ))
15529 })?;
15530 parsed.push((i, expr));
15531 }
15532 for (batch_idx, row_values) in rows.iter().enumerate() {
15533 let tmp_row = spg_storage::Row {
15534 values: row_values.clone(),
15535 };
15536 for (i, expr) in &parsed {
15537 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15538 EngineError::Unsupported(alloc::format!(
15539 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15540 ))
15541 })?;
15542 if matches!(v, spg_storage::Value::Bool(false)) {
15544 return Err(EngineError::Unsupported(alloc::format!(
15545 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15546 schema.checks[*i]
15547 )));
15548 }
15549 }
15550 for (col_idx, checks) in &domain_checks_per_col {
15556 let cell = row_values
15557 .get(*col_idx)
15558 .cloned()
15559 .unwrap_or(spg_storage::Value::Null);
15560 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15561 "value",
15562 schema.columns[*col_idx].ty,
15563 schema.columns[*col_idx].nullable,
15564 )];
15565 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15566 let synth_row = spg_storage::Row {
15567 values: alloc::vec![cell],
15568 };
15569 for (ci, expr) in checks.iter().enumerate() {
15570 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15571 EngineError::Unsupported(alloc::format!(
15572 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15573 schema.columns[*col_idx].name
15574 ))
15575 })?;
15576 if matches!(v, spg_storage::Value::Bool(false)) {
15577 return Err(EngineError::Unsupported(alloc::format!(
15578 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15579 schema.columns[*col_idx].name
15580 )));
15581 }
15582 }
15583 }
15584 }
15585 Ok(())
15586}
15587
15588fn enforce_fk_inserts(
15589 catalog: &Catalog,
15590 child_table: &str,
15591 fks: &[spg_storage::ForeignKeyConstraint],
15592 rows: &[Vec<Value>],
15593) -> Result<(), EngineError> {
15594 for fk in fks {
15595 let parent_is_self = fk.parent_table == child_table;
15596 let parent = if parent_is_self {
15597 catalog.get(child_table).ok_or_else(|| {
15600 EngineError::Storage(StorageError::TableNotFound {
15601 name: child_table.into(),
15602 })
15603 })?
15604 } else {
15605 catalog.get(&fk.parent_table).ok_or_else(|| {
15606 EngineError::Storage(StorageError::TableNotFound {
15607 name: fk.parent_table.clone(),
15608 })
15609 })?
15610 };
15611 for (batch_idx, row_values) in rows.iter().enumerate() {
15612 if fk.local_columns.len() == 1 {
15616 let v = &row_values[fk.local_columns[0]];
15617 if matches!(v, Value::Null) {
15618 continue;
15619 }
15620 let parent_col = fk.parent_columns[0];
15621 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15622 EngineError::Unsupported(alloc::format!(
15623 "FOREIGN KEY column value of type {:?} is not index-eligible",
15624 v.data_type()
15625 ))
15626 })?;
15627 let present_committed = parent.indices().iter().any(|idx| {
15628 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15629 && idx.column_position == parent_col
15630 && idx.partial_predicate.is_none()
15631 && !idx.lookup_eq(&key).is_empty()
15632 });
15633 let present_in_batch = parent_is_self
15637 && rows[..batch_idx]
15638 .iter()
15639 .any(|earlier| earlier.get(parent_col) == Some(v));
15640 if !(present_committed || present_in_batch) {
15641 return Err(EngineError::Unsupported(alloc::format!(
15642 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15643 fk.parent_table,
15644 parent
15645 .schema()
15646 .columns
15647 .get(parent_col)
15648 .map_or("?", |c| c.name.as_str()),
15649 v,
15650 )));
15651 }
15652 } else {
15653 if fk
15657 .local_columns
15658 .iter()
15659 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15660 {
15661 continue;
15662 }
15663 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15664 let parent_match_committed = parent.rows().iter().any(|prow| {
15665 fk.parent_columns
15666 .iter()
15667 .enumerate()
15668 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15669 });
15670 let parent_match_in_batch = parent_is_self
15671 && rows[..batch_idx].iter().any(|earlier| {
15672 fk.parent_columns
15673 .iter()
15674 .enumerate()
15675 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15676 });
15677 if !(parent_match_committed || parent_match_in_batch) {
15678 return Err(EngineError::Unsupported(alloc::format!(
15679 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15680 fk.parent_table,
15681 )));
15682 }
15683 }
15684 }
15685 }
15686 Ok(())
15687}
15688
15689#[derive(Debug, Clone)]
15693struct FkChildStep {
15694 child_table: String,
15695 action: FkChildAction,
15696}
15697
15698#[derive(Debug, Clone)]
15699enum FkChildAction {
15700 Delete { positions: Vec<usize> },
15702 SetNull {
15706 positions: Vec<usize>,
15707 columns: Vec<usize>,
15708 },
15709 SetDefault {
15713 positions: Vec<usize>,
15714 columns: Vec<usize>,
15715 defaults: Vec<Value>,
15716 },
15717}
15718
15719fn plan_fk_parent_deletions(
15735 catalog: &Catalog,
15736 parent_table_name: &str,
15737 to_delete_positions: &[usize],
15738 to_delete_rows: &[Vec<Value>],
15739) -> Result<Vec<FkChildStep>, EngineError> {
15740 use alloc::collections::{BTreeMap, BTreeSet};
15741 if to_delete_rows.is_empty() {
15742 return Ok(Vec::new());
15743 }
15744 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15745 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15747 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15748 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15749 for &p in to_delete_positions {
15750 visited.insert((parent_table_name.to_string(), p));
15751 }
15752 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15753 .iter()
15754 .map(|r| (parent_table_name.to_string(), r.clone()))
15755 .collect();
15756 while let Some((cur_parent, parent_row)) = work.pop() {
15757 for child_name in catalog.table_names() {
15758 let child = catalog
15759 .get(&child_name)
15760 .expect("table_names → catalog.get round-trip is total");
15761 for fk in &child.schema().foreign_keys {
15762 if fk.parent_table != cur_parent {
15763 continue;
15764 }
15765 let parent_key: Vec<&Value> = fk
15766 .parent_columns
15767 .iter()
15768 .map(|&pi| &parent_row[pi])
15769 .collect();
15770 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15771 continue;
15772 }
15773 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15774 if child_name == cur_parent
15775 && visited.contains(&(child_name.clone(), child_row_idx))
15776 {
15777 continue;
15778 }
15779 let matches_key = fk
15780 .local_columns
15781 .iter()
15782 .enumerate()
15783 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15784 if !matches_key {
15785 continue;
15786 }
15787 match fk.on_delete {
15788 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15789 return Err(EngineError::Unsupported(alloc::format!(
15790 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15791 restricted by FK from {child_name:?}.{:?}",
15792 fk.local_columns,
15793 )));
15794 }
15795 spg_storage::FkAction::Cascade => {
15796 if visited.insert((child_name.clone(), child_row_idx)) {
15797 delete_plan
15798 .entry(child_name.clone())
15799 .or_default()
15800 .insert(child_row_idx);
15801 work.push((child_name.clone(), child_row.values.clone()));
15802 }
15803 }
15804 spg_storage::FkAction::SetNull => {
15805 for &li in &fk.local_columns {
15807 let col = child.schema().columns.get(li).ok_or_else(|| {
15808 EngineError::Unsupported(alloc::format!(
15809 "FK local column {li} missing in {child_name:?}"
15810 ))
15811 })?;
15812 if !col.nullable {
15813 return Err(EngineError::Unsupported(alloc::format!(
15814 "FOREIGN KEY ON DELETE SET NULL: column \
15815 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15816 col.name,
15817 )));
15818 }
15819 }
15820 let entry = setnull_plan.entry(child_name.clone()).or_default();
15821 for &li in &fk.local_columns {
15822 entry.insert((child_row_idx, li));
15823 }
15824 }
15825 spg_storage::FkAction::SetDefault => {
15826 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15828 for &li in &fk.local_columns {
15829 let col = child.schema().columns.get(li).ok_or_else(|| {
15830 EngineError::Unsupported(alloc::format!(
15831 "FK local column {li} missing in {child_name:?}"
15832 ))
15833 })?;
15834 let default = col.default.clone().ok_or_else(|| {
15835 EngineError::Unsupported(alloc::format!(
15836 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15837 {child_name:?}.{:?} has no DEFAULT declared",
15838 col.name,
15839 ))
15840 })?;
15841 entry.insert((child_row_idx, li), default);
15842 }
15843 }
15844 }
15845 }
15846 }
15847 }
15848 }
15849 let mut steps: Vec<FkChildStep> = Vec::new();
15857 for (child_table, entries) in setnull_plan {
15858 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15859 steps.push(FkChildStep {
15860 child_table,
15861 action: FkChildAction::SetNull { positions, columns },
15862 });
15863 }
15864 for (child_table, entries) in setdefault_plan {
15865 let mut positions = Vec::with_capacity(entries.len());
15866 let mut columns = Vec::with_capacity(entries.len());
15867 let mut defaults = Vec::with_capacity(entries.len());
15868 for ((p, c), v) in entries {
15869 positions.push(p);
15870 columns.push(c);
15871 defaults.push(v);
15872 }
15873 steps.push(FkChildStep {
15874 child_table,
15875 action: FkChildAction::SetDefault {
15876 positions,
15877 columns,
15878 defaults,
15879 },
15880 });
15881 }
15882 for (child_table, positions) in delete_plan {
15883 steps.push(FkChildStep {
15884 child_table,
15885 action: FkChildAction::Delete {
15886 positions: positions.into_iter().collect(),
15887 },
15888 });
15889 }
15890 Ok(steps)
15891}
15892
15893fn plan_fk_parent_updates(
15910 catalog: &Catalog,
15911 parent_table_name: &str,
15912 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15913) -> Result<Vec<FkChildStep>, EngineError> {
15914 use alloc::collections::BTreeMap;
15915 if plan_with_old.is_empty() {
15916 return Ok(Vec::new());
15917 }
15918 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15923 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15924 BTreeMap::new();
15925 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15926 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15928
15929 for child_name in catalog.table_names() {
15930 let child = catalog
15931 .get(&child_name)
15932 .expect("table_names → catalog.get total");
15933 for fk in &child.schema().foreign_keys {
15934 if fk.parent_table != parent_table_name {
15935 continue;
15936 }
15937 for (_pos, old_row, new_row) in plan_with_old {
15938 let key_changed = fk
15940 .parent_columns
15941 .iter()
15942 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15943 if !key_changed {
15944 continue;
15945 }
15946 let old_key: Vec<&Value> =
15948 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15949 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15950 continue;
15952 }
15953 let new_key: Vec<&Value> =
15954 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15955 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15956 if child_name == parent_table_name
15959 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15960 {
15961 continue;
15962 }
15963 let matches_key = fk
15964 .local_columns
15965 .iter()
15966 .enumerate()
15967 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15968 if !matches_key {
15969 continue;
15970 }
15971 match fk.on_update {
15972 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15973 return Err(EngineError::Unsupported(alloc::format!(
15974 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15975 restricted by FK from {child_name:?}.{:?}",
15976 fk.local_columns,
15977 )));
15978 }
15979 spg_storage::FkAction::Cascade => {
15980 let entry = cascade_plan.entry(child_name.clone()).or_default();
15982 for (i, &li) in fk.local_columns.iter().enumerate() {
15983 entry.insert((child_row_idx, li), new_key[i].clone());
15984 }
15985 }
15986 spg_storage::FkAction::SetNull => {
15987 for &li in &fk.local_columns {
15988 let col = child.schema().columns.get(li).ok_or_else(|| {
15989 EngineError::Unsupported(alloc::format!(
15990 "FK local column {li} missing in {child_name:?}"
15991 ))
15992 })?;
15993 if !col.nullable {
15994 return Err(EngineError::Unsupported(alloc::format!(
15995 "FOREIGN KEY ON UPDATE SET NULL: column \
15996 {child_name:?}.{:?} is NOT NULL",
15997 col.name,
15998 )));
15999 }
16000 }
16001 let entry = setnull_plan.entry(child_name.clone()).or_default();
16002 for &li in &fk.local_columns {
16003 entry.insert((child_row_idx, li));
16004 }
16005 }
16006 spg_storage::FkAction::SetDefault => {
16007 let entry = setdefault_plan.entry(child_name.clone()).or_default();
16008 for &li in &fk.local_columns {
16009 let col = child.schema().columns.get(li).ok_or_else(|| {
16010 EngineError::Unsupported(alloc::format!(
16011 "FK local column {li} missing in {child_name:?}"
16012 ))
16013 })?;
16014 let default = col.default.clone().ok_or_else(|| {
16015 EngineError::Unsupported(alloc::format!(
16016 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
16017 {child_name:?}.{:?} has no DEFAULT",
16018 col.name,
16019 ))
16020 })?;
16021 entry.insert((child_row_idx, li), default);
16022 }
16023 }
16024 }
16025 }
16026 }
16027 }
16028 }
16029 let mut steps: Vec<FkChildStep> = Vec::new();
16032 for (child_table, entries) in cascade_plan {
16033 let mut positions = Vec::with_capacity(entries.len());
16034 let mut columns = Vec::with_capacity(entries.len());
16035 let mut defaults = Vec::with_capacity(entries.len());
16036 for ((p, c), v) in entries {
16037 positions.push(p);
16038 columns.push(c);
16039 defaults.push(v);
16040 }
16041 steps.push(FkChildStep {
16046 child_table,
16047 action: FkChildAction::SetDefault {
16048 positions,
16049 columns,
16050 defaults,
16051 },
16052 });
16053 }
16054 for (child_table, entries) in setnull_plan {
16055 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16056 steps.push(FkChildStep {
16057 child_table,
16058 action: FkChildAction::SetNull { positions, columns },
16059 });
16060 }
16061 for (child_table, entries) in setdefault_plan {
16062 let mut positions = Vec::with_capacity(entries.len());
16063 let mut columns = Vec::with_capacity(entries.len());
16064 let mut defaults = Vec::with_capacity(entries.len());
16065 for ((p, c), v) in entries {
16066 positions.push(p);
16067 columns.push(c);
16068 defaults.push(v);
16069 }
16070 steps.push(FkChildStep {
16071 child_table,
16072 action: FkChildAction::SetDefault {
16073 positions,
16074 columns,
16075 defaults,
16076 },
16077 });
16078 }
16079 let _ = delete_plan; Ok(steps)
16081}
16082
16083fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
16087 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
16088 EngineError::Storage(StorageError::TableNotFound {
16089 name: step.child_table.clone(),
16090 })
16091 })?;
16092 match &step.action {
16093 FkChildAction::Delete { positions } => {
16094 let _ = child.delete_rows(positions);
16095 }
16096 FkChildAction::SetNull { positions, columns } => {
16097 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
16098 }
16099 FkChildAction::SetDefault {
16100 positions,
16101 columns,
16102 defaults,
16103 } => {
16104 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
16105 }
16106 }
16107 Ok(())
16108}
16109
16110fn apply_per_cell_writes(
16116 child: &mut spg_storage::Table,
16117 positions: &[usize],
16118 columns: &[usize],
16119 mut value_for: impl FnMut(usize) -> Value,
16120) -> Result<(), EngineError> {
16121 use alloc::collections::BTreeMap;
16122 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
16123 for i in 0..positions.len() {
16124 by_row
16125 .entry(positions[i])
16126 .or_default()
16127 .push((columns[i], value_for(i)));
16128 }
16129 for (pos, mutations) in by_row {
16130 let mut new_values = child.rows()[pos].values.clone();
16131 for (col, v) in mutations {
16132 if let Some(slot) = new_values.get_mut(col) {
16133 *slot = v;
16134 }
16135 }
16136 child
16137 .update_row(pos, new_values)
16138 .map_err(EngineError::Storage)?;
16139 }
16140 Ok(())
16141}
16142
16143fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
16144 match a {
16145 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
16146 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
16147 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
16148 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
16149 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
16150 }
16151}
16152
16153fn resolve_column_default_free(
16159 col: &ColumnSchema,
16160 clock_fn: Option<ClockFn>,
16161) -> Result<Value, EngineError> {
16162 if let Some(rt) = &col.runtime_default {
16163 return eval_runtime_default_free(rt, col.ty, clock_fn);
16164 }
16165 Ok(col.default.clone().unwrap_or(Value::Null))
16166}
16167
16168fn eval_runtime_default_free(
16169 rt: &str,
16170 ty: DataType,
16171 clock_fn: Option<ClockFn>,
16172) -> Result<Value, EngineError> {
16173 let s = rt.trim().to_ascii_lowercase();
16174 let with_no_parens = s.trim_end_matches("()");
16180 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
16181 if with_no_parens.ends_with(')') {
16182 &with_no_parens[..open_idx]
16183 } else {
16184 with_no_parens
16185 }
16186 } else {
16187 with_no_parens
16188 };
16189 let now_us = match clock_fn {
16190 Some(f) => f(),
16191 None => 0,
16192 };
16193 let v = match canonical {
16194 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
16195 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
16196 "current_time" | "localtime" => Value::Timestamp(now_us),
16197 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
16203 other => {
16204 return Err(EngineError::Unsupported(alloc::format!(
16205 "runtime DEFAULT expression {other:?} not supported \
16206 (v7.17.0 whitelist: now() / current_timestamp / \
16207 current_date / current_time / localtimestamp / \
16208 localtime / gen_random_uuid() / \
16209 uuid_generate_v4())"
16210 )));
16211 }
16212 };
16213 coerce_value(v, ty, "DEFAULT", 0)
16214}
16215
16216fn is_runtime_default_expr(expr: &Expr) -> bool {
16222 match expr {
16223 Expr::FunctionCall { .. } => true,
16224 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
16225 _ => false,
16226 }
16227}
16228
16229fn canonicalize_set_value(
16242 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16243 col_idx: usize,
16244 col_name: &str,
16245 value: Value,
16246) -> Result<Value, EngineError> {
16247 let Some(variants) = lookup.get(&col_idx) else {
16248 return Ok(value);
16249 };
16250 match value {
16251 Value::Null => Ok(Value::Null),
16252 Value::Text(s) => {
16253 if s.is_empty() {
16254 return Ok(Value::Text(alloc::string::String::new()));
16255 }
16256 let mut present = alloc::vec![false; variants.len()];
16259 for raw in s.split(',') {
16260 let tok = raw.trim();
16261 if tok.is_empty() {
16262 continue;
16263 }
16264 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
16265 EngineError::Unsupported(alloc::format!(
16266 "column {col_name:?}: invalid SET token {tok:?}; \
16267 allowed: {variants:?}"
16268 ))
16269 })?;
16270 present[idx] = true;
16271 }
16272 let mut out = alloc::string::String::new();
16274 let mut first = true;
16275 for (i, keep) in present.iter().enumerate() {
16276 if !keep {
16277 continue;
16278 }
16279 if !first {
16280 out.push(',');
16281 }
16282 first = false;
16283 out.push_str(&variants[i]);
16284 }
16285 Ok(Value::Text(out))
16286 }
16287 other => Err(EngineError::Unsupported(alloc::format!(
16288 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16289 other.data_type()
16290 ))),
16291 }
16292}
16293
16294fn enforce_enum_label(
16295 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16296 col_idx: usize,
16297 col_name: &str,
16298 value: &Value,
16299) -> Result<(), EngineError> {
16300 if let Some(labels) = lookup.get(&col_idx) {
16301 match value {
16302 Value::Null => Ok(()),
16303 Value::Text(s) => {
16304 if labels.iter().any(|l| l == s) {
16305 Ok(())
16306 } else {
16307 Err(EngineError::Unsupported(alloc::format!(
16308 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16309 )))
16310 }
16311 }
16312 other => Err(EngineError::Unsupported(alloc::format!(
16313 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16314 other.data_type()
16315 ))),
16316 }
16317 } else {
16318 Ok(())
16319 }
16320}
16321
16322fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16323 let ty = column_type_to_data_type(c.ty);
16324 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16325 if let Some(name) = c.user_type_ref {
16332 schema.user_enum_type = Some(name);
16333 }
16334 if let Some(expr) = c.on_update_runtime {
16337 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16338 }
16339 schema.collation = match c.collation {
16343 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16344 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16345 };
16346 schema.is_unsigned = c.is_unsigned;
16349 schema.inline_enum_variants = c.inline_enum_variants;
16353 schema.inline_set_variants = c.inline_set_variants;
16357 if let Some(default_expr) = c.default {
16358 if is_runtime_default_expr(&default_expr) {
16364 let display = alloc::format!("{default_expr}");
16365 schema = schema.with_runtime_default(display);
16366 } else {
16367 let raw = literal_expr_to_value(default_expr)?;
16368 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16369 schema = schema.with_default(coerced);
16370 }
16371 }
16372 if c.auto_increment {
16373 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16375 return Err(EngineError::Unsupported(alloc::format!(
16376 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16377 )));
16378 }
16379 schema = schema.with_auto_increment();
16380 }
16381 Ok(schema)
16382}
16383
16384fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16389 let s = s.trim();
16390 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16391 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16393 if cleaned.len() % 2 != 0 {
16394 return Err("odd-length hex literal");
16395 }
16396 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16397 let cleaned_bytes = cleaned.as_bytes();
16398 for i in (0..cleaned_bytes.len()).step_by(2) {
16399 let hi = hex_nibble(cleaned_bytes[i])?;
16400 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16401 out.push((hi << 4) | lo);
16402 }
16403 return Ok(out);
16404 }
16405 let bytes = s.as_bytes();
16408 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16409 let mut i = 0;
16410 while i < bytes.len() {
16411 let b = bytes[i];
16412 if b == b'\\' && i + 1 < bytes.len() {
16413 let n = bytes[i + 1];
16414 if n == b'\\' {
16415 out.push(b'\\');
16416 i += 2;
16417 continue;
16418 }
16419 if n.is_ascii_digit()
16420 && i + 3 < bytes.len()
16421 && bytes[i + 2].is_ascii_digit()
16422 && bytes[i + 3].is_ascii_digit()
16423 {
16424 let oct = |x: u8| (x - b'0') as u32;
16425 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16426 if v <= 0xFF {
16427 out.push(v as u8);
16428 i += 4;
16429 continue;
16430 }
16431 }
16432 }
16433 out.push(b);
16434 i += 1;
16435 }
16436 Ok(out)
16437}
16438
16439fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16440 match b {
16441 b'0'..=b'9' => Ok(b - b'0'),
16442 b'a'..=b'f' => Ok(b - b'a' + 10),
16443 b'A'..=b'F' => Ok(b - b'A' + 10),
16444 _ => Err("invalid hex digit"),
16445 }
16446}
16447
16448fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16462 let mut has_text = false;
16463 let mut has_bigint = false;
16464 let mut has_int = false;
16465 for v in &items {
16466 match v {
16467 Value::Null => {}
16468 Value::Text(_) | Value::Json(_) => has_text = true,
16469 Value::BigInt(_) => has_bigint = true,
16470 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16471 _ => has_text = true,
16472 }
16473 }
16474 if has_text || (!has_bigint && !has_int) {
16475 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16476 .into_iter()
16477 .map(|v| match v {
16478 Value::Null => None,
16479 Value::Text(s) | Value::Json(s) => Some(s),
16480 other => Some(alloc::format!("{other:?}")),
16481 })
16482 .collect();
16483 return Value::TextArray(out);
16484 }
16485 if has_bigint {
16486 let out: alloc::vec::Vec<Option<i64>> = items
16487 .into_iter()
16488 .map(|v| match v {
16489 Value::Null => None,
16490 Value::Int(n) => Some(i64::from(n)),
16491 Value::SmallInt(n) => Some(i64::from(n)),
16492 Value::BigInt(n) => Some(n),
16493 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16494 })
16495 .collect();
16496 return Value::BigIntArray(out);
16497 }
16498 let out: alloc::vec::Vec<Option<i32>> = items
16499 .into_iter()
16500 .map(|v| match v {
16501 Value::Null => None,
16502 Value::Int(n) => Some(n),
16503 Value::SmallInt(n) => Some(i32::from(n)),
16504 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16505 })
16506 .collect();
16507 Value::IntArray(out)
16508}
16509
16510fn decode_text_array_literal(
16511 s: &str,
16512) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16513 let trimmed = s.trim();
16514 let inner = trimmed
16515 .strip_prefix('{')
16516 .and_then(|x| x.strip_suffix('}'))
16517 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16518 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16519 if inner.trim().is_empty() {
16520 return Ok(out);
16521 }
16522 let bytes = inner.as_bytes();
16523 let mut i = 0;
16524 while i <= bytes.len() {
16525 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16527 i += 1;
16528 }
16529 if i < bytes.len() && bytes[i] == b'"' {
16531 i += 1; let mut buf = alloc::string::String::new();
16533 while i < bytes.len() && bytes[i] != b'"' {
16534 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16535 buf.push(bytes[i + 1] as char);
16536 i += 2;
16537 } else {
16538 buf.push(bytes[i] as char);
16539 i += 1;
16540 }
16541 }
16542 if i >= bytes.len() {
16543 return Err("unterminated quoted element");
16544 }
16545 i += 1; out.push(Some(buf));
16547 } else {
16548 let start = i;
16550 while i < bytes.len() && bytes[i] != b',' {
16551 i += 1;
16552 }
16553 let raw = inner[start..i].trim();
16554 if raw.eq_ignore_ascii_case("NULL") {
16555 out.push(None);
16556 } else {
16557 out.push(Some(alloc::string::ToString::to_string(raw)));
16558 }
16559 }
16560 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16562 i += 1;
16563 }
16564 if i >= bytes.len() {
16565 break;
16566 }
16567 if bytes[i] != b',' {
16568 return Err("expected ',' between TEXT[] elements");
16569 }
16570 i += 1;
16571 }
16572 Ok(out)
16573}
16574
16575fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16580 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16581 out.push('{');
16582 for (i, item) in items.iter().enumerate() {
16583 if i > 0 {
16584 out.push(',');
16585 }
16586 match item {
16587 None => out.push_str("NULL"),
16588 Some(s) => {
16589 let needs_quote = s.is_empty()
16590 || s.eq_ignore_ascii_case("NULL")
16591 || s.chars()
16592 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16593 if needs_quote {
16594 out.push('"');
16595 for c in s.chars() {
16596 if c == '"' || c == '\\' {
16597 out.push('\\');
16598 }
16599 out.push(c);
16600 }
16601 out.push('"');
16602 } else {
16603 out.push_str(s);
16604 }
16605 }
16606 }
16607 }
16608 out.push('}');
16609 out
16610}
16611
16612fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16616 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16617 out.push_str("\\x");
16618 for byte in b {
16619 let hi = byte >> 4;
16620 let lo = byte & 0x0F;
16621 out.push(hex_digit(hi));
16622 out.push(hex_digit(lo));
16623 }
16624 out
16625}
16626
16627const fn hex_digit(n: u8) -> char {
16628 match n {
16629 0..=9 => (b'0' + n) as char,
16630 10..=15 => (b'a' + n - 10) as char,
16631 _ => '?',
16632 }
16633}
16634
16635fn parse_hstore_str(
16647 s: &str,
16648) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16649 let bytes = s.as_bytes();
16650 let mut i = 0;
16651 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16652 let skip_ws = |bytes: &[u8], i: &mut usize| {
16653 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16654 *i += 1;
16655 }
16656 };
16657 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16658 if *i >= bytes.len() {
16659 return None;
16660 }
16661 if bytes[*i] == b'"' {
16662 *i += 1;
16663 let mut out = alloc::string::String::new();
16664 while *i < bytes.len() {
16665 match bytes[*i] {
16666 b'"' => {
16667 *i += 1;
16668 return Some(out);
16669 }
16670 b'\\' if *i + 1 < bytes.len() => {
16671 out.push(bytes[*i + 1] as char);
16672 *i += 2;
16673 }
16674 c => {
16675 out.push(c as char);
16676 *i += 1;
16677 }
16678 }
16679 }
16680 None
16681 } else {
16682 let start = *i;
16683 while *i < bytes.len()
16684 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16685 {
16686 *i += 1;
16687 }
16688 if *i == start {
16689 return None;
16690 }
16691 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16692 }
16693 };
16694 skip_ws(bytes, &mut i);
16695 while i < bytes.len() {
16696 let key = parse_token(bytes, &mut i)?;
16697 skip_ws(bytes, &mut i);
16698 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16699 return None;
16700 }
16701 i += 2;
16702 skip_ws(bytes, &mut i);
16703 let val_token = if i + 4 <= bytes.len()
16705 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16706 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16707 {
16708 i += 4;
16709 None
16710 } else {
16711 Some(parse_token(bytes, &mut i)?)
16712 };
16713 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16715 out[pos] = (key, val_token);
16716 } else {
16717 out.push((key, val_token));
16718 }
16719 skip_ws(bytes, &mut i);
16720 if i >= bytes.len() {
16721 break;
16722 }
16723 if bytes[i] == b',' {
16724 i += 1;
16725 skip_ws(bytes, &mut i);
16726 continue;
16727 }
16728 return None;
16729 }
16730 Some(out)
16731}
16732
16733fn format_hstore_str(
16737 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16738) -> alloc::string::String {
16739 let mut out = alloc::string::String::new();
16740 for (i, (k, v)) in pairs.iter().enumerate() {
16741 if i > 0 {
16742 out.push_str(", ");
16743 }
16744 out.push('"');
16745 out.push_str(k);
16746 out.push_str("\"=>");
16747 match v {
16748 None => out.push_str("NULL"),
16749 Some(val) => {
16750 out.push('"');
16751 out.push_str(val);
16752 out.push('"');
16753 }
16754 }
16755 }
16756 out
16757}
16758
16759pub fn format_hstore_text(
16762 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16763) -> alloc::string::String {
16764 format_hstore_str(pairs)
16765}
16766
16767fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16772 let s = s.trim();
16773 let outer = s
16774 .strip_prefix('{')
16775 .and_then(|x| x.strip_suffix('}'))
16776 .ok_or("missing outer '{...}' braces")?;
16777 let trimmed = outer.trim();
16778 if trimmed.is_empty() {
16779 return Ok(Vec::new());
16780 }
16781 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16782 let mut i = 0;
16783 let bytes = trimmed.as_bytes();
16784 while i < bytes.len() {
16785 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16786 i += 1;
16787 }
16788 if i >= bytes.len() {
16789 break;
16790 }
16791 if bytes[i] != b'{' {
16792 return Err("expected '{' opening a row");
16793 }
16794 i += 1;
16795 let row_start = i;
16796 let mut depth = 1;
16797 while i < bytes.len() && depth > 0 {
16798 match bytes[i] {
16799 b'{' => depth += 1,
16800 b'}' => depth -= 1,
16801 _ => {}
16802 }
16803 if depth > 0 {
16804 i += 1;
16805 }
16806 }
16807 if depth != 0 {
16808 return Err("unbalanced '{...}' in row");
16809 }
16810 let row_text = &trimmed[row_start..i];
16811 i += 1;
16812 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16813 Vec::new()
16814 } else {
16815 row_text.split(',').map(|t| t.trim().to_string()).collect()
16816 };
16817 rows.push(cells);
16818 }
16819 if let Some(first) = rows.first() {
16820 let cols = first.len();
16821 for r in &rows {
16822 if r.len() != cols {
16823 return Err("ragged 2D array (rows have different column counts)");
16824 }
16825 }
16826 }
16827 Ok(rows)
16828}
16829
16830fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16831 let raw = split_2d_literal(s)?;
16832 raw.into_iter()
16833 .map(|row| {
16834 row.into_iter()
16835 .map(|cell| {
16836 if cell.eq_ignore_ascii_case("NULL") {
16837 Ok(None)
16838 } else {
16839 cell.parse::<i32>()
16840 .map(Some)
16841 .map_err(|_| "invalid int element")
16842 }
16843 })
16844 .collect()
16845 })
16846 .collect()
16847}
16848
16849fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16850 let raw = split_2d_literal(s)?;
16851 raw.into_iter()
16852 .map(|row| {
16853 row.into_iter()
16854 .map(|cell| {
16855 if cell.eq_ignore_ascii_case("NULL") {
16856 Ok(None)
16857 } else {
16858 cell.parse::<i64>()
16859 .map(Some)
16860 .map_err(|_| "invalid bigint element")
16861 }
16862 })
16863 .collect()
16864 })
16865 .collect()
16866}
16867
16868fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16869 let raw = split_2d_literal(s)?;
16870 Ok(raw
16871 .into_iter()
16872 .map(|row| {
16873 row.into_iter()
16874 .map(|cell| {
16875 if cell.eq_ignore_ascii_case("NULL") {
16876 None
16877 } else {
16878 Some(cell.trim_matches('"').to_string())
16879 }
16880 })
16881 .collect()
16882 })
16883 .collect())
16884}
16885
16886fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16887 let mut out = alloc::string::String::from("{");
16888 for (i, row) in rows.iter().enumerate() {
16889 if i > 0 {
16890 out.push(',');
16891 }
16892 out.push('{');
16893 for (j, cell) in row.iter().enumerate() {
16894 if j > 0 {
16895 out.push(',');
16896 }
16897 match cell {
16898 None => out.push_str("NULL"),
16899 Some(n) => out.push_str(&alloc::format!("{n}")),
16900 }
16901 }
16902 out.push('}');
16903 }
16904 out.push('}');
16905 out
16906}
16907
16908fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16909 let mut out = alloc::string::String::from("{");
16910 for (i, row) in rows.iter().enumerate() {
16911 if i > 0 {
16912 out.push(',');
16913 }
16914 out.push('{');
16915 for (j, cell) in row.iter().enumerate() {
16916 if j > 0 {
16917 out.push(',');
16918 }
16919 match cell {
16920 None => out.push_str("NULL"),
16921 Some(n) => out.push_str(&alloc::format!("{n}")),
16922 }
16923 }
16924 out.push('}');
16925 }
16926 out.push('}');
16927 out
16928}
16929
16930fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16931 let mut out = alloc::string::String::from("{");
16932 for (i, row) in rows.iter().enumerate() {
16933 if i > 0 {
16934 out.push(',');
16935 }
16936 out.push('{');
16937 for (j, cell) in row.iter().enumerate() {
16938 if j > 0 {
16939 out.push(',');
16940 }
16941 match cell {
16942 None => out.push_str("NULL"),
16943 Some(s) => out.push_str(s),
16944 }
16945 }
16946 out.push('}');
16947 }
16948 out.push('}');
16949 out
16950}
16951
16952pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16955 format_int_2d_text(rows)
16956}
16957pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16958 format_bigint_2d_text(rows)
16959}
16960pub fn format_text_2d_text_pub(
16961 rows: &[Vec<Option<alloc::string::String>>],
16962) -> alloc::string::String {
16963 format_text_2d_text(rows)
16964}
16965
16966fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16971 let s = s.trim();
16972 if s.eq_ignore_ascii_case("empty") {
16973 return Some(Value::Range {
16974 kind,
16975 lower: None,
16976 upper: None,
16977 lower_inc: false,
16978 upper_inc: false,
16979 empty: true,
16980 });
16981 }
16982 let bytes = s.as_bytes();
16983 if bytes.len() < 3 {
16984 return None;
16985 }
16986 let lower_inc = match bytes[0] {
16987 b'[' => true,
16988 b'(' => false,
16989 _ => return None,
16990 };
16991 let upper_inc = match bytes[bytes.len() - 1] {
16992 b']' => true,
16993 b')' => false,
16994 _ => return None,
16995 };
16996 let inner = &s[1..s.len() - 1];
16997 let (lo_text, up_text) = inner.split_once(',')?;
16998 let lower = if lo_text.is_empty() {
16999 None
17000 } else {
17001 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
17002 };
17003 let upper = if up_text.is_empty() {
17004 None
17005 } else {
17006 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
17007 };
17008 Some(Value::Range {
17009 kind,
17010 lower,
17011 upper,
17012 lower_inc,
17013 upper_inc,
17014 empty: false,
17015 })
17016}
17017
17018fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17021 let text = text.trim().trim_matches('"');
17022 use spg_storage::RangeKind as K;
17023 match kind {
17024 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
17025 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
17026 K::Num => {
17027 let dot = text.find('.');
17030 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
17031 let digits: alloc::string::String = text
17032 .chars()
17033 .filter(|c| *c == '-' || c.is_ascii_digit())
17034 .collect();
17035 let scaled: i128 = digits.parse().ok()?;
17036 Some(Value::Numeric { scaled, scale })
17037 }
17038 K::Ts | K::TsTz => {
17039 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
17044 }
17045 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
17046 }
17047}
17048
17049pub fn format_range_text(v: &Value) -> alloc::string::String {
17053 format_range_str(v)
17054}
17055
17056fn format_range_str(v: &Value) -> alloc::string::String {
17057 let Value::Range {
17058 lower,
17059 upper,
17060 lower_inc,
17061 upper_inc,
17062 empty,
17063 ..
17064 } = v
17065 else {
17066 return alloc::string::String::new();
17067 };
17068 if *empty {
17069 return "empty".into();
17070 }
17071 let mut out = alloc::string::String::new();
17072 out.push(if *lower_inc { '[' } else { '(' });
17073 if let Some(l) = lower {
17074 out.push_str(&format_range_element(l));
17075 }
17076 out.push(',');
17077 if let Some(u) = upper {
17078 out.push_str(&format_range_element(u));
17079 }
17080 out.push(if *upper_inc { ']' } else { ')' });
17081 out
17082}
17083
17084fn format_range_element(v: &Value) -> alloc::string::String {
17085 match v {
17086 Value::Int(n) => alloc::format!("{n}"),
17087 Value::BigInt(n) => alloc::format!("{n}"),
17088 Value::Date(d) => crate::eval::format_date(*d),
17089 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
17090 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
17091 other => alloc::format!("{other:?}"),
17092 }
17093}
17094
17095fn parse_money_str(s: &str) -> Option<i64> {
17106 let s = s.trim();
17107 let (neg, rest) = match s.strip_prefix('-') {
17108 Some(r) => (true, r.trim_start()),
17109 None => (false, s),
17110 };
17111 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
17112 let (int_part, frac_part) = match rest.split_once('.') {
17113 Some((i, f)) => (i, Some(f)),
17114 None => (rest, None),
17115 };
17116 if int_part.is_empty() {
17117 return None;
17118 }
17119 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
17121 for b in int_part.bytes() {
17122 match b {
17123 b',' => {}
17124 b'0'..=b'9' => int_digits.push(b as char),
17125 _ => return None,
17126 }
17127 }
17128 if int_digits.is_empty() {
17129 return None;
17130 }
17131 let dollars: i64 = int_digits.parse().ok()?;
17132 let cents: i64 = match frac_part {
17133 None => 0,
17134 Some(f) => {
17135 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
17136 return None;
17137 }
17138 let padded = if f.len() == 1 {
17139 alloc::format!("{f}0")
17140 } else {
17141 f.to_string()
17142 };
17143 padded.parse().ok()?
17144 }
17145 };
17146 let total = dollars.checked_mul(100)?.checked_add(cents)?;
17147 Some(if neg { -total } else { total })
17148}
17149
17150fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
17161 let s = s.trim();
17162 let bytes = s.as_bytes();
17166 let sign_pos = bytes
17167 .iter()
17168 .enumerate()
17169 .rev()
17170 .find(|&(_, &b)| b == b'+' || b == b'-')
17171 .map(|(i, _)| i)?;
17172 if sign_pos == 0 {
17173 return None; }
17175 let time_part = &s[..sign_pos];
17176 let offset_part = &s[sign_pos..];
17177 let us = parse_time_str(time_part)?;
17178 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
17179 let offset_body = &offset_part[1..];
17180 let (hh_str, mm_str) = match offset_body.split_once(':') {
17181 Some((h, m)) => (h, m),
17182 None => (offset_body, "0"),
17183 };
17184 let hh: i32 = hh_str.parse().ok()?;
17185 let mm: i32 = mm_str.parse().ok()?;
17186 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
17187 return None;
17188 }
17189 let total = sign * (hh * 3600 + mm * 60);
17190 if total.abs() > 50_400 {
17191 return None;
17192 }
17193 Some((us, total))
17194}
17195
17196fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
17201 if n == 0 || (1901..=2155).contains(&n) {
17202 return Ok(Value::Year(n as u16));
17205 }
17206 Err(EngineError::Eval(EvalError::TypeMismatch {
17207 detail: alloc::format!(
17208 "year value out of range: {n} (column `{col_name}`; \
17209 MySQL accepts 0 or 1901..=2155)"
17210 ),
17211 }))
17212}
17213
17214fn parse_time_str(s: &str) -> Option<i64> {
17226 let s = s.trim();
17227 let (hms, frac) = match s.split_once('.') {
17228 Some((h, f)) => (h, Some(f)),
17229 None => (s, None),
17230 };
17231 let mut parts = hms.split(':');
17232 let hh: u32 = parts.next()?.parse().ok()?;
17233 let mm: u32 = parts.next()?.parse().ok()?;
17234 let ss: u32 = parts.next()?.parse().ok()?;
17235 if parts.next().is_some() {
17236 return None;
17237 }
17238 if hh > 23 || mm > 59 || ss > 59 {
17239 return None;
17240 }
17241 let frac_us: i64 = match frac {
17242 None => 0,
17243 Some(f) => {
17244 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
17245 return None;
17246 }
17247 let mut padded = alloc::string::String::with_capacity(6);
17249 padded.push_str(f);
17250 while padded.len() < 6 {
17251 padded.push('0');
17252 }
17253 padded.parse().ok()?
17254 }
17255 };
17256 Some(
17257 i64::from(hh) * 3_600_000_000
17258 + i64::from(mm) * 60_000_000
17259 + i64::from(ss) * 1_000_000
17260 + frac_us,
17261 )
17262}
17263
17264const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
17265 match t {
17266 ColumnTypeName::SmallInt => DataType::SmallInt,
17267 ColumnTypeName::Int => DataType::Int,
17268 ColumnTypeName::BigInt => DataType::BigInt,
17269 ColumnTypeName::Float => DataType::Float,
17270 ColumnTypeName::Text => DataType::Text,
17271 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17272 ColumnTypeName::Char(n) => DataType::Char(n),
17273 ColumnTypeName::Bool => DataType::Bool,
17274 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17275 dim,
17276 encoding: match encoding {
17277 SqlVecEncoding::F32 => VecEncoding::F32,
17278 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17279 SqlVecEncoding::F16 => VecEncoding::F16,
17280 },
17281 },
17282 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17283 ColumnTypeName::Date => DataType::Date,
17284 ColumnTypeName::Timestamp => DataType::Timestamp,
17285 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17286 ColumnTypeName::Json => DataType::Json,
17287 ColumnTypeName::Jsonb => DataType::Jsonb,
17288 ColumnTypeName::Bytes => DataType::Bytes,
17289 ColumnTypeName::TextArray => DataType::TextArray,
17290 ColumnTypeName::IntArray => DataType::IntArray,
17291 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17292 ColumnTypeName::TsVector => DataType::TsVector,
17293 ColumnTypeName::TsQuery => DataType::TsQuery,
17294 ColumnTypeName::Uuid => DataType::Uuid,
17295 ColumnTypeName::Time => DataType::Time,
17296 ColumnTypeName::Year => DataType::Year,
17297 ColumnTypeName::TimeTz => DataType::TimeTz,
17298 ColumnTypeName::Money => DataType::Money,
17299 ColumnTypeName::Range(k) => DataType::Range(match k {
17300 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17301 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17302 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17303 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17304 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17305 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17306 }),
17307 ColumnTypeName::Hstore => DataType::Hstore,
17308 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17309 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17310 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17311 }
17312}
17313
17314fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17318 match expr {
17319 Expr::Literal(l) => Ok(literal_to_value(l)),
17320 Expr::Cast { expr, target } => {
17321 let inner_value = literal_expr_to_value(*expr)?;
17322 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17323 }
17324 Expr::Unary {
17325 op: UnOp::Neg,
17326 expr,
17327 } => match *expr {
17328 Expr::Literal(Literal::Integer(n)) => {
17329 let neg = n.checked_neg().ok_or_else(|| {
17332 EngineError::Unsupported("integer literal overflow on negation".into())
17333 })?;
17334 Ok(int_value_for(neg))
17335 }
17336 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17337 other => Err(EngineError::Unsupported(alloc::format!(
17338 "unary minus over non-literal expression: {other:?}"
17339 ))),
17340 },
17341 Expr::Array(items) => {
17349 let mut materialised: alloc::vec::Vec<Value> =
17350 alloc::vec::Vec::with_capacity(items.len());
17351 for elem in items {
17352 materialised.push(literal_expr_to_value(elem)?);
17353 }
17354 Ok(array_literal_widen(materialised))
17355 }
17356 other => {
17369 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17370 let ctx = EvalContext::new(&empty_schema, None);
17371 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17372 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17373 }
17374 }
17375}
17376
17377fn literal_to_value(l: Literal) -> Value {
17378 match l {
17379 Literal::Integer(n) => int_value_for(n),
17380 Literal::Float(x) => Value::Float(x),
17381 Literal::String(s) => Value::Text(s),
17382 Literal::Bool(b) => Value::Bool(b),
17383 Literal::Null => Value::Null,
17384 Literal::Vector(v) => Value::Vector(v),
17385 Literal::TextArray(items) => Value::TextArray(items),
17386 Literal::IntArray(items) => Value::IntArray(items),
17387 Literal::BigIntArray(items) => Value::BigIntArray(items),
17388 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17389 }
17390}
17391
17392fn int_value_for(n: i64) -> Value {
17396 if let Ok(small) = i32::try_from(n) {
17397 Value::Int(small)
17398 } else {
17399 Value::BigInt(n)
17400 }
17401}
17402
17403#[allow(clippy::too_many_lines)]
17409fn check_unsigned_range(
17414 v: &Value,
17415 schema: &ColumnSchema,
17416 position: usize,
17417) -> Result<(), EngineError> {
17418 if !schema.is_unsigned {
17419 return Ok(());
17420 }
17421 let n = match v {
17422 Value::SmallInt(x) => i64::from(*x),
17423 Value::Int(x) => i64::from(*x),
17424 Value::BigInt(x) => *x,
17425 _ => return Ok(()), };
17427 if n < 0 {
17428 return Err(EngineError::Unsupported(alloc::format!(
17429 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17430 schema.name
17431 )));
17432 }
17433 Ok(())
17434}
17435
17436fn coerce_value(
17437 v: Value,
17438 expected: DataType,
17439 col_name: &str,
17440 position: usize,
17441) -> Result<Value, EngineError> {
17442 if v.is_null() {
17443 return Ok(Value::Null);
17444 }
17445 let actual = v.data_type().expect("non-null");
17446 if actual == expected {
17447 return Ok(v);
17448 }
17449 let coerced = match (v, expected) {
17450 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17451 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17452 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17453 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17454 i128::from(n),
17455 precision,
17456 scale,
17457 col_name,
17458 )?),
17459 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17460 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17461 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17462 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17463 i128::from(n),
17464 precision,
17465 scale,
17466 col_name,
17467 )?),
17468 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17469 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17470 #[allow(clippy::cast_precision_loss)]
17471 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17472 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17473 i128::from(n),
17474 precision,
17475 scale,
17476 col_name,
17477 )?),
17478 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17479 Some(numeric_from_float(x, precision, scale, col_name)?)
17480 }
17481 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17492 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17493 return Err(EngineError::Eval(EvalError::TypeMismatch {
17494 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17495 }));
17496 };
17497 Some(numeric_rescale(
17498 mantissa, src_scale, precision, scale, col_name,
17499 )?)
17500 }
17501 (Value::Text(s), DataType::Date) => {
17503 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17504 EngineError::Eval(EvalError::TypeMismatch {
17505 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17506 })
17507 })?;
17508 Some(Value::Date(d))
17509 }
17510 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17517 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17518 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17519 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17520 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17521 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17522 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17523 _ => None,
17524 },
17525 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17534 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17535 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17536 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17540 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17541 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17549 (Value::Text(s), DataType::Bytes) => {
17556 let bytes = decode_bytea_literal(&s).map_err(|e| {
17557 EngineError::Eval(EvalError::TypeMismatch {
17558 detail: alloc::format!(
17559 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17560 ),
17561 })
17562 })?;
17563 Some(Value::Bytes(bytes))
17564 }
17565 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17569 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17577 Some(b) => Some(Value::Uuid(b)),
17578 None => {
17579 return Err(EngineError::Eval(EvalError::TypeMismatch {
17580 detail: alloc::format!(
17581 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17582 ),
17583 }));
17584 }
17585 },
17586 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17591 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17597 Some(us) => Some(Value::Time(us)),
17598 None => {
17599 return Err(EngineError::Eval(EvalError::TypeMismatch {
17600 detail: alloc::format!(
17601 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17602 ),
17603 }));
17604 }
17605 },
17606 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17608 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17613 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17614 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17615 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17619 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17620 Err(_) => {
17621 return Err(EngineError::Eval(EvalError::TypeMismatch {
17622 detail: alloc::format!(
17623 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17624 ),
17625 }));
17626 }
17627 },
17628 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17630 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17634 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17635 None => {
17636 return Err(EngineError::Eval(EvalError::TypeMismatch {
17637 detail: alloc::format!(
17638 "invalid input syntax for type time with time zone: \
17639 {s:?} (column `{col_name}`)"
17640 ),
17641 }));
17642 }
17643 },
17644 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17646 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17647 }
17648 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17652 Some(c) => Some(Value::Money(c)),
17653 None => {
17654 return Err(EngineError::Eval(EvalError::TypeMismatch {
17655 detail: alloc::format!(
17656 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17657 ),
17658 }));
17659 }
17660 },
17661 (Value::SmallInt(n), DataType::Money) => {
17665 Some(Value::Money(i64::from(n).saturating_mul(100)))
17666 }
17667 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17668 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17669 (Value::Float(x), DataType::Money) => {
17670 let scaled = x * 100.0;
17673 let cents = if scaled >= 0.0 {
17674 (scaled + 0.5) as i64
17675 } else {
17676 (scaled - 0.5) as i64
17677 };
17678 Some(Value::Money(cents))
17679 }
17680 (Value::Numeric { scaled, scale }, DataType::Money) => {
17681 let cents = if scale == 2 {
17684 scaled
17685 } else if scale < 2 {
17686 let mult = 10_i128.pow(u32::from(2 - scale));
17687 scaled.saturating_mul(mult)
17688 } else {
17689 let div = 10_i128.pow(u32::from(scale - 2));
17690 let half = div / 2;
17691 let bias = if scaled >= 0 { half } else { -half };
17692 (scaled + bias) / div
17693 };
17694 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17695 }
17696 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17698 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17702 Some(v) => Some(v),
17703 None => {
17704 return Err(EngineError::Eval(EvalError::TypeMismatch {
17705 detail: alloc::format!(
17706 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17707 ),
17708 }));
17709 }
17710 },
17711 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17713 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17715 Some(pairs) => Some(Value::Hstore(pairs)),
17716 None => {
17717 return Err(EngineError::Eval(EvalError::TypeMismatch {
17718 detail: alloc::format!(
17719 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17720 ),
17721 }));
17722 }
17723 },
17724 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17726 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17729 Ok(m) => Some(Value::IntArray2D(m)),
17730 Err(e) => {
17731 return Err(EngineError::Eval(EvalError::TypeMismatch {
17732 detail: alloc::format!(
17733 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17734 ),
17735 }));
17736 }
17737 },
17738 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17739 Ok(m) => Some(Value::BigIntArray2D(m)),
17740 Err(e) => {
17741 return Err(EngineError::Eval(EvalError::TypeMismatch {
17742 detail: alloc::format!(
17743 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17744 ),
17745 }));
17746 }
17747 },
17748 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17749 Ok(m) => Some(Value::TextArray2D(m)),
17750 Err(e) => {
17751 return Err(EngineError::Eval(EvalError::TypeMismatch {
17752 detail: alloc::format!(
17753 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17754 ),
17755 }));
17756 }
17757 },
17758 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17760 (Value::BigIntArray2D(rows), DataType::Text) => {
17761 Some(Value::Text(format_bigint_2d_text(&rows)))
17762 }
17763 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17764 (Value::Text(s), DataType::TextArray) => {
17769 let arr = decode_text_array_literal(&s).map_err(|e| {
17770 EngineError::Eval(EvalError::TypeMismatch {
17771 detail: alloc::format!(
17772 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17773 ),
17774 })
17775 })?;
17776 Some(Value::TextArray(arr))
17777 }
17778 (Value::Text(s), DataType::IntArray) => {
17784 let arr = decode_text_array_literal(&s).map_err(|e| {
17785 EngineError::Eval(EvalError::TypeMismatch {
17786 detail: alloc::format!(
17787 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17788 ),
17789 })
17790 })?;
17791 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17792 for elem in arr {
17793 match elem {
17794 None => out.push(None),
17795 Some(t) => {
17796 let n: i32 = t.parse().map_err(|_| {
17797 EngineError::Eval(EvalError::TypeMismatch {
17798 detail: alloc::format!(
17799 "cannot parse {t:?} as INT element for `{col_name}`"
17800 ),
17801 })
17802 })?;
17803 out.push(Some(n));
17804 }
17805 }
17806 }
17807 Some(Value::IntArray(out))
17808 }
17809 (Value::Text(s), DataType::BigIntArray) => {
17810 let arr = decode_text_array_literal(&s).map_err(|e| {
17811 EngineError::Eval(EvalError::TypeMismatch {
17812 detail: alloc::format!(
17813 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17814 ),
17815 })
17816 })?;
17817 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17818 for elem in arr {
17819 match elem {
17820 None => out.push(None),
17821 Some(t) => {
17822 let n: i64 = t.parse().map_err(|_| {
17823 EngineError::Eval(EvalError::TypeMismatch {
17824 detail: alloc::format!(
17825 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17826 ),
17827 })
17828 })?;
17829 out.push(Some(n));
17830 }
17831 }
17832 }
17833 Some(Value::BigIntArray(out))
17834 }
17835 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17839 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17848 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17849 EngineError::Eval(EvalError::TypeMismatch {
17850 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17851 })
17852 })?;
17853 if parsed.len() != dim as usize {
17854 return Err(EngineError::Eval(EvalError::TypeMismatch {
17855 detail: alloc::format!(
17856 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17857 parsed.len()
17858 ),
17859 }));
17860 }
17861 Some(match encoding {
17862 VecEncoding::F32 => Value::Vector(parsed),
17863 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17864 VecEncoding::F16 => {
17865 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17866 }
17867 })
17868 }
17869 (Value::Text(s), DataType::TsVector) => {
17879 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17880 EngineError::Eval(EvalError::TypeMismatch {
17881 detail: alloc::format!(
17882 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17883 ),
17884 })
17885 })?;
17886 Some(Value::TsVector(lexs))
17887 }
17888 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17889 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17890 EngineError::Eval(EvalError::TypeMismatch {
17891 detail: alloc::format!(
17892 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17893 ),
17894 })
17895 })?;
17896 Some(Value::Timestamp(t))
17897 }
17898 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17901 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17902 }
17903 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17907 (Value::Timestamp(t), DataType::Date) => {
17908 let days = t.div_euclid(86_400_000_000);
17909 i32::try_from(days).ok().map(Value::Date)
17910 }
17911 (
17912 Value::Numeric {
17913 scaled,
17914 scale: src_scale,
17915 },
17916 DataType::Numeric { precision, scale },
17917 ) => Some(numeric_rescale(
17918 scaled, src_scale, precision, scale, col_name,
17919 )?),
17920 #[allow(clippy::cast_precision_loss)]
17921 (Value::Numeric { scaled, scale }, DataType::Float) => {
17922 let mut div = 1.0_f64;
17923 for _ in 0..scale {
17924 div *= 10.0;
17925 }
17926 Some(Value::Float((scaled as f64) / div))
17927 }
17928 (Value::Numeric { scaled, scale }, DataType::Int) => {
17929 let truncated = numeric_truncate_to_integer(scaled, scale);
17930 i32::try_from(truncated).ok().map(Value::Int)
17931 }
17932 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17933 let truncated = numeric_truncate_to_integer(scaled, scale);
17934 i64::try_from(truncated).ok().map(Value::BigInt)
17935 }
17936 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17937 let truncated = numeric_truncate_to_integer(scaled, scale);
17938 i16::try_from(truncated).ok().map(Value::SmallInt)
17939 }
17940 (Value::Text(s), DataType::Varchar(max)) => {
17942 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17943 Some(Value::Text(s))
17944 } else {
17945 return Err(EngineError::Unsupported(alloc::format!(
17946 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17947 {} chars",
17948 s.chars().count()
17949 )));
17950 }
17951 }
17952 (
17960 Value::Vector(v),
17961 DataType::Vector {
17962 dim,
17963 encoding: VecEncoding::Sq8,
17964 },
17965 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17966 (
17971 Value::Vector(v),
17972 DataType::Vector {
17973 dim,
17974 encoding: VecEncoding::F16,
17975 },
17976 ) if v.len() == dim as usize => Some(Value::HalfVector(
17977 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17978 )),
17979 (Value::Text(s), DataType::Char(size)) => {
17983 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17984 if len > size {
17985 return Err(EngineError::Unsupported(alloc::format!(
17986 "value for CHAR({size}) column `{col_name}` exceeds length: \
17987 {len} chars"
17988 )));
17989 }
17990 let need = (size - len) as usize;
17991 let mut padded = s;
17992 padded.reserve(need);
17993 for _ in 0..need {
17994 padded.push(' ');
17995 }
17996 Some(Value::Text(padded))
17997 }
17998 _ => None,
17999 };
18000 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
18001 column: col_name.into(),
18002 expected,
18003 actual,
18004 position,
18005 }))
18006}
18007
18008fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
18014 use core::fmt::Write;
18015 let mut out = alloc::string::String::from("(");
18016 for (i, a) in args.iter().enumerate() {
18017 if i > 0 {
18018 out.push_str(", ");
18019 }
18020 match a.mode {
18021 spg_sql::ast::FunctionArgMode::In => {}
18022 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
18023 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
18024 }
18025 if let Some(n) = &a.name {
18026 out.push_str(n);
18027 out.push(' ');
18028 }
18029 match &a.ty {
18030 spg_sql::ast::FunctionArgType::Typed(t) => {
18031 let _ = write!(out, "{t}");
18032 }
18033 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
18034 }
18035 }
18036 out.push(')');
18037 out
18038}
18039
18040fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
18049 match expr {
18050 spg_sql::ast::Expr::FunctionCall { name, args } => {
18051 name.eq_ignore_ascii_case("unnest") && args.len() == 1
18052 }
18053 _ => false,
18054 }
18055}
18056
18057fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
18061 match expr {
18062 spg_sql::ast::Expr::FunctionCall { name, args }
18063 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
18064 {
18065 Some(&args[0])
18066 }
18067 _ => None,
18068 }
18069}
18070
18071fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
18076 match v {
18077 Value::Null => Ok(Vec::new()),
18078 Value::TextArray(items) => Ok(items
18079 .iter()
18080 .map(|opt| {
18081 opt.as_ref()
18082 .map(|s| Value::Text(s.clone()))
18083 .unwrap_or(Value::Null)
18084 })
18085 .collect()),
18086 Value::IntArray(items) => Ok(items
18087 .iter()
18088 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
18089 .collect()),
18090 Value::BigIntArray(items) => Ok(items
18091 .iter()
18092 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
18093 .collect()),
18094 other => Err(EngineError::Eval(EvalError::TypeMismatch {
18095 detail: alloc::format!(
18096 "unnest() expects an array argument, got {:?}",
18097 other.data_type()
18098 ),
18099 })),
18100 }
18101}
18102
18103#[cfg(test)]
18104mod tests {
18105 use super::*;
18106 use alloc::vec;
18107
18108 fn unwrap_command_ok(r: &QueryResult) -> usize {
18109 match r {
18110 QueryResult::CommandOk { affected, .. } => *affected,
18111 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
18112 }
18113 }
18114
18115 #[test]
18116 fn update_seek_positions_engages_on_indexed_eq() {
18117 let mut e = Engine::new();
18118 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
18119 .unwrap();
18120 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
18121 for i in 0..100 {
18122 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
18123 .unwrap();
18124 }
18125 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
18126 .expect("parse");
18127 let Statement::Update(u) = stmt else {
18128 panic!("expected Update, got {stmt:?}");
18129 };
18130 let w = u.where_.as_ref().expect("where");
18131 let table = e.catalog().get("b").unwrap();
18132 let schema_cols = table.schema().columns.clone();
18133 let Expr::Binary { lhs, op, rhs } = w else {
18135 panic!("WHERE not Binary: {w:?}");
18136 };
18137 assert_eq!(*op, BinOp::Eq, "op not Eq");
18138 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
18139 assert!(
18140 pair.is_some(),
18141 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
18142 );
18143 let (col_pos, value) = pair.unwrap();
18144 assert!(
18145 table.index_on(col_pos).is_some(),
18146 "no index on col {col_pos}"
18147 );
18148 assert!(
18149 IndexKey::from_value(&value).is_some(),
18150 "IndexKey::from_value None for {value:?}"
18151 );
18152 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
18153 assert_eq!(positions, Some(vec![42]), "seek did not engage");
18154 }
18155
18156 #[test]
18157 fn create_table_registers_schema() {
18158 let mut e = Engine::new();
18159 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
18160 .unwrap();
18161 assert_eq!(e.catalog().table_count(), 1);
18162 let t = e.catalog().get("foo").unwrap();
18163 assert_eq!(t.schema().columns.len(), 2);
18164 assert_eq!(t.schema().columns[0].ty, DataType::Int);
18165 assert!(!t.schema().columns[0].nullable);
18166 assert_eq!(t.schema().columns[1].ty, DataType::Text);
18167 }
18168
18169 #[test]
18170 fn create_table_vector_default_is_f32_encoded() {
18171 let mut e = Engine::new();
18172 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
18173 let t = e.catalog().get("t").unwrap();
18174 assert_eq!(
18175 t.schema().columns[0].ty,
18176 DataType::Vector {
18177 dim: 8,
18178 encoding: VecEncoding::F32,
18179 },
18180 );
18181 }
18182
18183 #[test]
18184 fn create_table_vector_using_sq8_succeeds() {
18185 let mut e = Engine::new();
18189 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
18190 let t = e.catalog().get("t").unwrap();
18191 assert_eq!(
18192 t.schema().columns[0].ty,
18193 DataType::Vector {
18194 dim: 8,
18195 encoding: VecEncoding::Sq8,
18196 },
18197 );
18198 }
18199
18200 #[test]
18201 fn insert_into_sq8_column_quantises_f32_payload() {
18202 let mut e = Engine::new();
18209 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18210 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18211 .unwrap();
18212 let t = e.catalog().get("t").unwrap();
18213 assert_eq!(t.rows().len(), 1);
18214 match &t.rows()[0].values[0] {
18215 Value::Sq8Vector(q) => {
18216 assert_eq!(q.bytes.len(), 4);
18217 assert!((q.min - 0.0).abs() < 1e-6);
18219 assert!((q.max - 1.0).abs() < 1e-6);
18220 }
18221 other => panic!("expected Sq8Vector cell, got {other:?}"),
18222 }
18223 }
18224
18225 #[test]
18226 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
18227 let mut e = Engine::new();
18234 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18235 .unwrap();
18236 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18237 .unwrap();
18238 let t = e.catalog().get("t").unwrap();
18239 assert_eq!(t.rows().len(), 1);
18240 match &t.rows()[0].values[0] {
18241 Value::HalfVector(h) => {
18242 assert_eq!(h.dim(), 4);
18243 let back = h.to_f32_vec();
18244 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
18245 for (g, e) in back.iter().zip(expected.iter()) {
18246 assert!(
18247 (g - e).abs() < 1e-6,
18248 "{g} vs {e} should be exact on f16 grid"
18249 );
18250 }
18251 }
18252 other => panic!("expected HalfVector cell, got {other:?}"),
18253 }
18254 }
18255
18256 #[test]
18257 fn alter_index_rebuild_in_place_succeeds() {
18258 let mut e = Engine::new();
18263 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18264 .unwrap();
18265 for i in 0..8_i32 {
18266 #[allow(clippy::cast_precision_loss)]
18267 let base = (i as f32) * 0.1;
18268 e.execute(&alloc::format!(
18269 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
18270 b1 = base + 0.01,
18271 b2 = base + 0.02,
18272 ))
18273 .unwrap();
18274 }
18275 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18276 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18277 assert_eq!(
18279 e.catalog().get("t").unwrap().schema().columns[1].ty,
18280 DataType::Vector {
18281 dim: 3,
18282 encoding: VecEncoding::F32,
18283 },
18284 );
18285 }
18286
18287 #[test]
18288 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18289 let mut e = Engine::new();
18294 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18295 .unwrap();
18296 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18297 .unwrap();
18298 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18299 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18300 .unwrap();
18301 let t = e.catalog().get("t").unwrap();
18302 assert_eq!(
18303 t.schema().columns[1].ty,
18304 DataType::Vector {
18305 dim: 4,
18306 encoding: VecEncoding::Sq8,
18307 },
18308 );
18309 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18310 }
18311
18312 #[test]
18313 fn alter_index_rebuild_unknown_index_errors() {
18314 let mut e = Engine::new();
18315 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18316 assert!(
18317 matches!(
18318 &err,
18319 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18320 ),
18321 "got: {err}"
18322 );
18323 }
18324
18325 #[test]
18326 fn alter_index_rebuild_on_btree_index_errors() {
18327 let mut e = Engine::new();
18330 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18331 e.execute("INSERT INTO t VALUES (1)").unwrap();
18332 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18333 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18334 assert!(
18335 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18336 "got: {err}"
18337 );
18338 }
18339
18340 #[test]
18341 fn prepared_insert_substitutes_placeholders() {
18342 let mut e = Engine::new();
18348 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18349 .unwrap();
18350 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18351 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18352 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18353 .unwrap();
18354 }
18355 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18357 let QueryResult::Rows { rows, .. } = rows_result else {
18358 panic!("expected Rows")
18359 };
18360 assert_eq!(rows.len(), 3);
18361 }
18362
18363 #[test]
18364 fn prepared_select_with_placeholder_filters_rows() {
18365 let mut e = Engine::new();
18366 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18367 .unwrap();
18368 for i in 0..10_i32 {
18369 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18370 .unwrap();
18371 }
18372 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18373 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18374 else {
18375 panic!("expected Rows")
18376 };
18377 assert_eq!(rows.len(), 1);
18379 assert_eq!(rows[0].values[0], Value::Int(5));
18380 }
18381
18382 #[test]
18383 fn prepared_too_few_params_errors() {
18384 let mut e = Engine::new();
18385 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18386 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18387 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18388 assert!(
18389 matches!(
18390 &err,
18391 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18392 ),
18393 "got: {err}"
18394 );
18395 }
18396
18397 #[test]
18398 fn bytea_cast_round_trips_text_input() {
18399 let e = Engine::new();
18402 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18403 let QueryResult::Rows { rows, .. } = r else {
18404 panic!("expected Rows")
18405 };
18406 assert_eq!(rows.len(), 1);
18407 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18408 }
18409
18410 #[test]
18411 fn bytea_cast_pg_escape_hex_form() {
18412 let e = Engine::new();
18416 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18417 let QueryResult::Rows { rows, .. } = r else {
18418 panic!("expected Rows")
18419 };
18420 assert_eq!(
18421 rows[0].values[0],
18422 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18423 );
18424 }
18425
18426 #[test]
18427 fn bytea_cast_chains_through_octet_length() {
18428 let e = Engine::new();
18432 let r = e
18433 .execute_readonly("SELECT octet_length('hello'::bytea)")
18434 .unwrap();
18435 let QueryResult::Rows { rows, .. } = r else {
18436 panic!("expected Rows")
18437 };
18438 match &rows[0].values[0] {
18439 Value::Int(n) => assert_eq!(*n, 5),
18440 Value::BigInt(n) => assert_eq!(*n, 5),
18441 other => panic!("expected integer length, got {other:?}"),
18442 }
18443 }
18444
18445 #[test]
18446 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18447 let mut e = Engine::new();
18453 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18454 .unwrap();
18455 for i in 0..10_i32 {
18456 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18457 .unwrap();
18458 }
18459 let snapshot = e.clone_snapshot();
18460 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18461 let QueryResult::Rows { rows, .. } =
18462 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18463 .unwrap()
18464 else {
18465 panic!("expected Rows")
18466 };
18467 assert_eq!(rows.len(), 1);
18468 assert_eq!(rows[0].values[0], Value::Int(5));
18469 }
18470
18471 #[test]
18472 fn readonly_prepared_on_snapshot_rejects_writes() {
18473 let mut e = Engine::new();
18477 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18478 let snapshot = e.clone_snapshot();
18479 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18480 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18481 .unwrap_err();
18482 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18483 }
18484
18485 #[test]
18486 fn readonly_prepared_on_snapshot_frozen_view() {
18487 let mut e = Engine::new();
18493 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18494 e.execute("INSERT INTO t VALUES (1)").unwrap();
18495 let snapshot = e.clone_snapshot();
18496 e.execute("INSERT INTO t VALUES (2)").unwrap();
18497 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18498 let QueryResult::Rows { rows, .. } =
18499 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18500 .unwrap()
18501 else {
18502 panic!("expected Rows")
18503 };
18504 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18505 }
18506
18507 #[test]
18508 fn describe_prepared_on_snapshot_resolves_columns() {
18509 let mut e = Engine::new();
18514 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18515 .unwrap();
18516 let snapshot = e.clone_snapshot();
18517 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18518 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18519 assert_eq!(cols.len(), 2);
18520 assert_eq!(cols[0].name, "id");
18521 assert_eq!(cols[0].ty, DataType::Int);
18522 assert_eq!(cols[1].name, "name");
18523 assert_eq!(cols[1].ty, DataType::Text);
18524 }
18525
18526 #[test]
18527 fn insert_into_half_column_dim_mismatch_errors() {
18528 let mut e = Engine::new();
18529 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18530 .unwrap();
18531 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18532 assert!(matches!(
18533 &err,
18534 EngineError::Storage(StorageError::TypeMismatch { .. })
18535 ));
18536 }
18537
18538 #[test]
18539 fn insert_into_sq8_column_dim_mismatch_errors() {
18540 let mut e = Engine::new();
18545 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18546 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18547 assert!(
18548 matches!(
18549 &err,
18550 EngineError::Storage(StorageError::TypeMismatch { .. })
18551 ),
18552 "got: {err}",
18553 );
18554 }
18555
18556 #[test]
18557 fn create_table_duplicate_errors() {
18558 let mut e = Engine::new();
18559 e.execute("CREATE TABLE foo (a INT)").unwrap();
18560 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18561 assert!(matches!(
18562 err,
18563 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18564 ));
18565 }
18566
18567 #[test]
18568 fn insert_into_unknown_table_errors() {
18569 let mut e = Engine::new();
18570 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18571 assert!(matches!(
18572 err,
18573 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18574 ));
18575 }
18576
18577 #[test]
18578 fn insert_happy_path_reports_one_affected() {
18579 let mut e = Engine::new();
18580 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18581 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18582 assert_eq!(unwrap_command_ok(&r), 1);
18583 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18584 }
18585
18586 #[test]
18587 fn insert_arity_mismatch_propagates() {
18588 let mut e = Engine::new();
18589 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18590 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18591 assert!(matches!(
18592 err,
18593 EngineError::Storage(StorageError::ArityMismatch { .. })
18594 ));
18595 }
18596
18597 #[test]
18598 fn insert_negative_integer_via_unary_minus() {
18599 let mut e = Engine::new();
18600 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18601 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18602 let rows = e.catalog().get("foo").unwrap().rows();
18603 assert_eq!(rows[0].values[0], Value::Int(-7));
18604 }
18605
18606 #[test]
18607 fn insert_expression_evaluated_against_empty_context() {
18608 let mut e = Engine::new();
18613 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18614 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18615 let rows = e.catalog().get("foo").unwrap().rows();
18616 assert_eq!(rows[0].values[0], Value::Int(3));
18617 }
18618
18619 #[test]
18620 fn select_star_returns_all_rows_in_insertion_order() {
18621 let mut e = Engine::new();
18622 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18623 .unwrap();
18624 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18625 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18626 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18627
18628 let r = e.execute("SELECT * FROM foo").unwrap();
18629 let QueryResult::Rows { columns, rows } = r else {
18630 panic!("expected Rows")
18631 };
18632 assert_eq!(columns.len(), 2);
18633 assert_eq!(columns[0].name, "a");
18634 assert_eq!(rows.len(), 3);
18635 assert_eq!(
18636 rows[1].values,
18637 vec![Value::Int(2), Value::Text("two".into())]
18638 );
18639 }
18640
18641 #[test]
18642 fn select_star_on_empty_table_returns_zero_rows() {
18643 let mut e = Engine::new();
18644 e.execute("CREATE TABLE foo (a INT)").unwrap();
18645 let r = e.execute("SELECT * FROM foo").unwrap();
18646 match r {
18647 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18648 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18649 }
18650 }
18651
18652 fn make_three_row_users(e: &mut Engine) {
18655 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18656 .unwrap();
18657 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18658 .unwrap();
18659 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18660 .unwrap();
18661 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18662 .unwrap();
18663 }
18664
18665 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18666 match r {
18667 QueryResult::Rows { columns, rows } => (columns, rows),
18668 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18669 }
18670 }
18671
18672 #[test]
18673 fn where_filter_passes_only_true_rows() {
18674 let mut e = Engine::new();
18675 make_three_row_users(&mut e);
18676 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18677 let (_, rows) = unwrap_rows(r);
18678 assert_eq!(rows.len(), 2);
18679 assert_eq!(rows[0].values[0], Value::Int(2));
18680 assert_eq!(rows[1].values[0], Value::Int(3));
18681 }
18682
18683 #[test]
18684 fn where_with_null_result_filters_out_row() {
18685 let mut e = Engine::new();
18686 make_three_row_users(&mut e);
18687 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18689 let (_, rows) = unwrap_rows(r);
18690 assert_eq!(rows.len(), 1);
18691 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18692 }
18693
18694 #[test]
18695 fn projection_named_columns() {
18696 let mut e = Engine::new();
18697 make_three_row_users(&mut e);
18698 let r = e.execute("SELECT name, score FROM users").unwrap();
18699 let (cols, rows) = unwrap_rows(r);
18700 assert_eq!(cols.len(), 2);
18701 assert_eq!(cols[0].name, "name");
18702 assert_eq!(cols[1].name, "score");
18703 assert_eq!(rows.len(), 3);
18704 assert_eq!(
18705 rows[0].values,
18706 vec![Value::Text("alice".into()), Value::Int(90)]
18707 );
18708 }
18709
18710 #[test]
18711 fn projection_with_column_alias() {
18712 let mut e = Engine::new();
18713 make_three_row_users(&mut e);
18714 let r = e
18715 .execute("SELECT name AS who FROM users WHERE id = 1")
18716 .unwrap();
18717 let (cols, rows) = unwrap_rows(r);
18718 assert_eq!(cols[0].name, "who");
18719 assert_eq!(rows.len(), 1);
18720 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18721 }
18722
18723 #[test]
18724 fn qualified_column_with_table_alias_resolves() {
18725 let mut e = Engine::new();
18726 make_three_row_users(&mut e);
18727 let r = e
18728 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18729 .unwrap();
18730 let (cols, rows) = unwrap_rows(r);
18731 assert_eq!(cols.len(), 2);
18732 assert_eq!(rows.len(), 2);
18733 }
18734
18735 #[test]
18736 fn qualified_column_with_wrong_alias_errors() {
18737 let mut e = Engine::new();
18738 make_three_row_users(&mut e);
18739 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18740 assert!(matches!(
18741 err,
18742 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18743 ));
18744 }
18745
18746 #[test]
18747 fn select_unknown_column_errors_in_projection() {
18748 let mut e = Engine::new();
18749 make_three_row_users(&mut e);
18750 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18751 assert!(matches!(
18752 err,
18753 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18754 ));
18755 }
18756
18757 #[test]
18758 fn where_unknown_column_errors() {
18759 let mut e = Engine::new();
18760 make_three_row_users(&mut e);
18761 let err = e
18762 .execute("SELECT * FROM users WHERE ghost = 1")
18763 .unwrap_err();
18764 assert!(matches!(
18765 err,
18766 EngineError::Eval(EvalError::ColumnNotFound { .. })
18767 ));
18768 }
18769
18770 #[test]
18771 fn expression_projection_evaluates_and_renders() {
18772 let mut e = Engine::new();
18775 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18776 e.execute("INSERT INTO t VALUES (3)").unwrap();
18777 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18778 assert_eq!(rows.len(), 1);
18779 assert_eq!(rows[0].values[0], Value::Int(3));
18782 }
18783
18784 #[test]
18785 fn select_unknown_table_errors() {
18786 let mut e = Engine::new();
18787 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18788 assert!(matches!(
18789 err,
18790 EngineError::Storage(StorageError::TableNotFound { .. })
18791 ));
18792 }
18793
18794 #[test]
18795 fn invalid_sql_returns_parse_error() {
18796 let mut e = Engine::new();
18799 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18800 assert!(matches!(err, EngineError::Parse(_)));
18801 }
18802
18803 #[test]
18806 fn create_index_registers_on_table() {
18807 let mut e = Engine::new();
18808 make_three_row_users(&mut e);
18809 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18810 let t = e.catalog().get("users").unwrap();
18811 assert_eq!(t.indices().len(), 1);
18812 assert_eq!(t.indices()[0].name, "by_name");
18813 }
18814
18815 #[test]
18816 fn create_index_on_unknown_table_errors() {
18817 let mut e = Engine::new();
18818 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18819 assert!(matches!(
18820 err,
18821 EngineError::Storage(StorageError::TableNotFound { .. })
18822 ));
18823 }
18824
18825 #[test]
18826 fn create_index_on_unknown_column_errors() {
18827 let mut e = Engine::new();
18828 make_three_row_users(&mut e);
18829 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18830 assert!(matches!(
18831 err,
18832 EngineError::Storage(StorageError::ColumnNotFound { .. })
18833 ));
18834 }
18835
18836 #[test]
18837 fn select_eq_uses_index_returns_same_rows_as_scan() {
18838 let mut without = Engine::new();
18842 make_three_row_users(&mut without);
18843 let mut with = Engine::new();
18844 make_three_row_users(&mut with);
18845 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18846
18847 let q = "SELECT * FROM users WHERE id = 2";
18848 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18849 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18850 assert_eq!(no_idx_rows, idx_rows);
18851 assert_eq!(idx_rows.len(), 1);
18852 }
18853
18854 #[test]
18855 fn select_eq_with_no_matching_index_value_returns_empty() {
18856 let mut e = Engine::new();
18857 make_three_row_users(&mut e);
18858 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18859 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18860 assert_eq!(rows.len(), 0);
18861 }
18862
18863 #[test]
18866 fn begin_sets_in_transaction_flag() {
18867 let mut e = Engine::new();
18868 assert!(!e.in_transaction());
18869 e.execute("BEGIN").unwrap();
18870 assert!(e.in_transaction());
18871 }
18872
18873 #[test]
18874 fn double_begin_errors() {
18875 let mut e = Engine::new();
18876 e.execute("BEGIN").unwrap();
18877 let err = e.execute("BEGIN").unwrap_err();
18878 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18879 }
18880
18881 #[test]
18882 fn commit_without_begin_errors() {
18883 let mut e = Engine::new();
18884 let err = e.execute("COMMIT").unwrap_err();
18885 assert_eq!(err, EngineError::NoActiveTransaction);
18886 }
18887
18888 #[test]
18889 fn rollback_without_begin_errors() {
18890 let mut e = Engine::new();
18891 let err = e.execute("ROLLBACK").unwrap_err();
18892 assert_eq!(err, EngineError::NoActiveTransaction);
18893 }
18894
18895 #[test]
18896 fn commit_applies_shadow_to_committed_catalog() {
18897 let mut e = Engine::new();
18898 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18899 e.execute("BEGIN").unwrap();
18900 e.execute("INSERT INTO t VALUES (1)").unwrap();
18901 e.execute("INSERT INTO t VALUES (2)").unwrap();
18902 e.execute("COMMIT").unwrap();
18903 assert!(!e.in_transaction());
18904 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18905 }
18906
18907 #[test]
18908 fn rollback_discards_shadow() {
18909 let mut e = Engine::new();
18910 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18911 e.execute("BEGIN").unwrap();
18912 e.execute("INSERT INTO t VALUES (1)").unwrap();
18913 e.execute("INSERT INTO t VALUES (2)").unwrap();
18914 e.execute("ROLLBACK").unwrap();
18915 assert!(!e.in_transaction());
18916 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18917 }
18918
18919 #[test]
18920 fn select_during_tx_sees_uncommitted_writes_own_session() {
18921 let mut e = Engine::new();
18924 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18925 e.execute("BEGIN").unwrap();
18926 e.execute("INSERT INTO t VALUES (42)").unwrap();
18927 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18928 assert_eq!(rows.len(), 1);
18929 assert_eq!(rows[0].values[0], Value::Int(42));
18930 }
18931
18932 #[test]
18933 fn snapshot_with_no_users_is_bare_catalog_format() {
18934 let mut e = Engine::new();
18935 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18936 let bytes = e.snapshot();
18937 assert_eq!(
18938 &bytes[..8],
18939 b"SPGDB001",
18940 "must be the bare v3.x catalog magic"
18941 );
18942 let e2 = Engine::restore_envelope(&bytes).unwrap();
18943 assert!(e2.users().is_empty());
18944 assert_eq!(e2.catalog().table_count(), 1);
18945 }
18946
18947 #[test]
18948 fn snapshot_with_users_round_trips_both_via_envelope() {
18949 let mut e = Engine::new();
18950 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18951 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18952 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18953 .unwrap();
18954 let bytes = e.snapshot();
18955 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18956 let e2 = Engine::restore_envelope(&bytes).unwrap();
18957 assert_eq!(e2.users().len(), 2);
18958 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18959 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18960 assert_eq!(e2.verify_user("alice", "wrong"), None);
18961 assert_eq!(e2.catalog().table_count(), 1);
18962 }
18963
18964 #[test]
18965 fn ddl_inside_tx_also_rolled_back() {
18966 let mut e = Engine::new();
18967 e.execute("BEGIN").unwrap();
18968 e.execute("CREATE TABLE t (v INT)").unwrap();
18969 e.execute("SELECT * FROM t").unwrap();
18971 e.execute("ROLLBACK").unwrap();
18972 let err = e.execute("SELECT * FROM t").unwrap_err();
18974 assert!(matches!(
18975 err,
18976 EngineError::Storage(StorageError::TableNotFound { .. })
18977 ));
18978 }
18979
18980 #[test]
18983 fn create_publication_lands_in_catalog() {
18984 let mut e = Engine::new();
18985 assert!(e.publications().is_empty());
18986 e.execute("CREATE PUBLICATION pub_a").unwrap();
18987 assert_eq!(e.publications().len(), 1);
18988 assert!(e.publications().contains("pub_a"));
18989 }
18990
18991 #[test]
18992 fn create_publication_duplicate_errors() {
18993 let mut e = Engine::new();
18994 e.execute("CREATE PUBLICATION pub_a").unwrap();
18995 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18996 assert!(
18997 alloc::format!("{err:?}").contains("DuplicateName"),
18998 "got {err:?}"
18999 );
19000 }
19001
19002 #[test]
19003 fn drop_publication_silent_when_absent() {
19004 let mut e = Engine::new();
19005 let r = e.execute("DROP PUBLICATION nope").unwrap();
19008 match r {
19009 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19010 other => panic!("expected CommandOk, got {other:?}"),
19011 }
19012 }
19013
19014 #[test]
19015 fn drop_publication_present_reports_one_affected() {
19016 let mut e = Engine::new();
19017 e.execute("CREATE PUBLICATION pub_a").unwrap();
19018 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
19019 match r {
19020 QueryResult::CommandOk {
19021 affected,
19022 modified_catalog,
19023 } => {
19024 assert_eq!(affected, 1);
19025 assert!(modified_catalog);
19026 }
19027 other => panic!("expected CommandOk, got {other:?}"),
19028 }
19029 assert!(e.publications().is_empty());
19030 }
19031
19032 #[test]
19033 fn publications_persist_across_snapshot_restore() {
19034 let mut e = Engine::new();
19039 e.execute("CREATE PUBLICATION pub_a").unwrap();
19040 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
19041 .unwrap();
19042 let snap = e.snapshot();
19043 let e2 = Engine::restore_envelope(&snap).unwrap();
19044 assert_eq!(e2.publications().len(), 2);
19045 assert!(e2.publications().contains("pub_a"));
19046 assert!(e2.publications().contains("pub_b"));
19047 }
19048
19049 #[test]
19050 fn create_publication_allowed_inside_transaction() {
19051 let mut e = Engine::new();
19055 e.execute("BEGIN").unwrap();
19056 e.execute("CREATE PUBLICATION pub_a").unwrap();
19057 e.execute("COMMIT").unwrap();
19058 assert!(e.publications().contains("pub_a"));
19059 }
19060
19061 #[test]
19064 fn create_publication_for_table_list_lands_with_scope() {
19065 let mut e = Engine::new();
19066 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19067 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
19068 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
19069 .unwrap();
19070 let scope = e.publications().get("pub_a").cloned();
19071 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
19072 panic!("expected ForTables scope, got {scope:?}")
19073 };
19074 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19075 }
19076
19077 #[test]
19078 fn create_publication_all_tables_except_lands_with_scope() {
19079 let mut e = Engine::new();
19080 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
19081 .unwrap();
19082 let scope = e.publications().get("pub_a").cloned();
19083 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
19084 panic!("expected AllTablesExcept scope, got {scope:?}")
19085 };
19086 assert_eq!(ts, alloc::vec!["t3".to_string()]);
19087 }
19088
19089 #[test]
19090 fn show_publications_empty_returns_zero_rows() {
19091 let e = Engine::new();
19092 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19093 let QueryResult::Rows { rows, columns } = r else {
19094 panic!()
19095 };
19096 assert!(rows.is_empty());
19097 assert_eq!(columns.len(), 3);
19098 assert_eq!(columns[0].name, "name");
19099 assert_eq!(columns[1].name, "scope");
19100 assert_eq!(columns[2].name, "table_count");
19101 }
19102
19103 #[test]
19104 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
19105 let mut e = Engine::new();
19106 e.execute("CREATE PUBLICATION z_pub").unwrap();
19107 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
19108 .unwrap();
19109 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
19110 .unwrap();
19111 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19112 let QueryResult::Rows { rows, .. } = r else {
19113 panic!()
19114 };
19115 assert_eq!(rows.len(), 3);
19116 let names: Vec<&str> = rows
19118 .iter()
19119 .map(|r| {
19120 if let Value::Text(s) = &r.values[0] {
19121 s.as_str()
19122 } else {
19123 panic!()
19124 }
19125 })
19126 .collect();
19127 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
19128 match &rows[0].values[1] {
19130 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
19131 other => panic!("expected Text, got {other:?}"),
19132 }
19133 assert_eq!(rows[0].values[2], Value::Int(2));
19134 match &rows[1].values[1] {
19136 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
19137 other => panic!("expected Text, got {other:?}"),
19138 }
19139 assert_eq!(rows[1].values[2], Value::Int(1));
19140 match &rows[2].values[1] {
19142 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
19143 other => panic!("expected Text, got {other:?}"),
19144 }
19145 assert_eq!(rows[2].values[2], Value::Null);
19146 }
19147
19148 #[test]
19149 fn for_list_scopes_persist_across_snapshot() {
19150 let mut e = Engine::new();
19153 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
19154 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
19155 .unwrap();
19156 let snap = e.snapshot();
19157 let e2 = Engine::restore_envelope(&snap).unwrap();
19158 assert_eq!(e2.publications().len(), 2);
19159 let p1 = e2.publications().get("p1").cloned();
19160 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
19161 panic!("p1 scope lost: {p1:?}")
19162 };
19163 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19164 let p2 = e2.publications().get("p2").cloned();
19165 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
19166 panic!("p2 scope lost: {p2:?}")
19167 };
19168 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
19169 }
19170
19171 #[test]
19174 fn create_subscription_lands_in_catalog_with_defaults() {
19175 let mut e = Engine::new();
19176 e.execute(
19177 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
19178 )
19179 .unwrap();
19180 let s = e.subscriptions().get("sub_a").cloned().expect("present");
19181 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
19182 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
19183 assert!(s.enabled);
19184 assert_eq!(s.last_received_pos, 0);
19185 }
19186
19187 #[test]
19188 fn create_subscription_duplicate_name_errors() {
19189 let mut e = Engine::new();
19190 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
19191 .unwrap();
19192 let err = e
19193 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
19194 .unwrap_err();
19195 assert!(
19196 alloc::format!("{err:?}").contains("DuplicateName"),
19197 "got {err:?}"
19198 );
19199 }
19200
19201 #[test]
19202 fn drop_subscription_silent_when_absent() {
19203 let mut e = Engine::new();
19204 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
19205 match r {
19206 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19207 other => panic!("expected CommandOk, got {other:?}"),
19208 }
19209 }
19210
19211 #[test]
19212 fn subscription_advance_updates_last_pos_monotone() {
19213 let mut e = Engine::new();
19214 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19215 .unwrap();
19216 assert!(e.subscription_advance("s", 100));
19217 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19218 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19220 assert!(e.subscription_advance("s", 200));
19221 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
19222 assert!(!e.subscription_advance("missing", 1));
19223 }
19224
19225 #[test]
19226 fn show_subscriptions_returns_rows_ordered_by_name() {
19227 let mut e = Engine::new();
19228 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
19229 .unwrap();
19230 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
19231 .unwrap();
19232 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
19233 let QueryResult::Rows { rows, columns } = r else {
19234 panic!()
19235 };
19236 assert_eq!(rows.len(), 2);
19237 assert_eq!(columns.len(), 5);
19238 assert_eq!(columns[0].name, "name");
19239 assert_eq!(columns[4].name, "last_received_pos");
19240 let names: Vec<&str> = rows
19242 .iter()
19243 .map(|r| {
19244 if let Value::Text(s) = &r.values[0] {
19245 s.as_str()
19246 } else {
19247 panic!()
19248 }
19249 })
19250 .collect();
19251 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
19252 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
19254 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
19255 assert_eq!(rows[0].values[3], Value::Bool(true));
19256 assert_eq!(rows[0].values[4], Value::BigInt(0));
19257 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
19259 }
19260
19261 #[test]
19262 fn subscriptions_persist_across_snapshot_envelope_v4() {
19263 let mut e = Engine::new();
19264 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
19265 .unwrap();
19266 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
19267 .unwrap();
19268 e.subscription_advance("s2", 42);
19269 let snap = e.snapshot();
19270 let e2 = Engine::restore_envelope(&snap).unwrap();
19271 assert_eq!(e2.subscriptions().len(), 2);
19272 let s1 = e2.subscriptions().get("s1").unwrap();
19273 assert_eq!(s1.conn_str, "h=A");
19274 assert_eq!(
19275 s1.publications,
19276 alloc::vec!["p1".to_string(), "p2".to_string()]
19277 );
19278 assert_eq!(s1.last_received_pos, 0);
19279 let s2 = e2.subscriptions().get("s2").unwrap();
19280 assert_eq!(s2.last_received_pos, 42);
19281 }
19282
19283 #[test]
19284 fn v3_envelope_loads_with_empty_subscriptions() {
19285 let mut e = Engine::new();
19289 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19290 let catalog = e.catalog.serialize();
19291 let users = crate::users::serialize_users(&e.users);
19292 let pubs = e.publications.serialize();
19293 let mut buf = Vec::new();
19294 buf.extend_from_slice(b"SPGENV01");
19295 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19297 buf.extend_from_slice(&catalog);
19298 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19299 buf.extend_from_slice(&users);
19300 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19301 buf.extend_from_slice(&pubs);
19302 let crc = spg_crypto::crc32::crc32(&buf);
19303 buf.extend_from_slice(&crc.to_le_bytes());
19304
19305 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19306 assert!(e2.subscriptions().is_empty());
19307 assert!(e2.publications().contains("pub_legacy"));
19308 }
19309
19310 #[test]
19311 fn create_subscription_allowed_inside_transaction() {
19312 let mut e = Engine::new();
19313 e.execute("BEGIN").unwrap();
19314 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19315 .unwrap();
19316 e.execute("COMMIT").unwrap();
19317 assert!(e.subscriptions().contains("s"));
19318 }
19319
19320 #[test]
19322 fn analyze_populates_histogram_bounds() {
19323 let mut e = Engine::new();
19324 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19325 .unwrap();
19326 for i in 0..50 {
19327 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19328 .unwrap();
19329 }
19330 e.execute("ANALYZE t").unwrap();
19331 let stats = e.statistics();
19332 let id_stats = stats.get("t", "id").unwrap();
19333 assert!(id_stats.histogram_bounds.len() >= 2);
19334 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19335 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19336 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19337 assert_eq!(id_stats.n_distinct, 50);
19338 }
19339
19340 #[test]
19341 fn reanalyze_overwrites_prior_stats() {
19342 let mut e = Engine::new();
19343 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19344 for i in 0..10 {
19345 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19346 .unwrap();
19347 }
19348 e.execute("ANALYZE t").unwrap();
19349 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19350 assert_eq!(n1, 10);
19351 for i in 10..30 {
19352 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19353 .unwrap();
19354 }
19355 e.execute("ANALYZE t").unwrap();
19356 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19357 assert_eq!(n2, 30);
19358 }
19359
19360 #[test]
19361 fn analyze_unknown_table_errors() {
19362 let mut e = Engine::new();
19363 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19364 assert!(matches!(
19365 err,
19366 EngineError::Storage(StorageError::TableNotFound { .. })
19367 ));
19368 }
19369
19370 #[test]
19371 fn bare_analyze_covers_all_user_tables() {
19372 let mut e = Engine::new();
19373 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19374 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19375 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19376 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19377 let r = e.execute("ANALYZE").unwrap();
19378 match r {
19379 QueryResult::CommandOk {
19380 affected,
19381 modified_catalog,
19382 } => {
19383 assert_eq!(affected, 2);
19384 assert!(modified_catalog);
19385 }
19386 other => panic!("expected CommandOk, got {other:?}"),
19387 }
19388 assert!(e.statistics().get("t1", "id").is_some());
19389 assert!(e.statistics().get("t2", "name").is_some());
19390 }
19391
19392 #[test]
19393 fn select_from_spg_statistic_returns_rows_per_column() {
19394 let mut e = Engine::new();
19395 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19396 .unwrap();
19397 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19398 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19399 e.execute("ANALYZE t").unwrap();
19400 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19401 let QueryResult::Rows { rows, columns } = r else {
19402 panic!()
19403 };
19404 assert_eq!(columns.len(), 6);
19406 assert_eq!(columns[0].name, "table_name");
19407 assert_eq!(columns[4].name, "histogram_bounds");
19408 assert_eq!(columns[5].name, "cold_row_count");
19409 assert_eq!(rows.len(), 2, "one row per column of t");
19410 match (&rows[0].values[0], &rows[0].values[1]) {
19412 (Value::Text(t), Value::Text(c)) => {
19413 assert_eq!(t, "t");
19414 assert_eq!(c, "id");
19416 }
19417 _ => panic!(),
19418 }
19419 }
19420
19421 #[test]
19422 fn analyze_skips_vector_columns() {
19423 let mut e = Engine::new();
19426 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19427 .unwrap();
19428 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19429 e.execute("ANALYZE t").unwrap();
19430 assert!(e.statistics().get("t", "id").is_some());
19431 assert!(e.statistics().get("t", "v").is_none());
19432 }
19433
19434 #[test]
19435 fn statistics_persist_across_envelope_v5_round_trip() {
19436 let mut e = Engine::new();
19437 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19438 for i in 0..20 {
19439 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19440 .unwrap();
19441 }
19442 e.execute("ANALYZE").unwrap();
19443 let snap = e.snapshot();
19444 let e2 = Engine::restore_envelope(&snap).unwrap();
19445 let s = e2.statistics().get("t", "id").unwrap();
19446 assert_eq!(s.n_distinct, 20);
19447 }
19448
19449 #[test]
19452 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19453 let mut e = Engine::new();
19457 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19458 for i in 0..9 {
19459 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19460 .unwrap();
19461 }
19462 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19463 e.execute("INSERT INTO t VALUES (9)").unwrap();
19464 let needs = e.tables_needing_analyze();
19465 assert_eq!(needs, alloc::vec!["t".to_string()]);
19466 }
19467
19468 #[test]
19469 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19470 let mut e = Engine::new();
19476 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19477 for i in 0..1000 {
19478 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19479 .unwrap();
19480 }
19481 e.execute("ANALYZE t").unwrap();
19482 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19483 for i in 1000..1050 {
19484 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19485 .unwrap();
19486 }
19487 assert!(
19488 e.tables_needing_analyze().is_empty(),
19489 "50 inserts < threshold of ~105"
19490 );
19491 for i in 1050..1200 {
19492 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19493 .unwrap();
19494 }
19495 assert_eq!(
19496 e.tables_needing_analyze(),
19497 alloc::vec!["t".to_string()],
19498 "200 inserts > 0.1 × 1200 threshold"
19499 );
19500 }
19501
19502 #[test]
19503 fn auto_analyze_threshold_resets_after_analyze() {
19504 let mut e = Engine::new();
19505 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19506 for i in 0..200 {
19507 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19508 .unwrap();
19509 }
19510 assert!(!e.tables_needing_analyze().is_empty());
19511 e.execute("ANALYZE").unwrap();
19512 assert!(
19513 e.tables_needing_analyze().is_empty(),
19514 "ANALYZE must reset the counter"
19515 );
19516 }
19517
19518 #[test]
19519 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19520 let mut e = Engine::new();
19521 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19522 .unwrap();
19523 for i in 0..50 {
19524 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19525 .unwrap();
19526 }
19527 e.execute("ANALYZE t").unwrap();
19528 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19531 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19532 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19533 }
19534
19535 #[test]
19536 fn v4_envelope_loads_with_empty_statistics() {
19537 let mut e = Engine::new();
19541 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19542 .unwrap();
19543 let catalog = e.catalog.serialize();
19544 let users = crate::users::serialize_users(&e.users);
19545 let pubs = e.publications.serialize();
19546 let subs = e.subscriptions.serialize();
19547 let mut buf = Vec::new();
19548 buf.extend_from_slice(b"SPGENV01");
19549 buf.push(4u8);
19550 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19551 buf.extend_from_slice(&catalog);
19552 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19553 buf.extend_from_slice(&users);
19554 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19555 buf.extend_from_slice(&pubs);
19556 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19557 buf.extend_from_slice(&subs);
19558 let crc = spg_crypto::crc32::crc32(&buf);
19559 buf.extend_from_slice(&crc.to_le_bytes());
19560 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19561 assert!(e2.statistics().is_empty());
19562 }
19563
19564 #[test]
19565 fn v1_v2_envelope_loads_with_empty_publications() {
19566 let mut e = Engine::new();
19573 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19576 .unwrap();
19577
19578 let catalog = e.catalog.serialize();
19580 let users = crate::users::serialize_users(&e.users);
19581 let mut buf = Vec::new();
19582 buf.extend_from_slice(b"SPGENV01");
19583 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19585 buf.extend_from_slice(&catalog);
19586 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19587 buf.extend_from_slice(&users);
19588 let crc = spg_crypto::crc32::crc32(&buf);
19589 buf.extend_from_slice(&crc.to_le_bytes());
19590
19591 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19592 assert!(e2.publications().is_empty());
19593 }
19594}