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 body_result = self.exec_select_cancel(&cte.body, cancel)?;
10126 let QueryResult::Rows { columns, rows } = body_result else {
10127 return Err(EngineError::Unsupported(alloc::format!(
10128 "CTE {:?} body did not return rows",
10129 cte.name
10130 )));
10131 };
10132 (columns, rows)
10133 };
10134 let inferred = infer_column_types(&columns, &rows);
10139 let mut columns = inferred;
10140 if !cte.column_overrides.is_empty() {
10142 if cte.column_overrides.len() != columns.len() {
10143 return Err(EngineError::Unsupported(alloc::format!(
10144 "CTE {:?} column list has {} names but body returns {} columns",
10145 cte.name,
10146 cte.column_overrides.len(),
10147 columns.len()
10148 )));
10149 }
10150 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10151 col.name.clone_from(name);
10152 }
10153 }
10154 let schema = TableSchema::new(cte.name.clone(), columns);
10155 catalog.create_table(schema).map_err(EngineError::Storage)?;
10156 let table = catalog
10157 .get_mut(&cte.name)
10158 .expect("just-created CTE table must exist");
10159 for row in rows {
10160 table.insert(row).map_err(EngineError::Storage)?;
10161 }
10162 }
10163 let mut body = stmt.clone();
10166 body.ctes = Vec::new();
10167 let mut temp = Engine::restore(catalog);
10168 if let Some(c) = self.clock {
10169 temp = temp.with_clock(c);
10170 }
10171 if let Some(f) = self.salt_fn {
10172 temp = temp.with_salt_fn(f);
10173 }
10174 temp.exec_select_cancel(&body, cancel)
10175 }
10176
10177 #[allow(clippy::too_many_lines)]
10187 fn materialise_recursive_cte(
10188 &self,
10189 cte: &spg_sql::ast::Cte,
10190 base_catalog: &Catalog,
10191 cancel: CancelToken<'_>,
10192 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10193 const MAX_TOTAL_ROWS: usize = 1_000_000;
10194 const MAX_ITERATIONS: usize = 100_000;
10195 cancel.check()?;
10196 if cte.body.unions.is_empty() {
10197 return Err(EngineError::Unsupported(alloc::format!(
10198 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10199 cte.name
10200 )));
10201 }
10202 let mut anchor = cte.body.clone();
10204 let union_terms = core::mem::take(&mut anchor.unions);
10205 anchor.ctes = Vec::new();
10206 if select_refers_to(&anchor, &cte.name) {
10208 return Err(EngineError::Unsupported(alloc::format!(
10209 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10210 cte.name
10211 )));
10212 }
10213 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10214 let QueryResult::Rows {
10215 columns: anchor_cols,
10216 rows: anchor_rows,
10217 } = anchor_result
10218 else {
10219 return Err(EngineError::Unsupported(alloc::format!(
10220 "WITH RECURSIVE {:?}: anchor did not return rows",
10221 cte.name
10222 )));
10223 };
10224 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10228 if !cte.column_overrides.is_empty() {
10229 if cte.column_overrides.len() != columns.len() {
10230 return Err(EngineError::Unsupported(alloc::format!(
10231 "CTE {:?} column list has {} names but anchor returns {} columns",
10232 cte.name,
10233 cte.column_overrides.len(),
10234 columns.len()
10235 )));
10236 }
10237 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10238 col.name.clone_from(name);
10239 }
10240 }
10241 let mut all_rows: Vec<Row> = anchor_rows.clone();
10242 let mut working_set: Vec<Row> = anchor_rows;
10243 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10244 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10247 if !all_union_all {
10248 for r in &all_rows {
10249 seen.insert(encode_row_key(r));
10250 }
10251 }
10252 for iter in 0..MAX_ITERATIONS {
10253 cancel.check()?;
10254 if working_set.is_empty() {
10255 break;
10256 }
10257 let mut iter_catalog = base_catalog.clone();
10259 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10260 iter_catalog
10261 .create_table(schema)
10262 .map_err(EngineError::Storage)?;
10263 {
10264 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10265 for row in &working_set {
10266 table.insert(row.clone()).map_err(EngineError::Storage)?;
10267 }
10268 }
10269 let mut iter_engine = Engine::restore(iter_catalog);
10270 if let Some(c) = self.clock {
10271 iter_engine = iter_engine.with_clock(c);
10272 }
10273 if let Some(f) = self.salt_fn {
10274 iter_engine = iter_engine.with_salt_fn(f);
10275 }
10276 let mut next_set: Vec<Row> = Vec::new();
10278 for (_, term) in &union_terms {
10279 let mut term = term.clone();
10280 term.ctes = Vec::new();
10281 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10282 let QueryResult::Rows {
10283 columns: rc,
10284 rows: rs,
10285 } = r
10286 else {
10287 return Err(EngineError::Unsupported(alloc::format!(
10288 "WITH RECURSIVE {:?}: recursive term did not return rows",
10289 cte.name
10290 )));
10291 };
10292 if rc.len() != columns.len() {
10293 return Err(EngineError::Unsupported(alloc::format!(
10294 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10295 cte.name,
10296 rc.len(),
10297 columns.len()
10298 )));
10299 }
10300 for row in rs {
10301 if !all_union_all {
10302 let key = encode_row_key(&row);
10303 if !seen.insert(key) {
10304 continue;
10305 }
10306 }
10307 next_set.push(row);
10308 }
10309 }
10310 if next_set.is_empty() {
10311 break;
10312 }
10313 all_rows.extend(next_set.iter().cloned());
10314 working_set = next_set;
10315 if all_rows.len() > MAX_TOTAL_ROWS {
10316 return Err(EngineError::Unsupported(alloc::format!(
10317 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10318 cte.name
10319 )));
10320 }
10321 if iter + 1 == MAX_ITERATIONS {
10322 return Err(EngineError::Unsupported(alloc::format!(
10323 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10324 cte.name
10325 )));
10326 }
10327 }
10328 Ok((columns, all_rows))
10329 }
10330
10331 fn resolve_select_subqueries(
10332 &self,
10333 stmt: &mut SelectStatement,
10334 cancel: CancelToken<'_>,
10335 ) -> Result<(), EngineError> {
10336 for item in &mut stmt.items {
10337 if let SelectItem::Expr { expr, .. } = item {
10338 self.resolve_expr_subqueries(expr, cancel)?;
10339 }
10340 }
10341 if let Some(w) = &mut stmt.where_ {
10342 self.resolve_expr_subqueries(w, cancel)?;
10343 }
10344 if let Some(from) = &mut stmt.from {
10348 for j in &mut from.joins {
10349 if let Some(on) = &mut j.on {
10350 self.resolve_expr_subqueries(on, cancel)?;
10351 }
10352 }
10353 }
10354 if let Some(gs) = &mut stmt.group_by {
10355 for g in gs {
10356 self.resolve_expr_subqueries(g, cancel)?;
10357 }
10358 }
10359 if let Some(h) = &mut stmt.having {
10360 self.resolve_expr_subqueries(h, cancel)?;
10361 }
10362 for o in &mut stmt.order_by {
10363 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10364 }
10365 for (_, peer) in &mut stmt.unions {
10366 self.resolve_select_subqueries(peer, cancel)?;
10367 }
10368 Ok(())
10369 }
10370
10371 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10373 &self,
10374 e: &mut Expr,
10375 cancel: CancelToken<'_>,
10376 ) -> Result<(), EngineError> {
10377 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10379 *e = replacement;
10380 return Ok(());
10381 }
10382 match e {
10383 Expr::AggregateOrdered { call, order_by } => {
10384 self.resolve_expr_subqueries(call, cancel)?;
10385 for o in order_by.iter_mut() {
10386 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10387 }
10388 }
10389 Expr::Binary { lhs, rhs, .. } => {
10390 self.resolve_expr_subqueries(lhs, cancel)?;
10391 self.resolve_expr_subqueries(rhs, cancel)?;
10392 }
10393 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10394 self.resolve_expr_subqueries(expr, cancel)?;
10395 }
10396 Expr::FunctionCall { args, .. } => {
10397 for a in args {
10398 self.resolve_expr_subqueries(a, cancel)?;
10399 }
10400 }
10401 Expr::Like { expr, pattern, .. } => {
10402 self.resolve_expr_subqueries(expr, cancel)?;
10403 self.resolve_expr_subqueries(pattern, cancel)?;
10404 }
10405 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10406 Expr::WindowFunction {
10409 args,
10410 partition_by,
10411 order_by,
10412 ..
10413 } => {
10414 for a in args {
10415 self.resolve_expr_subqueries(a, cancel)?;
10416 }
10417 for p in partition_by {
10418 self.resolve_expr_subqueries(p, cancel)?;
10419 }
10420 for (e, _, _) in order_by {
10421 self.resolve_expr_subqueries(e, cancel)?;
10422 }
10423 }
10424 Expr::ScalarSubquery(_)
10428 | Expr::Exists { .. }
10429 | Expr::InSubquery { .. }
10430 | Expr::Literal(_)
10431 | Expr::Placeholder(_)
10432 | Expr::Column(_) => {}
10433 Expr::Array(items) => {
10435 for elem in items {
10436 self.resolve_expr_subqueries(elem, cancel)?;
10437 }
10438 }
10439 Expr::ArraySubscript { target, index } => {
10440 self.resolve_expr_subqueries(target, cancel)?;
10441 self.resolve_expr_subqueries(index, cancel)?;
10442 }
10443 Expr::AnyAll { expr, array, .. } => {
10444 self.resolve_expr_subqueries(expr, cancel)?;
10445 self.resolve_expr_subqueries(array, cancel)?;
10446 }
10447 Expr::Case {
10448 operand,
10449 branches,
10450 else_branch,
10451 } => {
10452 if let Some(o) = operand {
10453 self.resolve_expr_subqueries(o, cancel)?;
10454 }
10455 for (w, t) in branches {
10456 self.resolve_expr_subqueries(w, cancel)?;
10457 self.resolve_expr_subqueries(t, cancel)?;
10458 }
10459 if let Some(e) = else_branch {
10460 self.resolve_expr_subqueries(e, cancel)?;
10461 }
10462 }
10463 }
10464 Ok(())
10465 }
10466
10467 fn eval_expr_with_correlated(
10475 &self,
10476 expr: &Expr,
10477 row: &Row,
10478 ctx: &EvalContext<'_>,
10479 cancel: CancelToken<'_>,
10480 memo: Option<&mut memoize::MemoizeCache>,
10481 ) -> Result<Value, EngineError> {
10482 if !expr_has_subquery(expr) {
10483 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10484 }
10485 let mut e = expr.clone();
10486 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10487 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10488 }
10489
10490 fn resolve_correlated_in_expr(
10491 &self,
10492 e: &mut Expr,
10493 row: &Row,
10494 ctx: &EvalContext<'_>,
10495 cancel: CancelToken<'_>,
10496 mut memo: Option<&mut memoize::MemoizeCache>,
10497 ) -> Result<(), EngineError> {
10498 match e {
10499 Expr::AggregateOrdered { call, order_by } => {
10500 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
10501 for o in order_by.iter_mut() {
10502 self.resolve_correlated_in_expr(
10503 &mut o.expr,
10504 row,
10505 ctx,
10506 cancel,
10507 memo.as_deref_mut(),
10508 )?;
10509 }
10510 }
10511 Expr::ScalarSubquery(inner) => {
10512 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10517 subquery_repr: alloc::format!("{}", **inner),
10518 outer_values: row.values.clone(),
10519 });
10520 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10521 && let Some(cached) = cache.get(k)
10522 {
10523 *e = value_to_literal_expr(cached)?;
10524 return Ok(());
10525 }
10526 let mut s = (**inner).clone();
10527 substitute_outer_columns(&mut s, row, ctx);
10528 let r = self.exec_select_cancel(&s, cancel)?;
10529 let QueryResult::Rows { rows, .. } = r else {
10530 return Err(EngineError::Unsupported(
10531 "scalar subquery: inner did not return rows".into(),
10532 ));
10533 };
10534 let value = match rows.as_slice() {
10535 [] => Value::Null,
10536 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10537 _ => {
10538 return Err(EngineError::Unsupported(alloc::format!(
10539 "scalar subquery returned {} rows; expected 0 or 1",
10540 rows.len()
10541 )));
10542 }
10543 };
10544 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10545 cache.insert(k, value.clone());
10546 }
10547 *e = value_to_literal_expr(value)?;
10548 }
10549 Expr::Exists { subquery, negated } => {
10550 let mut s = (**subquery).clone();
10551 substitute_outer_columns(&mut s, row, ctx);
10552 let r = self.exec_select_cancel(&s, cancel)?;
10553 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10554 let bit = if *negated { !exists } else { exists };
10555 *e = Expr::Literal(Literal::Bool(bit));
10556 }
10557 Expr::InSubquery {
10558 expr: lhs,
10559 subquery,
10560 negated,
10561 } => {
10562 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10563 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10564 let mut s = (**subquery).clone();
10565 substitute_outer_columns(&mut s, row, ctx);
10566 let r = self.exec_select_cancel(&s, cancel)?;
10567 let QueryResult::Rows { columns, rows, .. } = r else {
10568 return Err(EngineError::Unsupported(
10569 "IN-subquery: inner did not return rows".into(),
10570 ));
10571 };
10572 if columns.len() != 1 {
10573 return Err(EngineError::Unsupported(alloc::format!(
10574 "IN-subquery must project exactly one column; got {}",
10575 columns.len()
10576 )));
10577 }
10578 let mut found = false;
10579 let mut any_null = false;
10580 for r0 in rows {
10581 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10582 if v.is_null() {
10583 any_null = true;
10584 continue;
10585 }
10586 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10587 found = true;
10588 break;
10589 }
10590 }
10591 let bit = if found {
10592 !*negated
10593 } else if any_null {
10594 return Err(EngineError::Unsupported(
10595 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10596 ));
10597 } else {
10598 *negated
10599 };
10600 *e = Expr::Literal(Literal::Bool(bit));
10601 }
10602 Expr::Binary { lhs, rhs, .. } => {
10603 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10604 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10605 }
10606 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10607 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10608 }
10609 Expr::Like { expr, pattern, .. } => {
10610 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10611 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10612 }
10613 Expr::FunctionCall { args, .. } => {
10614 for a in args {
10615 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10616 }
10617 }
10618 Expr::Extract { source, .. } => {
10619 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10620 }
10621 Expr::WindowFunction { .. }
10622 | Expr::Literal(_)
10623 | Expr::Placeholder(_)
10624 | Expr::Column(_) => {}
10625 Expr::Array(items) => {
10627 for elem in items {
10628 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10629 }
10630 }
10631 Expr::ArraySubscript { target, index } => {
10632 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10633 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10634 }
10635 Expr::AnyAll { expr, array, .. } => {
10636 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10637 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10638 }
10639 Expr::Case {
10640 operand,
10641 branches,
10642 else_branch,
10643 } => {
10644 if let Some(o) = operand {
10645 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10646 }
10647 for (w, t) in branches {
10648 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10649 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10650 }
10651 if let Some(e) = else_branch {
10652 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10653 }
10654 }
10655 }
10656 Ok(())
10657 }
10658
10659 fn subquery_replacement(
10660 &self,
10661 e: &Expr,
10662 cancel: CancelToken<'_>,
10663 ) -> Result<Option<Expr>, EngineError> {
10664 match e {
10665 Expr::ScalarSubquery(inner) => {
10666 let mut s = (**inner).clone();
10667 self.resolve_select_subqueries(&mut s, cancel)?;
10670 let r = match self.exec_bare_select_cancel(&s, cancel) {
10671 Ok(r) => r,
10672 Err(e) if is_correlation_error(&e) => return Ok(None),
10673 Err(e) => return Err(e),
10674 };
10675 let QueryResult::Rows { rows, .. } = r else {
10676 return Err(EngineError::Unsupported(
10677 "scalar subquery: inner statement did not return rows".into(),
10678 ));
10679 };
10680 let value = match rows.as_slice() {
10681 [] => Value::Null,
10682 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10683 _ => {
10684 return Err(EngineError::Unsupported(alloc::format!(
10685 "scalar subquery returned {} rows; expected 0 or 1",
10686 rows.len()
10687 )));
10688 }
10689 };
10690 Ok(Some(value_to_literal_expr(value)?))
10691 }
10692 Expr::Exists { subquery, negated } => {
10693 let mut s = (**subquery).clone();
10694 self.resolve_select_subqueries(&mut s, cancel)?;
10695 let r = match self.exec_bare_select_cancel(&s, cancel) {
10696 Ok(r) => r,
10697 Err(e) if is_correlation_error(&e) => return Ok(None),
10698 Err(e) => return Err(e),
10699 };
10700 let exists = match r {
10701 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10702 QueryResult::CommandOk { .. } => false,
10703 };
10704 let bit = if *negated { !exists } else { exists };
10705 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10706 }
10707 Expr::InSubquery {
10708 expr,
10709 subquery,
10710 negated,
10711 } => {
10712 let mut s = (**subquery).clone();
10713 self.resolve_select_subqueries(&mut s, cancel)?;
10714 let r = match self.exec_bare_select_cancel(&s, cancel) {
10715 Ok(r) => r,
10716 Err(e) if is_correlation_error(&e) => return Ok(None),
10717 Err(e) => return Err(e),
10718 };
10719 let QueryResult::Rows { columns, rows, .. } = r else {
10720 return Err(EngineError::Unsupported(
10721 "IN-subquery: inner statement did not return rows".into(),
10722 ));
10723 };
10724 if columns.len() != 1 {
10725 return Err(EngineError::Unsupported(alloc::format!(
10726 "IN-subquery must project exactly one column; got {}",
10727 columns.len()
10728 )));
10729 }
10730 let mut acc: Option<Expr> = None;
10733 for row in rows {
10734 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10735 let lit = value_to_literal_expr(v)?;
10736 let cmp = Expr::Binary {
10737 lhs: expr.clone(),
10738 op: BinOp::Eq,
10739 rhs: Box::new(lit),
10740 };
10741 acc = Some(match acc {
10742 None => cmp,
10743 Some(prev) => Expr::Binary {
10744 lhs: Box::new(prev),
10745 op: BinOp::Or,
10746 rhs: Box::new(cmp),
10747 },
10748 });
10749 }
10750 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10751 let final_expr = if *negated {
10752 Expr::Unary {
10753 op: UnOp::Not,
10754 expr: Box::new(combined),
10755 }
10756 } else {
10757 combined
10758 };
10759 Ok(Some(final_expr))
10760 }
10761 _ => Ok(None),
10762 }
10763 }
10764}
10765
10766fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10778 if let Some(from) = &stmt.from
10779 && from_refers_to(from, target)
10780 {
10781 return true;
10782 }
10783 for (_, peer) in &stmt.unions {
10784 if select_refers_to(peer, target) {
10785 return true;
10786 }
10787 }
10788 for item in &stmt.items {
10789 if let SelectItem::Expr { expr, .. } = item
10790 && expr_refers_to(expr, target)
10791 {
10792 return true;
10793 }
10794 }
10795 if let Some(w) = &stmt.where_
10796 && expr_refers_to(w, target)
10797 {
10798 return true;
10799 }
10800 false
10801}
10802
10803fn from_refers_to(from: &FromClause, target: &str) -> bool {
10804 if from.primary.name.eq_ignore_ascii_case(target) {
10805 return true;
10806 }
10807 from.joins
10808 .iter()
10809 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10810}
10811
10812fn expr_refers_to(e: &Expr, target: &str) -> bool {
10813 match e {
10814 Expr::AggregateOrdered { call, order_by } => {
10815 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
10816 }
10817 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10818 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10819 select_refers_to(subquery, target)
10820 }
10821 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10822 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10823 expr_refers_to(expr, target)
10824 }
10825 Expr::Like { expr, pattern, .. } => {
10826 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10827 }
10828 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10829 Expr::Extract { source, .. } => expr_refers_to(source, target),
10830 Expr::WindowFunction {
10831 args,
10832 partition_by,
10833 order_by,
10834 ..
10835 } => {
10836 args.iter().any(|a| expr_refers_to(a, target))
10837 || partition_by.iter().any(|p| expr_refers_to(p, target))
10838 || order_by.iter().any(|(o, _, _)| expr_refers_to(o, target))
10839 }
10840 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10841 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10842 Expr::ArraySubscript { target: t, index } => {
10843 expr_refers_to(t, target) || expr_refers_to(index, target)
10844 }
10845 Expr::AnyAll { expr, array, .. } => {
10846 expr_refers_to(expr, target) || expr_refers_to(array, target)
10847 }
10848 Expr::Case {
10849 operand,
10850 branches,
10851 else_branch,
10852 } => {
10853 operand
10854 .as_deref()
10855 .is_some_and(|o| expr_refers_to(o, target))
10856 || branches
10857 .iter()
10858 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10859 || else_branch
10860 .as_deref()
10861 .is_some_and(|e| expr_refers_to(e, target))
10862 }
10863 }
10864}
10865
10866fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10877 let s = match ty {
10878 DataType::Int => "integer",
10879 DataType::BigInt => "bigint",
10880 DataType::SmallInt => "smallint",
10881 DataType::Float => "double precision",
10882 DataType::Bool => "boolean",
10883 DataType::Text => "text",
10884 DataType::Varchar(_) => "character varying",
10885 DataType::Date => "date",
10886 DataType::Timestamp => "timestamp without time zone",
10887 DataType::Timestamptz => "timestamp with time zone",
10888 DataType::Json => "jsonb",
10889 DataType::Bytes => "bytea",
10890 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10891 DataType::TsVector => "tsvector",
10892 DataType::TsQuery => "tsquery",
10893 DataType::Vector { .. } => "USER-DEFINED",
10894 _ => "USER-DEFINED",
10897 };
10898 alloc::string::String::from(s)
10899}
10900
10901fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10908 let schema = alloc::vec![
10909 ColumnSchema::new("table_catalog", DataType::Text, false),
10910 ColumnSchema::new("table_schema", DataType::Text, false),
10911 ColumnSchema::new("table_name", DataType::Text, false),
10912 ColumnSchema::new("column_name", DataType::Text, false),
10913 ColumnSchema::new("ordinal_position", DataType::Int, false),
10914 ColumnSchema::new("is_nullable", DataType::Text, false),
10915 ColumnSchema::new("data_type", DataType::Text, false),
10916 ];
10917 let mut rows: Vec<Row> = Vec::new();
10918 for tname in cat.table_names() {
10919 let Some(t) = cat.get(&tname) else { continue };
10920 for (i, col) in t.schema().columns.iter().enumerate() {
10921 #[allow(clippy::cast_possible_wrap)]
10922 let ordinal = (i + 1) as i32;
10923 rows.push(Row::new(alloc::vec![
10924 Value::Text("spg".into()),
10925 Value::Text("public".into()),
10926 Value::Text(tname.clone()),
10927 Value::Text(col.name.clone()),
10928 Value::Int(ordinal),
10929 Value::Text(if col.nullable {
10930 "YES".into()
10931 } else {
10932 "NO".into()
10933 }),
10934 Value::Text(pg_data_type_text(col.ty)),
10935 ]));
10936 }
10937 }
10938 (schema, rows)
10939}
10940
10941fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10943 let schema = alloc::vec![
10944 ColumnSchema::new("table_catalog", DataType::Text, false),
10945 ColumnSchema::new("table_schema", DataType::Text, false),
10946 ColumnSchema::new("table_name", DataType::Text, false),
10947 ColumnSchema::new("table_type", DataType::Text, false),
10948 ];
10949 let mut rows: Vec<Row> = Vec::new();
10950 for tname in cat.table_names() {
10951 rows.push(Row::new(alloc::vec![
10952 Value::Text("spg".into()),
10953 Value::Text("public".into()),
10954 Value::Text(tname.clone()),
10955 Value::Text("BASE TABLE".into()),
10956 ]));
10957 }
10958 (schema, rows)
10959}
10960
10961fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10965 let schema = alloc::vec![
10966 ColumnSchema::new("relname", DataType::Text, false),
10967 ColumnSchema::new("relkind", DataType::Text, false),
10968 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10969 ];
10970 let mut rows: Vec<Row> = Vec::new();
10971 for tname in cat.table_names() {
10972 rows.push(Row::new(alloc::vec![
10973 Value::Text(tname.clone()),
10974 Value::Text("r".into()),
10975 Value::BigInt(2200), ]));
10977 }
10978 (schema, rows)
10979}
10980
10981fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10985 let schema = alloc::vec![
10986 ColumnSchema::new("attrelid", DataType::Text, false),
10987 ColumnSchema::new("attname", DataType::Text, false),
10988 ColumnSchema::new("attnum", DataType::Int, false),
10989 ColumnSchema::new("atttypid", DataType::Text, false),
10990 ColumnSchema::new("attnotnull", DataType::Bool, false),
10991 ];
10992 let mut rows: Vec<Row> = Vec::new();
10993 for tname in cat.table_names() {
10994 let Some(t) = cat.get(&tname) else { continue };
10995 for (i, col) in t.schema().columns.iter().enumerate() {
10996 #[allow(clippy::cast_possible_wrap)]
10997 let ordinal = (i + 1) as i32;
10998 rows.push(Row::new(alloc::vec![
10999 Value::Text(tname.clone()),
11000 Value::Text(col.name.clone()),
11001 Value::Int(ordinal),
11002 Value::Text(pg_data_type_text(col.ty)),
11003 Value::Bool(!col.nullable),
11004 ]));
11005 }
11006 }
11007 (schema, rows)
11008}
11009
11010fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11027 let schema = alloc::vec![
11028 ColumnSchema::new("oid", DataType::BigInt, false),
11029 ColumnSchema::new("typname", DataType::Text, false),
11030 ColumnSchema::new("typlen", DataType::SmallInt, false),
11031 ColumnSchema::new("typtype", DataType::Text, false),
11032 ColumnSchema::new("typcategory", DataType::Text, false),
11033 ColumnSchema::new("typelem", DataType::BigInt, false),
11034 ColumnSchema::new("typarray", DataType::BigInt, false),
11035 ColumnSchema::new("typnamespace", DataType::BigInt, false),
11036 ];
11037 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
11040 (16, "bool", 1, "b", "B", 0, 1000),
11042 (17, "bytea", -1, "b", "U", 0, 1001),
11043 (18, "char", 1, "b", "S", 0, 1002),
11044 (19, "name", 64, "b", "S", 0, 1003),
11045 (20, "int8", 8, "b", "N", 0, 1016),
11046 (21, "int2", 2, "b", "N", 0, 1005),
11047 (23, "int4", 4, "b", "N", 0, 1007),
11048 (24, "regproc", 4, "b", "N", 0, 1008),
11049 (25, "text", -1, "b", "S", 0, 1009),
11050 (26, "oid", 4, "b", "N", 0, 1028),
11051 (114, "json", -1, "b", "U", 0, 199),
11052 (142, "xml", -1, "b", "U", 0, 143),
11053 (700, "float4", 4, "b", "N", 0, 1021),
11054 (701, "float8", 8, "b", "N", 0, 1022),
11055 (650, "cidr", -1, "b", "I", 0, 651),
11056 (869, "inet", -1, "b", "I", 0, 1041),
11057 (829, "macaddr", 6, "b", "U", 0, 1040),
11058 (1042, "bpchar", -1, "b", "S", 0, 1014),
11059 (1043, "varchar", -1, "b", "S", 0, 1015),
11060 (1082, "date", 4, "b", "D", 0, 1182),
11061 (1083, "time", 8, "b", "D", 0, 1183),
11062 (1114, "timestamp", 8, "b", "D", 0, 1115),
11063 (1184, "timestamptz", 8, "b", "D", 0, 1185),
11064 (1186, "interval", 16, "b", "T", 0, 1187),
11065 (1266, "timetz", 12, "b", "D", 0, 1270),
11066 (1700, "numeric", -1, "b", "N", 0, 1231),
11067 (790, "money", 8, "b", "N", 0, 791),
11068 (2950, "uuid", 16, "b", "U", 0, 2951),
11069 (3802, "jsonb", -1, "b", "U", 0, 3807),
11070 (3614, "tsvector", -1, "b", "U", 0, 3643),
11071 (3615, "tsquery", -1, "b", "U", 0, 3645),
11072 (3908, "tstzrange", -1, "r", "R", 0, 3909),
11074 (3910, "tsrange", -1, "r", "R", 0, 3911),
11075 (3904, "int4range", -1, "r", "R", 0, 3905),
11076 (3926, "int8range", -1, "r", "R", 0, 3927),
11077 (3906, "numrange", -1, "r", "R", 0, 3907),
11078 (3912, "daterange", -1, "r", "R", 0, 3913),
11079 ];
11080 let arrays: &[(i64, &str, i64)] = &[
11083 (1000, "_bool", 16),
11084 (1001, "_bytea", 17),
11085 (1002, "_char", 18),
11086 (1003, "_name", 19),
11087 (1016, "_int8", 20),
11088 (1005, "_int2", 21),
11089 (1007, "_int4", 23),
11090 (1008, "_regproc", 24),
11091 (1009, "_text", 25),
11092 (1028, "_oid", 26),
11093 (199, "_json", 114),
11094 (143, "_xml", 142),
11095 (1021, "_float4", 700),
11096 (1022, "_float8", 701),
11097 (651, "_cidr", 650),
11098 (1041, "_inet", 869),
11099 (1040, "_macaddr", 829),
11100 (1014, "_bpchar", 1042),
11101 (1015, "_varchar", 1043),
11102 (1182, "_date", 1082),
11103 (1183, "_time", 1083),
11104 (1115, "_timestamp", 1114),
11105 (1185, "_timestamptz", 1184),
11106 (1187, "_interval", 1186),
11107 (1270, "_timetz", 1266),
11108 (1231, "_numeric", 1700),
11109 (791, "_money", 790),
11110 (2951, "_uuid", 2950),
11111 (3807, "_jsonb", 3802),
11112 (3643, "_tsvector", 3614),
11113 (3645, "_tsquery", 3615),
11114 ];
11115 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
11116 for &(oid, name, len, ty, cat, elem, arr) in scalars {
11117 rows.push(Row::new(alloc::vec![
11118 Value::BigInt(oid),
11119 Value::Text(name.into()),
11120 Value::SmallInt(len),
11121 Value::Text(ty.into()),
11122 Value::Text(cat.into()),
11123 Value::BigInt(elem),
11124 Value::BigInt(arr),
11125 Value::BigInt(2200),
11126 ]));
11127 }
11128 for &(oid, name, elem) in arrays {
11129 rows.push(Row::new(alloc::vec![
11130 Value::BigInt(oid),
11131 Value::Text(name.into()),
11132 Value::SmallInt(-1),
11133 Value::Text("b".into()),
11134 Value::Text("A".into()),
11135 Value::BigInt(elem),
11136 Value::BigInt(0),
11137 Value::BigInt(2200),
11138 ]));
11139 }
11140 (schema, rows)
11141}
11142
11143fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11162 let schema = alloc::vec![
11163 ColumnSchema::new("tgname", DataType::Text, false),
11164 ColumnSchema::new("relname", DataType::Text, false),
11165 ColumnSchema::new("tgenabled", DataType::Text, false),
11166 ColumnSchema::new("timing", DataType::Text, false),
11167 ColumnSchema::new("events", DataType::Text, false),
11168 ColumnSchema::new("function", DataType::Text, false),
11169 ];
11170 let rows: Vec<Row> = cat
11171 .triggers()
11172 .iter()
11173 .map(|t| {
11174 Row::new(alloc::vec![
11175 Value::Text(t.name.clone()),
11176 Value::Text(t.table.clone()),
11177 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
11178 Value::Text(t.timing.clone()),
11179 Value::Text(t.events.join(" OR ")),
11180 Value::Text(t.function.clone()),
11181 ])
11182 })
11183 .collect();
11184 (schema, rows)
11185}
11186
11187fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11188 let schema = alloc::vec![
11189 ColumnSchema::new("oid", DataType::BigInt, false),
11190 ColumnSchema::new("proname", DataType::Text, false),
11191 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11192 ColumnSchema::new("prokind", DataType::Text, false),
11193 ColumnSchema::new("pronargs", DataType::Int, false),
11194 ColumnSchema::new("prorettype", DataType::BigInt, false),
11195 ];
11196 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11199 (1318, "length", "f", 1, 23),
11201 (871, "upper", "f", 1, 25),
11202 (870, "lower", "f", 1, 25),
11203 (936, "substring", "f", 3, 25),
11204 (937, "substring", "f", 2, 25),
11205 (3055, "btrim", "f", 1, 25),
11206 (885, "btrim", "f", 2, 25),
11207 (3056, "ltrim", "f", 1, 25),
11208 (875, "ltrim", "f", 2, 25),
11209 (3057, "rtrim", "f", 1, 25),
11210 (876, "rtrim", "f", 2, 25),
11211 (1397, "abs", "f", 1, 23),
11212 (1396, "abs", "f", 1, 20),
11213 (1606, "round", "f", 1, 1700),
11214 (1707, "round", "f", 2, 1700),
11215 (2308, "ceil", "f", 1, 701),
11216 (2309, "ceiling", "f", 1, 701),
11217 (2310, "floor", "f", 1, 701),
11218 (1376, "sqrt", "f", 1, 701),
11219 (1369, "ln", "f", 1, 701),
11220 (1373, "exp", "f", 1, 701),
11221 (1368, "power", "f", 2, 701),
11222 (2228, "random", "f", 0, 701),
11223 (1299, "now", "f", 0, 1184),
11225 (1274, "current_timestamp", "f", 0, 1184),
11226 (1140, "current_date", "f", 0, 1082),
11227 (2050, "current_time", "f", 0, 1083),
11228 (1158, "date_trunc", "f", 2, 1184),
11229 (1171, "date_part", "f", 2, 701),
11230 (1172, "age", "f", 1, 1186),
11231 (936, "to_char", "f", 2, 25),
11232 (861, "current_database", "f", 0, 19),
11234 (745, "current_user", "f", 0, 19),
11235 (745, "session_user", "f", 0, 19),
11236 (1402, "current_schema", "f", 0, 19),
11237 (3058, "concat", "f", -1, 25),
11239 (3059, "concat_ws", "f", -1, 25),
11240 (3539, "format", "f", -1, 25),
11241 (2877, "pg_typeof", "f", 1, 2206),
11243 (3198, "json_build_object", "f", -1, 114),
11245 (3199, "jsonb_build_object", "f", -1, 3802),
11246 (3271, "json_build_array", "f", -1, 114),
11247 (3272, "jsonb_build_array", "f", -1, 3802),
11248 (3253, "gen_random_uuid", "f", 0, 2950),
11250 (3252, "uuid_generate_v4", "f", 0, 2950),
11251 (2147, "count", "a", 0, 20),
11253 (2803, "count", "a", -1, 20),
11254 (2116, "max", "a", 1, 23),
11255 (2132, "min", "a", 1, 23),
11256 (2108, "sum", "a", 1, 20),
11257 (2100, "avg", "a", 1, 1700),
11258 (2517, "string_agg", "a", 2, 25),
11259 (2747, "array_agg", "a", 1, 1009),
11260 (2517, "bool_and", "a", 1, 16),
11261 (2518, "bool_or", "a", 1, 16),
11262 (2519, "every", "a", 1, 16),
11263 (3100, "row_number", "w", 0, 20),
11265 (3101, "rank", "w", 0, 20),
11266 (3102, "dense_rank", "w", 0, 20),
11267 (3103, "percent_rank", "w", 0, 701),
11268 (3104, "cume_dist", "w", 0, 701),
11269 (3105, "lag", "w", -1, 2283),
11270 (3106, "lead", "w", -1, 2283),
11271 (3107, "first_value", "w", 1, 2283),
11272 (3108, "last_value", "w", 1, 2283),
11273 (3109, "nth_value", "w", 2, 2283),
11274 ];
11275 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11276 for &(oid, name, kind, nargs, rettype) in funcs {
11277 rows.push(Row::new(alloc::vec![
11278 Value::BigInt(oid),
11279 Value::Text(name.into()),
11280 Value::BigInt(11),
11281 Value::Text(kind.into()),
11282 Value::Int(nargs),
11283 Value::BigInt(rettype),
11284 ]));
11285 }
11286 (schema, rows)
11287}
11288
11289fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11295 let schema = alloc::vec![
11296 ColumnSchema::new("user", DataType::Text, false),
11297 ColumnSchema::new("host", DataType::Text, false),
11298 ColumnSchema::new("select_priv", DataType::Text, false),
11299 ];
11300 let mut rows: Vec<Row> = Vec::new();
11301 rows.push(Row::new(alloc::vec![
11302 Value::Text("root".into()),
11303 Value::Text("localhost".into()),
11304 Value::Text("Y".into()),
11305 ]));
11306 for (name, _) in engine.users.iter() {
11307 if name != "root" {
11308 rows.push(Row::new(alloc::vec![
11309 Value::Text(name.to_string()),
11310 Value::Text("%".into()),
11311 Value::Text("Y".into()),
11312 ]));
11313 }
11314 }
11315 (schema, rows)
11316}
11317
11318fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11323 let schema = alloc::vec![
11324 ColumnSchema::new("host", DataType::Text, false),
11325 ColumnSchema::new("db", DataType::Text, false),
11326 ColumnSchema::new("user", DataType::Text, false),
11327 ColumnSchema::new("select_priv", DataType::Text, false),
11328 ];
11329 let rows = alloc::vec![Row::new(alloc::vec![
11330 Value::Text("localhost".into()),
11331 Value::Text("postgres".into()),
11332 Value::Text("root".into()),
11333 Value::Text("Y".into()),
11334 ])];
11335 (schema, rows)
11336}
11337
11338fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11351 let schema = alloc::vec![
11352 ColumnSchema::new("constraint_name", DataType::Text, false),
11353 ColumnSchema::new("table_name", DataType::Text, false),
11354 ColumnSchema::new("column_name", DataType::Text, false),
11355 ColumnSchema::new("ordinal_position", DataType::Int, false),
11356 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11357 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11358 ];
11359 let mut rows: Vec<Row> = Vec::new();
11360 for tname in cat.table_names() {
11361 let Some(t) = cat.get(&tname) else { continue };
11362 let cols = &t.schema().columns;
11363 let col_name_at = |pos: usize| -> String {
11364 cols.get(pos)
11365 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11366 };
11367 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11369 let conname = fk
11370 .name
11371 .clone()
11372 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11373 for (i, (&local, &parent)) in fk
11374 .local_columns
11375 .iter()
11376 .zip(fk.parent_columns.iter())
11377 .enumerate()
11378 {
11379 let parent_name = cat
11380 .get(&fk.parent_table)
11381 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11382 .unwrap_or_else(|| alloc::format!("col{parent}"));
11383 #[allow(clippy::cast_possible_wrap)]
11384 let ordinal = (i + 1) as i32;
11385 rows.push(Row::new(alloc::vec![
11386 Value::Text(conname.clone()),
11387 Value::Text(tname.clone()),
11388 Value::Text(col_name_at(local)),
11389 Value::Int(ordinal),
11390 Value::Text(fk.parent_table.clone()),
11391 Value::Text(parent_name),
11392 ]));
11393 }
11394 }
11395 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11397 let conname = if uc.is_primary_key {
11398 alloc::format!("{}_pkey", tname)
11399 } else {
11400 alloc::format!("{}_uniq{ci}", tname)
11401 };
11402 for (i, &local) in uc.columns.iter().enumerate() {
11403 #[allow(clippy::cast_possible_wrap)]
11404 let ordinal = (i + 1) as i32;
11405 rows.push(Row::new(alloc::vec![
11406 Value::Text(conname.clone()),
11407 Value::Text(tname.clone()),
11408 Value::Text(col_name_at(local)),
11409 Value::Int(ordinal),
11410 Value::Text(String::new()),
11411 Value::Text(String::new()),
11412 ]));
11413 }
11414 }
11415 }
11416 (schema, rows)
11417}
11418
11419fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11422 let schema = alloc::vec![
11423 ColumnSchema::new("constraint_name", DataType::Text, false),
11424 ColumnSchema::new("table_name", DataType::Text, false),
11425 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11426 ColumnSchema::new("update_rule", DataType::Text, false),
11427 ColumnSchema::new("delete_rule", DataType::Text, false),
11428 ];
11429 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11430 match a {
11431 spg_storage::FkAction::Cascade => "CASCADE",
11432 spg_storage::FkAction::SetNull => "SET NULL",
11433 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11434 spg_storage::FkAction::Restrict => "RESTRICT",
11435 spg_storage::FkAction::NoAction => "NO ACTION",
11436 }
11437 }
11438 let mut rows: Vec<Row> = Vec::new();
11439 for tname in cat.table_names() {
11440 let Some(t) = cat.get(&tname) else { continue };
11441 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11442 let conname = fk
11443 .name
11444 .clone()
11445 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11446 rows.push(Row::new(alloc::vec![
11447 Value::Text(conname),
11448 Value::Text(tname.clone()),
11449 Value::Text(fk.parent_table.clone()),
11450 Value::Text(rule_name(fk.on_update).into()),
11451 Value::Text(rule_name(fk.on_delete).into()),
11452 ]));
11453 }
11454 }
11455 (schema, rows)
11456}
11457
11458fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11462 let schema = alloc::vec![
11463 ColumnSchema::new("table_name", DataType::Text, false),
11464 ColumnSchema::new("index_name", DataType::Text, false),
11465 ColumnSchema::new("column_name", DataType::Text, false),
11466 ColumnSchema::new("seq_in_index", DataType::Int, false),
11467 ColumnSchema::new("non_unique", DataType::Int, false),
11468 ColumnSchema::new("index_type", DataType::Text, false),
11469 ];
11470 let mut rows: Vec<Row> = Vec::new();
11471 for tname in cat.table_names() {
11472 let Some(t) = cat.get(&tname) else { continue };
11473 for idx in t.indices() {
11474 let col = t
11475 .schema()
11476 .columns
11477 .get(idx.column_position)
11478 .map_or("?".into(), |c| c.name.clone());
11479 rows.push(Row::new(alloc::vec![
11480 Value::Text(tname.clone()),
11481 Value::Text(idx.name.clone()),
11482 Value::Text(col),
11483 Value::Int(1),
11484 Value::Int(i32::from(!idx.is_unique)),
11485 Value::Text("BTREE".into()),
11486 ]));
11487 }
11488 }
11489 (schema, rows)
11490}
11491
11492fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11496 let schema = alloc::vec![
11497 ColumnSchema::new("routine_name", DataType::Text, false),
11498 ColumnSchema::new("routine_type", DataType::Text, false),
11499 ColumnSchema::new("data_type", DataType::Text, false),
11500 ];
11501 (schema, Vec::new())
11502}
11503
11504fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11519 let schema = alloc::vec![
11520 ColumnSchema::new("conname", DataType::Text, false),
11521 ColumnSchema::new("contype", DataType::Text, false),
11522 ColumnSchema::new("conrelid", DataType::Text, false),
11523 ColumnSchema::new("confrelid", DataType::Text, false),
11524 ColumnSchema::new("conkey", DataType::Text, false),
11525 ColumnSchema::new("confkey", DataType::Text, false),
11526 ];
11527 let mut rows: Vec<Row> = Vec::new();
11528 for tname in cat.table_names() {
11529 let Some(t) = cat.get(&tname) else { continue };
11530 let cols = &t.schema().columns;
11531 let col_name_at = |pos: usize| -> String {
11532 cols.get(pos)
11533 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11534 };
11535 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11537 let kind = if uc.is_primary_key { "p" } else { "u" };
11538 let conname = if uc.is_primary_key {
11539 alloc::format!("{}_pkey", tname)
11540 } else {
11541 alloc::format!("{}_uniq{ci}", tname)
11542 };
11543 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11544 rows.push(Row::new(alloc::vec![
11545 Value::Text(conname),
11546 Value::Text(kind.into()),
11547 Value::Text(tname.clone()),
11548 Value::Text(String::new()),
11549 Value::Text(conkey.join(",")),
11550 Value::Text(String::new()),
11551 ]));
11552 }
11553 for idx in t.indices() {
11558 if !idx.is_unique {
11559 continue;
11560 }
11561 let is_primary = idx.name.ends_with("_pkey");
11562 let conname = idx.name.clone();
11563 let kind = if is_primary { "p" } else { "u" };
11564 let col_name = col_name_at(idx.column_position);
11565 let already = t
11568 .schema()
11569 .uniqueness_constraints
11570 .iter()
11571 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11572 if already {
11573 continue;
11574 }
11575 rows.push(Row::new(alloc::vec![
11576 Value::Text(conname),
11577 Value::Text(kind.into()),
11578 Value::Text(tname.clone()),
11579 Value::Text(String::new()),
11580 Value::Text(col_name),
11581 Value::Text(String::new()),
11582 ]));
11583 }
11584 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11586 let conname = fk
11587 .name
11588 .clone()
11589 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11590 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11591 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11594 fk.parent_columns
11595 .iter()
11596 .map(|&p| {
11597 parent
11598 .schema()
11599 .columns
11600 .get(p)
11601 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11602 })
11603 .collect()
11604 } else {
11605 fk.parent_columns
11606 .iter()
11607 .map(|p| alloc::format!("col{p}"))
11608 .collect()
11609 };
11610 rows.push(Row::new(alloc::vec![
11611 Value::Text(conname),
11612 Value::Text("f".into()),
11613 Value::Text(tname.clone()),
11614 Value::Text(fk.parent_table.clone()),
11615 Value::Text(conkey.join(",")),
11616 Value::Text(confkey.join(",")),
11617 ]));
11618 }
11619 }
11620 (schema, rows)
11621}
11622
11623fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11628 let schema = alloc::vec![
11629 ColumnSchema::new("oid", DataType::BigInt, false),
11630 ColumnSchema::new("datname", DataType::Text, false),
11631 ColumnSchema::new("datdba", DataType::BigInt, false),
11632 ColumnSchema::new("encoding", DataType::Int, false),
11633 ColumnSchema::new("datcollate", DataType::Text, false),
11634 ];
11635 let rows = alloc::vec![Row::new(alloc::vec![
11636 Value::BigInt(16384),
11637 Value::Text("postgres".into()),
11638 Value::BigInt(10),
11639 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11641 ])];
11642 (schema, rows)
11643}
11644
11645fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11650 let schema = alloc::vec![
11651 ColumnSchema::new("oid", DataType::BigInt, false),
11652 ColumnSchema::new("rolname", DataType::Text, false),
11653 ColumnSchema::new("rolsuper", DataType::Bool, false),
11654 ColumnSchema::new("rolinherit", DataType::Bool, false),
11655 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11656 ];
11657 let mut rows: Vec<Row> = Vec::new();
11658 let oid: i64 = 10;
11659 for (i, (name, _)) in engine.users.iter().enumerate() {
11660 rows.push(Row::new(alloc::vec![
11661 Value::BigInt(oid + (i as i64) + 1),
11662 Value::Text(name.to_string()),
11663 Value::Bool(false),
11664 Value::Bool(true),
11665 Value::Bool(true),
11666 ]));
11667 }
11668 if !rows
11671 .iter()
11672 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11673 {
11674 rows.insert(
11675 0,
11676 Row::new(alloc::vec![
11677 Value::BigInt(10),
11678 Value::Text("postgres".into()),
11679 Value::Bool(true),
11680 Value::Bool(true),
11681 Value::Bool(true),
11682 ]),
11683 );
11684 }
11685 (schema, rows)
11686}
11687
11688fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11697 let schema = alloc::vec![
11698 ColumnSchema::new("oid", DataType::BigInt, false),
11699 ColumnSchema::new("extname", DataType::Text, false),
11700 ColumnSchema::new("extversion", DataType::Text, false),
11701 ColumnSchema::new("extnamespace", DataType::Text, false),
11702 ];
11703 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11704 let rows = exts
11705 .iter()
11706 .enumerate()
11707 .map(|(i, (name, ver))| {
11708 Row::new(alloc::vec![
11709 Value::BigInt(16384 + i as i64),
11710 Value::Text((*name).into()),
11711 Value::Text((*ver).into()),
11712 Value::Text("pg_catalog".into()),
11713 ])
11714 })
11715 .collect();
11716 (schema, rows)
11717}
11718
11719fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11720 let schema = alloc::vec![
11721 ColumnSchema::new("schemaname", DataType::Text, false),
11722 ColumnSchema::new("viewname", DataType::Text, false),
11723 ColumnSchema::new("definition", DataType::Text, false),
11724 ];
11725 let mut rows: Vec<Row> = Vec::new();
11726 for (name, def) in cat.views() {
11727 rows.push(Row::new(alloc::vec![
11728 Value::Text("public".into()),
11729 Value::Text(name.clone()),
11730 Value::Text(def.body.clone()),
11731 ]));
11732 }
11733 (schema, rows)
11734}
11735
11736fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11742 let schema = alloc::vec![
11743 ColumnSchema::new("name", DataType::Text, false),
11744 ColumnSchema::new("setting", DataType::Text, false),
11745 ColumnSchema::new("category", DataType::Text, false),
11746 ];
11747 let mut rows: Vec<Row> = Vec::new();
11748 let defaults: &[(&str, &str, &str)] = &[
11750 ("server_version", "16.0 (spg)", "Preset Options"),
11751 ("server_encoding", "UTF8", "Client Connection Defaults"),
11752 ("client_encoding", "UTF8", "Client Connection Defaults"),
11753 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11754 ("TimeZone", "UTC", "Client Connection Defaults"),
11755 ("standard_conforming_strings", "on", "Compatibility"),
11756 ("integer_datetimes", "on", "Compatibility"),
11757 ("max_connections", "100", "Connections and Authentication"),
11758 ];
11759 for &(name, val, cat) in defaults {
11760 rows.push(Row::new(alloc::vec![
11761 Value::Text(name.into()),
11762 Value::Text(val.into()),
11763 Value::Text(cat.into()),
11764 ]));
11765 }
11766 for (k, v) in &engine.session_params {
11768 if !defaults
11769 .iter()
11770 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11771 {
11772 rows.push(Row::new(alloc::vec![
11773 Value::Text(k.clone()),
11774 Value::Text(v.clone()),
11775 Value::Text("Session".into()),
11776 ]));
11777 }
11778 }
11779 (schema, rows)
11780}
11781
11782fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11793 let schema = alloc::vec![
11794 ColumnSchema::new("schemaname", DataType::Text, false),
11795 ColumnSchema::new("tablename", DataType::Text, false),
11796 ColumnSchema::new("indexname", DataType::Text, false),
11797 ColumnSchema::new("indexdef", DataType::Text, false),
11798 ];
11799 let mut rows: Vec<Row> = Vec::new();
11800 for tname in cat.table_names() {
11801 let Some(t) = cat.get(&tname) else { continue };
11802 for idx in t.indices() {
11803 let col_name = t
11804 .schema()
11805 .columns
11806 .get(idx.column_position)
11807 .map_or("?".into(), |c| c.name.clone());
11808 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11809 let indexdef = alloc::format!(
11810 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11811 idx.name,
11812 tname,
11813 col_name
11814 );
11815 rows.push(Row::new(alloc::vec![
11816 Value::Text("public".into()),
11817 Value::Text(tname.clone()),
11818 Value::Text(idx.name.clone()),
11819 Value::Text(indexdef),
11820 ]));
11821 }
11822 }
11823 (schema, rows)
11824}
11825
11826fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11838 let schema = alloc::vec![
11839 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11840 ColumnSchema::new("indrelid", DataType::BigInt, false),
11841 ColumnSchema::new("indnatts", DataType::Int, false),
11842 ColumnSchema::new("indisunique", DataType::Bool, false),
11843 ColumnSchema::new("indisprimary", DataType::Bool, false),
11844 ];
11845 let mut rows: Vec<Row> = Vec::new();
11846 let mut idx_oid: i64 = 100_000;
11847 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11848 let Some(t) = cat.get(tname) else { continue };
11849 for idx in t.indices() {
11850 idx_oid += 1;
11851 #[allow(clippy::cast_possible_wrap)]
11852 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11853 let is_primary = idx.name.ends_with("_pkey");
11856 rows.push(Row::new(alloc::vec![
11857 Value::BigInt(idx_oid),
11858 Value::BigInt((table_idx + 1) as i64),
11859 Value::Int(nattrs),
11860 Value::Bool(idx.is_unique),
11861 Value::Bool(is_primary),
11862 ]));
11863 }
11864 }
11865 (schema, rows)
11866}
11867
11868fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11873 let schema = alloc::vec![
11874 ColumnSchema::new("oid", DataType::BigInt, false),
11875 ColumnSchema::new("nspname", DataType::Text, false),
11876 ColumnSchema::new("nspowner", DataType::BigInt, false),
11877 ];
11878 let rows = alloc::vec![
11879 Row::new(alloc::vec![
11880 Value::BigInt(11),
11881 Value::Text("pg_catalog".into()),
11882 Value::BigInt(10),
11883 ]),
11884 Row::new(alloc::vec![
11885 Value::BigInt(2200),
11886 Value::Text("public".into()),
11887 Value::BigInt(10),
11888 ]),
11889 Row::new(alloc::vec![
11890 Value::BigInt(13000),
11891 Value::Text("information_schema".into()),
11892 Value::BigInt(10),
11893 ]),
11894 ];
11895 (schema, rows)
11896}
11897
11898fn materialise_meta_view(
11901 catalog: &mut Catalog,
11902 name: &str,
11903 columns: Vec<ColumnSchema>,
11904 rows: Vec<Row>,
11905) -> Result<(), EngineError> {
11906 let schema = TableSchema::new(name.to_string(), columns);
11907 catalog.create_table(schema).map_err(EngineError::Storage)?;
11908 let table = catalog
11909 .get_mut(name)
11910 .expect("just-created meta view must exist");
11911 for row in rows {
11912 table.insert(row).map_err(EngineError::Storage)?;
11913 }
11914 Ok(())
11915}
11916
11917fn collect_view_refs(
11930 tref: &spg_sql::ast::TableRef,
11931 cat: &spg_storage::Catalog,
11932 into: &mut Vec<String>,
11933) {
11934 if cat.views().contains_key(&tref.name)
11935 && cat.get(&tref.name).is_none()
11936 && !into.iter().any(|n| n == &tref.name)
11937 {
11938 into.push(tref.name.clone());
11939 }
11940}
11941
11942fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11943 fn is_meta(name: &str) -> bool {
11944 name.starts_with("__spg_info_")
11945 || name.starts_with("__spg_pg_")
11946 || name.starts_with("__spg_mysql_")
11947 }
11948 if let Some(from) = &stmt.from {
11949 if is_meta(&from.primary.name) {
11950 return true;
11951 }
11952 for j in &from.joins {
11953 if is_meta(&j.table.name) {
11954 return true;
11955 }
11956 }
11957 }
11958 for cte in &stmt.ctes {
11959 if select_references_meta_view(&cte.body) {
11960 return true;
11961 }
11962 }
11963 false
11964}
11965
11966fn collect_meta_view_names(
11971 stmt: &SelectStatement,
11972 into: &mut alloc::collections::BTreeSet<String>,
11973) {
11974 fn is_meta(name: &str) -> bool {
11975 name.starts_with("__spg_info_")
11976 || name.starts_with("__spg_pg_")
11977 || name.starts_with("__spg_mysql_")
11978 }
11979 if let Some(from) = &stmt.from {
11980 if is_meta(&from.primary.name) {
11981 into.insert(from.primary.name.clone());
11982 }
11983 for j in &from.joins {
11984 if is_meta(&j.table.name) {
11985 into.insert(j.table.name.clone());
11986 }
11987 }
11988 }
11989 for cte in &stmt.ctes {
11990 collect_meta_view_names(&cte.body, into);
11991 }
11992}
11993
11994fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11995 let mut out = columns.to_vec();
11996 for (col_idx, col) in out.iter_mut().enumerate() {
11997 if col.ty != DataType::Text {
11998 continue;
11999 }
12000 let mut inferred: Option<DataType> = None;
12001 let mut all_null = true;
12002 for row in rows {
12003 let Some(v) = row.values.get(col_idx) else {
12004 continue;
12005 };
12006 let ty = match v {
12007 Value::Null => continue,
12008 Value::SmallInt(_) => DataType::SmallInt,
12009 Value::Int(_) => DataType::Int,
12010 Value::BigInt(_) => DataType::BigInt,
12011 Value::Float(_) => DataType::Float,
12012 Value::Bool(_) => DataType::Bool,
12013 Value::Vector(_) => DataType::Vector {
12014 dim: 0,
12015 encoding: VecEncoding::F32,
12016 },
12017 _ => DataType::Text,
12018 };
12019 all_null = false;
12020 inferred = Some(match inferred {
12021 None => ty,
12022 Some(prev) if prev == ty => prev,
12023 Some(_) => DataType::Text,
12024 });
12025 }
12026 if let Some(t) = inferred {
12027 col.ty = t;
12028 col.nullable = true;
12029 } else if all_null {
12030 col.nullable = true;
12031 }
12032 }
12033 out
12034}
12035
12036#[allow(clippy::too_many_lines, clippy::format_push_string)]
12041fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
12058 use alloc::collections::BTreeSet;
12059 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
12060 let mut out: Vec<String> = Vec::new();
12061 let cat = engine.active_catalog();
12062 let Some(from) = &stmt.from else {
12066 return out;
12067 };
12068 let mut tables: Vec<String> = Vec::new();
12069 tables.push(from.primary.name.clone());
12070 for j in &from.joins {
12071 tables.push(j.table.name.clone());
12072 }
12073 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
12076 if let Some(w) = &stmt.where_ {
12077 collect_column_refs(w, &mut col_refs);
12078 }
12079 for j in &from.joins {
12080 if let Some(on) = &j.on {
12081 collect_column_refs(on, &mut col_refs);
12082 }
12083 }
12084 for cn in &col_refs {
12085 let owner: Option<String> = if let Some(q) = &cn.qualifier {
12088 tables.iter().find(|t| t == &q).cloned()
12089 } else {
12090 tables.iter().find_map(|t| {
12091 cat.get(t).and_then(|tbl| {
12092 if tbl.schema().column_position(&cn.name).is_some() {
12093 Some(t.clone())
12094 } else {
12095 None
12096 }
12097 })
12098 })
12099 };
12100 let Some(owner) = owner else {
12101 continue;
12102 };
12103 let Some(tbl) = cat.get(&owner) else {
12104 continue;
12105 };
12106 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
12107 continue;
12108 };
12109 let already_indexed = tbl.indices().iter().any(|i| {
12112 matches!(i.kind, spg_storage::IndexKind::BTree(_))
12113 && i.column_position == col_pos
12114 && i.expression.is_none()
12115 && i.partial_predicate.is_none()
12116 });
12117 if already_indexed {
12118 continue;
12119 }
12120 if seen.insert((owner.clone(), cn.name.clone())) {
12121 out.push(alloc::format!(
12122 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
12123 owner,
12124 cn.name,
12125 owner,
12126 cn.name
12127 ));
12128 }
12129 }
12130 out
12131}
12132
12133fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
12136 match expr {
12137 Expr::Column(cn) => out.push(cn.clone()),
12138 Expr::FunctionCall { args, .. } => {
12139 for a in args {
12140 collect_column_refs(a, out);
12141 }
12142 }
12143 Expr::Binary { lhs, rhs, .. } => {
12144 collect_column_refs(lhs, out);
12145 collect_column_refs(rhs, out);
12146 }
12147 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
12148 _ => {}
12149 }
12150}
12151
12152fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
12153 let catalog = engine.active_catalog();
12154 let cold_ids = catalog.cold_segment_ids_global();
12155 let any_cold = !cold_ids.is_empty();
12156 let cold_ids_repr = if any_cold {
12157 let mut s = alloc::string::String::from("[");
12158 for (i, id) in cold_ids.iter().enumerate() {
12159 if i > 0 {
12160 s.push(',');
12161 }
12162 s.push_str(&alloc::format!("{id}"));
12163 }
12164 s.push(']');
12165 s
12166 } else {
12167 alloc::string::String::new()
12168 };
12169 for (idx, line) in lines.iter_mut().enumerate() {
12170 let trimmed = line.trim_start();
12171 let is_top_level = idx == 0;
12172 if is_top_level {
12173 line.push_str(&alloc::format!(" (rows={total_rows})"));
12174 continue;
12175 }
12176 if let Some(rest) = trimmed.strip_prefix("From: ") {
12177 let (name, scan_kind) = match rest.split_once(" [") {
12178 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12179 None => (rest.trim(), ""),
12180 };
12181 let bare = name.split_whitespace().next().unwrap_or(name);
12182 let hot = catalog.get(bare).map(|t| t.rows().len());
12183 let annot = match (hot, scan_kind) {
12188 (Some(h), "full scan") => {
12189 let mut s = alloc::format!(" (hot_rows={h}");
12190 if any_cold {
12191 s.push_str(&alloc::format!(
12192 ", cold_tier=present, cold_segments={cold_ids_repr}"
12193 ));
12194 }
12195 s.push(')');
12196 s
12197 }
12198 (Some(h), "index seek") => {
12199 let mut s = alloc::format!(" (hot_rows≤{h}");
12200 if any_cold {
12201 s.push_str(&alloc::format!(
12202 ", cold_tier=present, cold_segments={cold_ids_repr}"
12203 ));
12204 }
12205 s.push(')');
12206 s
12207 }
12208 _ => " (rows=—)".to_string(),
12209 };
12210 line.push_str(&annot);
12211 continue;
12212 }
12213 line.push_str(" (rows=—)");
12215 }
12216}
12217
12218fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
12219 let pad = " ".repeat(depth);
12220 let top = if !stmt.ctes.is_empty() {
12222 if stmt.ctes.iter().any(|c| c.recursive) {
12223 "CTEScan (WITH RECURSIVE)"
12224 } else {
12225 "CTEScan (WITH)"
12226 }
12227 } else if !stmt.unions.is_empty() {
12228 "UnionScan"
12229 } else if select_has_window(stmt) {
12230 "WindowAgg"
12231 } else if aggregate::uses_aggregate(stmt) {
12232 "Aggregate"
12233 } else if stmt.distinct {
12234 "Distinct"
12235 } else if stmt.from.is_some() {
12236 "TableScan"
12237 } else {
12238 "Result"
12239 };
12240 out.push(alloc::format!("{pad}{top}"));
12241 let child = " ".repeat(depth + 1);
12242 for cte in &stmt.ctes {
12244 let head = if cte.recursive {
12245 alloc::format!("{child}CTE (recursive): {}", cte.name)
12246 } else {
12247 alloc::format!("{child}CTE: {}", cte.name)
12248 };
12249 out.push(head);
12250 explain_select(&cte.body, engine, depth + 2, out);
12251 }
12252 if let Some(from) = &stmt.from {
12254 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12255 if let Some(alias) = &from.primary.alias {
12256 tag.push_str(&alloc::format!(" AS {alias}"));
12257 }
12258 if let Some(w) = &stmt.where_
12261 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12262 {
12263 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12264 let cols = &table.schema().columns;
12265 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12266 tag.push_str(" [index seek]");
12267 } else {
12268 tag.push_str(" [full scan]");
12269 }
12270 } else {
12271 tag.push_str(" [full scan]");
12272 }
12273 out.push(tag);
12274 for j in &from.joins {
12275 let kind = match j.kind {
12276 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12277 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12278 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12279 };
12280 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12281 if let Some(alias) = &j.table.alias {
12282 s.push_str(&alloc::format!(" AS {alias}"));
12283 }
12284 if j.on.is_some() {
12285 s.push_str(" (ON …)");
12286 }
12287 out.push(s);
12288 }
12289 }
12290 if let Some(w) = &stmt.where_ {
12292 let mut s = alloc::format!("{child}Filter: {w}");
12293 if expr_has_subquery(w) {
12294 s.push_str(" [subquery]");
12295 }
12296 out.push(s);
12297 }
12298 if let Some(gs) = &stmt.group_by {
12299 let mut parts = Vec::new();
12300 for g in gs {
12301 parts.push(alloc::format!("{g}"));
12302 }
12303 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12304 }
12305 if let Some(h) = &stmt.having {
12306 out.push(alloc::format!("{child}Having: {h}"));
12307 }
12308 for o in &stmt.order_by {
12309 let dir = if o.desc { "DESC" } else { "ASC" };
12310 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12311 }
12312 if let Some(lim) = stmt.limit {
12313 out.push(alloc::format!("{child}Limit: {lim}"));
12314 }
12315 if let Some(off) = stmt.offset {
12316 out.push(alloc::format!("{child}Offset: {off}"));
12317 }
12318 if stmt
12320 .items
12321 .iter()
12322 .any(|it| matches!(it, SelectItem::Wildcard))
12323 {
12324 out.push(alloc::format!("{child}Project: *"));
12325 } else {
12326 out.push(alloc::format!(
12327 "{child}Project: {} item(s)",
12328 stmt.items.len()
12329 ));
12330 }
12331 for (kind, peer) in &stmt.unions {
12333 let label = match kind {
12334 UnionKind::All => "UNION ALL",
12335 UnionKind::Distinct => "UNION",
12336 };
12337 out.push(alloc::format!("{child}{label}"));
12338 explain_select(peer, engine, depth + 2, out);
12339 }
12340}
12341
12342fn is_correlation_error(e: &EngineError) -> bool {
12347 matches!(
12348 e,
12349 EngineError::Eval(
12350 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12351 )
12352 )
12353}
12354
12355struct JoinedPeer<'a> {
12366 eager_rows: Option<Vec<Row>>,
12367 cols: Vec<ColumnSchema>,
12368 alias: String,
12369 kind: JoinKind,
12370 on: Option<&'a Expr>,
12371 lateral: Option<&'a SelectStatement>,
12372}
12373
12374fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12381 match expr {
12382 Expr::Column(c) => c.name.clone(),
12384 Expr::FunctionCall { name, .. } => name.clone(),
12387 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12389 _ => alloc::format!("column{}", idx + 1),
12391 }
12392}
12393
12394fn substitute_outer_columns_multi(
12401 stmt: &mut SelectStatement,
12402 outer_row: &Row,
12403 outer_schema: &[ColumnSchema],
12404) {
12405 substitute_outer_in_select(stmt, outer_row, outer_schema);
12406}
12407
12408fn substitute_outer_in_select(
12409 stmt: &mut SelectStatement,
12410 outer_row: &Row,
12411 outer_schema: &[ColumnSchema],
12412) {
12413 for item in &mut stmt.items {
12414 if let SelectItem::Expr { expr, .. } = item {
12415 substitute_outer_in_expr(expr, outer_row, outer_schema);
12416 }
12417 }
12418 if let Some(w) = &mut stmt.where_ {
12419 substitute_outer_in_expr(w, outer_row, outer_schema);
12420 }
12421 if let Some(gs) = &mut stmt.group_by {
12422 for g in gs {
12423 substitute_outer_in_expr(g, outer_row, outer_schema);
12424 }
12425 }
12426 if let Some(h) = &mut stmt.having {
12427 substitute_outer_in_expr(h, outer_row, outer_schema);
12428 }
12429 for o in &mut stmt.order_by {
12430 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12431 }
12432 for (_, peer) in &mut stmt.unions {
12433 substitute_outer_in_select(peer, outer_row, outer_schema);
12434 }
12435}
12436
12437fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12438 if let Expr::Column(c) = e
12439 && let Some(qual) = &c.qualifier
12440 {
12441 let composite = alloc::format!("{qual}.{}", c.name);
12442 if let Some(idx) = outer_schema
12443 .iter()
12444 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12445 {
12446 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12447 if let Ok(lit) = value_to_literal_expr(v) {
12448 *e = lit;
12449 return;
12450 }
12451 }
12452 }
12453 match e {
12454 Expr::Binary { lhs, rhs, .. } => {
12455 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12456 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12457 }
12458 Expr::Unary { expr: inner, .. } => {
12459 substitute_outer_in_expr(inner, outer_row, outer_schema);
12460 }
12461 Expr::FunctionCall { args, .. } => {
12462 for a in args {
12463 substitute_outer_in_expr(a, outer_row, outer_schema);
12464 }
12465 }
12466 Expr::Cast { expr: inner, .. } => {
12467 substitute_outer_in_expr(inner, outer_row, outer_schema);
12468 }
12469 Expr::Case {
12470 operand,
12471 branches,
12472 else_branch,
12473 } => {
12474 if let Some(op) = operand {
12475 substitute_outer_in_expr(op, outer_row, outer_schema);
12476 }
12477 for (cond, val) in branches {
12478 substitute_outer_in_expr(cond, outer_row, outer_schema);
12479 substitute_outer_in_expr(val, outer_row, outer_schema);
12480 }
12481 if let Some(e) = else_branch {
12482 substitute_outer_in_expr(e, outer_row, outer_schema);
12483 }
12484 }
12485 _ => {}
12486 }
12487}
12488
12489fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12490 let outer_alias = ctx.table_alias.unwrap_or("");
12497 substitute_in_select(stmt, row, ctx, outer_alias);
12498}
12499
12500fn substitute_in_select(
12501 stmt: &mut SelectStatement,
12502 row: &Row,
12503 ctx: &EvalContext<'_>,
12504 outer_alias: &str,
12505) {
12506 for item in &mut stmt.items {
12507 if let SelectItem::Expr { expr, .. } = item {
12508 substitute_in_expr(expr, row, ctx, outer_alias);
12509 }
12510 }
12511 if let Some(w) = &mut stmt.where_ {
12512 substitute_in_expr(w, row, ctx, outer_alias);
12513 }
12514 if let Some(gs) = &mut stmt.group_by {
12515 for g in gs {
12516 substitute_in_expr(g, row, ctx, outer_alias);
12517 }
12518 }
12519 if let Some(h) = &mut stmt.having {
12520 substitute_in_expr(h, row, ctx, outer_alias);
12521 }
12522 for o in &mut stmt.order_by {
12523 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12524 }
12525 for (_, peer) in &mut stmt.unions {
12526 substitute_in_select(peer, row, ctx, outer_alias);
12527 }
12528}
12529
12530fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12531 if let Expr::Column(c) = e
12532 && let Some(qual) = &c.qualifier
12533 {
12534 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
12538 ctx.columns
12539 .iter()
12540 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12541 } else {
12542 None
12543 }
12544 .or_else(|| {
12545 let composite = alloc::format!("{qual}.{name}", name = c.name);
12546 ctx.columns
12547 .iter()
12548 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12549 });
12550 if let Some(idx) = idx {
12551 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12552 if let Ok(lit) = value_to_literal_expr(v) {
12553 *e = lit;
12554 return;
12555 }
12556 }
12557 }
12558 match e {
12559 Expr::AggregateOrdered { call, order_by } => {
12560 substitute_in_expr(call, row, ctx, outer_alias);
12561 for o in order_by.iter_mut() {
12562 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12563 }
12564 }
12565 Expr::Binary { lhs, rhs, .. } => {
12566 substitute_in_expr(lhs, row, ctx, outer_alias);
12567 substitute_in_expr(rhs, row, ctx, outer_alias);
12568 }
12569 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12570 substitute_in_expr(expr, row, ctx, outer_alias);
12571 }
12572 Expr::Like { expr, pattern, .. } => {
12573 substitute_in_expr(expr, row, ctx, outer_alias);
12574 substitute_in_expr(pattern, row, ctx, outer_alias);
12575 }
12576 Expr::FunctionCall { args, .. } => {
12577 for a in args {
12578 substitute_in_expr(a, row, ctx, outer_alias);
12579 }
12580 }
12581 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12582 Expr::WindowFunction {
12583 args,
12584 partition_by,
12585 order_by,
12586 ..
12587 } => {
12588 for a in args {
12589 substitute_in_expr(a, row, ctx, outer_alias);
12590 }
12591 for p in partition_by {
12592 substitute_in_expr(p, row, ctx, outer_alias);
12593 }
12594 for (o, _, _) in order_by {
12595 substitute_in_expr(o, row, ctx, outer_alias);
12596 }
12597 }
12598 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12599 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12600 substitute_in_select(subquery, row, ctx, outer_alias);
12601 }
12602 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12603 Expr::Array(items) => {
12604 for elem in items {
12605 substitute_in_expr(elem, row, ctx, outer_alias);
12606 }
12607 }
12608 Expr::ArraySubscript { target, index } => {
12609 substitute_in_expr(target, row, ctx, outer_alias);
12610 substitute_in_expr(index, row, ctx, outer_alias);
12611 }
12612 Expr::AnyAll { expr, array, .. } => {
12613 substitute_in_expr(expr, row, ctx, outer_alias);
12614 substitute_in_expr(array, row, ctx, outer_alias);
12615 }
12616 Expr::Case {
12617 operand,
12618 branches,
12619 else_branch,
12620 } => {
12621 if let Some(o) = operand {
12622 substitute_in_expr(o, row, ctx, outer_alias);
12623 }
12624 for (w, t) in branches {
12625 substitute_in_expr(w, row, ctx, outer_alias);
12626 substitute_in_expr(t, row, ctx, outer_alias);
12627 }
12628 if let Some(e) = else_branch {
12629 substitute_in_expr(e, row, ctx, outer_alias);
12630 }
12631 }
12632 }
12633}
12634
12635fn encode_row_key(row: &Row) -> Vec<u8> {
12639 let mut out = Vec::new();
12640 for v in &row.values {
12641 let s = alloc::format!("{v:?}|");
12642 out.extend_from_slice(s.as_bytes());
12643 }
12644 out
12645}
12646
12647fn select_has_window(stmt: &SelectStatement) -> bool {
12648 for item in &stmt.items {
12649 if let SelectItem::Expr { expr, .. } = item
12650 && expr_has_window(expr)
12651 {
12652 return true;
12653 }
12654 }
12655 false
12656}
12657
12658fn expr_has_window(e: &Expr) -> bool {
12659 match e {
12660 Expr::WindowFunction { .. } => true,
12661 Expr::AggregateOrdered { call, order_by } => {
12662 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
12663 }
12664 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12665 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12666 expr_has_window(expr)
12667 }
12668 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12669 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12670 Expr::Extract { source, .. } => expr_has_window(source),
12671 Expr::ScalarSubquery(_)
12672 | Expr::Exists { .. }
12673 | Expr::InSubquery { .. }
12674 | Expr::Literal(_)
12675 | Expr::Placeholder(_)
12676 | Expr::Column(_) => false,
12677 Expr::Array(items) => items.iter().any(expr_has_window),
12678 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12679 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12680 Expr::Case {
12681 operand,
12682 branches,
12683 else_branch,
12684 } => {
12685 operand.as_deref().is_some_and(expr_has_window)
12686 || branches
12687 .iter()
12688 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12689 || else_branch.as_deref().is_some_and(expr_has_window)
12690 }
12691 }
12692}
12693
12694fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12695 if let Expr::WindowFunction { .. } = e {
12696 if !out.iter().any(|x| x == e) {
12701 out.push(e.clone());
12702 }
12703 return;
12704 }
12705 match e {
12706 Expr::WindowFunction { .. } => unreachable!(),
12708 Expr::Binary { lhs, rhs, .. } => {
12709 collect_window_nodes(lhs, out);
12710 collect_window_nodes(rhs, out);
12711 }
12712 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12713 collect_window_nodes(expr, out);
12714 }
12715 Expr::FunctionCall { args, .. } => {
12716 for a in args {
12717 collect_window_nodes(a, out);
12718 }
12719 }
12720 Expr::Like { expr, pattern, .. } => {
12721 collect_window_nodes(expr, out);
12722 collect_window_nodes(pattern, out);
12723 }
12724 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12725 _ => {}
12726 }
12727}
12728
12729fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12730 if let Expr::WindowFunction { .. } = e
12731 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12732 {
12733 *e = Expr::Column(spg_sql::ast::ColumnName {
12734 qualifier: None,
12735 name: alloc::format!("__win_{idx}"),
12736 });
12737 return;
12738 }
12739 match e {
12740 Expr::Binary { lhs, rhs, .. } => {
12741 rewrite_window_to_columns(lhs, window_nodes);
12742 rewrite_window_to_columns(rhs, window_nodes);
12743 }
12744 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12745 rewrite_window_to_columns(expr, window_nodes);
12746 }
12747 Expr::FunctionCall { args, .. } => {
12748 for a in args {
12749 rewrite_window_to_columns(a, window_nodes);
12750 }
12751 }
12752 Expr::Like { expr, pattern, .. } => {
12753 rewrite_window_to_columns(expr, window_nodes);
12754 rewrite_window_to_columns(pattern, window_nodes);
12755 }
12756 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12757 _ => {}
12758 }
12759}
12760
12761fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12765 for (x, y) in a.iter().zip(b.iter()) {
12766 let c = value_cmp(x, y);
12767 if c != core::cmp::Ordering::Equal {
12768 return c;
12769 }
12770 }
12771 a.len().cmp(&b.len())
12772}
12773
12774fn order_key_cmp(
12775 a: &[(Value, bool, Option<bool>)],
12776 b: &[(Value, bool, Option<bool>)],
12777) -> core::cmp::Ordering {
12778 for ((va, desc, nf), (vb, _, _)) in a.iter().zip(b.iter()) {
12781 let c = order_by_value_cmp(*desc, *nf, va, vb);
12782 if c != core::cmp::Ordering::Equal {
12783 return c;
12784 }
12785 }
12786 a.len().cmp(&b.len())
12787}
12788
12789const fn value_is_integer(v: &Value) -> bool {
12795 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12796}
12797
12798const fn value_to_i64(v: &Value) -> i64 {
12802 match v {
12803 Value::SmallInt(n) => *n as i64,
12804 Value::Int(n) => *n as i64,
12805 Value::BigInt(n) => *n,
12806 _ => panic!("value_to_i64 called on non-integer Value"),
12807 }
12808}
12809
12810fn generate_series_integers(
12816 start: i64,
12817 stop: i64,
12818 step: i64,
12819 cancel: &CancelToken<'_>,
12820) -> Result<alloc::vec::Vec<Row>, EngineError> {
12821 if step == 0 {
12822 return Err(EngineError::Unsupported(
12823 "generate_series(): step argument cannot be zero".into(),
12824 ));
12825 }
12826 let mut out = alloc::vec::Vec::new();
12827 let mut cur = start;
12828 const MAX_ROWS: usize = 10_000_000;
12832 loop {
12833 cancel.check()?;
12834 if step > 0 && cur > stop {
12835 break;
12836 }
12837 if step < 0 && cur < stop {
12838 break;
12839 }
12840 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12841 if out.len() > MAX_ROWS {
12842 return Err(EngineError::Unsupported(alloc::format!(
12843 "generate_series(): exceeded {MAX_ROWS} rows; \
12844 narrow start/stop or use a larger step"
12845 )));
12846 }
12847 cur = match cur.checked_add(step) {
12848 Some(n) => n,
12849 None => break,
12850 };
12851 }
12852 Ok(out)
12853}
12854
12855fn generate_series_timestamps(
12860 start: i64,
12861 stop: i64,
12862 step: Value,
12863 cancel: &CancelToken<'_>,
12864) -> Result<alloc::vec::Vec<Row>, EngineError> {
12865 let (months, micros) = match &step {
12866 Value::Interval { months, micros } => (*months, *micros),
12867 _ => unreachable!("caller guards step.is_interval"),
12868 };
12869 if months == 0 && micros == 0 {
12870 return Err(EngineError::Unsupported(
12871 "generate_series(): INTERVAL step cannot be zero".into(),
12872 ));
12873 }
12874 let ascending = months > 0 || micros > 0;
12875 let mut out = alloc::vec::Vec::new();
12876 let mut cur = Value::Timestamp(start);
12877 const MAX_ROWS: usize = 10_000_000;
12878 loop {
12879 cancel.check()?;
12880 let cur_t = match cur {
12881 Value::Timestamp(t) => t,
12882 _ => unreachable!("loop invariant: cur is Timestamp"),
12883 };
12884 if ascending && cur_t > stop {
12885 break;
12886 }
12887 if !ascending && cur_t < stop {
12888 break;
12889 }
12890 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12891 if out.len() > MAX_ROWS {
12892 return Err(EngineError::Unsupported(alloc::format!(
12893 "generate_series(): exceeded {MAX_ROWS} rows; \
12894 narrow start/stop or use a larger step"
12895 )));
12896 }
12897 let next = eval::apply_binary_interval(
12898 spg_sql::ast::BinOp::Add,
12899 &cur,
12900 &Value::Interval { months, micros },
12901 )
12902 .map_err(EngineError::Eval)?;
12903 cur = match next {
12904 Some(v) => v,
12905 None => break,
12906 };
12907 }
12908 Ok(out)
12909}
12910
12911#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
12917 desc: bool,
12918 nulls_first: Option<bool>,
12919 a: &Value,
12920 b: &Value,
12921) -> core::cmp::Ordering {
12922 use core::cmp::Ordering;
12923 let nf = nulls_first.unwrap_or(desc);
12924 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
12925 (true, true) => Ordering::Equal,
12926 (true, false) => {
12927 if nf {
12928 Ordering::Less
12929 } else {
12930 Ordering::Greater
12931 }
12932 }
12933 (false, true) => {
12934 if nf {
12935 Ordering::Greater
12936 } else {
12937 Ordering::Less
12938 }
12939 }
12940 (false, false) => {
12941 let c = value_cmp(a, b);
12942 if desc { c.reverse() } else { c }
12943 }
12944 }
12945}
12946
12947fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12948 use core::cmp::Ordering;
12949 match (a, b) {
12950 (Value::Null, Value::Null) => Ordering::Equal,
12951 (Value::Null, _) => Ordering::Less,
12952 (_, Value::Null) => Ordering::Greater,
12953 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12954 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12955 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12956 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12957 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12958 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12959 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12960 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12961 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12964 }
12965}
12966
12967#[allow(
12973 clippy::too_many_arguments,
12974 clippy::cast_possible_truncation,
12975 clippy::cast_possible_wrap,
12976 clippy::cast_precision_loss,
12977 clippy::cast_sign_loss,
12978 clippy::doc_markdown,
12979 clippy::too_many_lines,
12980 clippy::type_complexity,
12981 clippy::match_same_arms
12982)]
12983fn compute_window_partition(
12984 name: &str,
12985 args: &[Expr],
12986 ordered: bool,
12987 frame: Option<&WindowFrame>,
12988 null_treatment: spg_sql::ast::NullTreatment,
12989 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
12990 filtered_rows: &[&Row],
12991 ctx: &EvalContext<'_>,
12992 out_vals: &mut [Value],
12993) -> Result<(), EngineError> {
12994 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12995 let lower = name.to_ascii_lowercase();
12996 match lower.as_str() {
12997 "row_number" => {
12998 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12999 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
13000 }
13001 Ok(())
13002 }
13003 "rank" => {
13004 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13005 let mut current_rank: i64 = 1;
13006 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13007 if let Some(p) = prev_key
13008 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13009 {
13010 current_rank = (i + 1) as i64;
13011 }
13012 if prev_key.is_none() {
13013 current_rank = 1;
13014 }
13015 out_vals[*idx] = Value::BigInt(current_rank);
13016 prev_key = Some(okey.as_slice());
13017 }
13018 Ok(())
13019 }
13020 "dense_rank" => {
13021 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13022 let mut current_rank: i64 = 0;
13023 for (_, okey, idx) in slice {
13024 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
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 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
13033 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
13036 slice.iter().map(|_| Value::Null).collect()
13037 } else {
13038 slice
13039 .iter()
13040 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13041 .collect::<Result<_, _>>()
13042 .map_err(EngineError::Eval)?
13043 };
13044 let eff = effective_frame(frame, ordered)?;
13048 #[allow(clippy::needless_range_loop)]
13049 for i in 0..slice.len() {
13050 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13051 let mut sum: f64 = 0.0;
13052 let mut count: i64 = 0;
13053 let mut min_v: Option<f64> = None;
13054 let mut max_v: Option<f64> = None;
13055 let mut row_count: i64 = 0;
13056 if lo <= hi {
13057 for j in lo..=hi {
13058 let v = &arg_values[j];
13059 match lower.as_str() {
13060 "count_star" => row_count += 1,
13061 "count" => {
13062 if !v.is_null() {
13063 count += 1;
13064 }
13065 }
13066 _ => {
13067 if let Some(x) = value_to_f64(v) {
13068 sum += x;
13069 count += 1;
13070 min_v = Some(min_v.map_or(x, |m| m.min(x)));
13071 max_v = Some(max_v.map_or(x, |m| m.max(x)));
13072 }
13073 }
13074 }
13075 }
13076 }
13077 let value = match lower.as_str() {
13078 "count_star" => Value::BigInt(row_count),
13079 "count" => Value::BigInt(count),
13080 "sum" => Value::Float(sum),
13081 "avg" => {
13082 if count == 0 {
13083 Value::Null
13084 } else {
13085 Value::Float(sum / count as f64)
13086 }
13087 }
13088 "min" => min_v.map_or(Value::Null, Value::Float),
13089 "max" => max_v.map_or(Value::Null, Value::Float),
13090 _ => unreachable!(),
13091 };
13092 let (_, _, idx) = &slice[i];
13093 out_vals[*idx] = value;
13094 }
13095 Ok(())
13096 }
13097 "lag" | "lead" => {
13098 if args.is_empty() {
13101 return Err(EngineError::Unsupported(alloc::format!(
13102 "{lower}() requires at least one argument"
13103 )));
13104 }
13105 let offset: i64 = if args.len() >= 2 {
13106 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13107 .map_err(EngineError::Eval)?;
13108 match v {
13109 Value::SmallInt(n) => i64::from(n),
13110 Value::Int(n) => i64::from(n),
13111 Value::BigInt(n) => n,
13112 _ => {
13113 return Err(EngineError::Unsupported(alloc::format!(
13114 "{lower}() offset must be integer"
13115 )));
13116 }
13117 }
13118 } else {
13119 1
13120 };
13121 let default: Value = if args.len() >= 3 {
13122 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
13123 .map_err(EngineError::Eval)?
13124 } else {
13125 Value::Null
13126 };
13127 let values: Vec<Value> = slice
13128 .iter()
13129 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13130 .collect::<Result<_, _>>()
13131 .map_err(EngineError::Eval)?;
13132 let n = slice.len();
13133 for (i, (_, _, idx)) in slice.iter().enumerate() {
13134 let signed_offset = if lower == "lag" { -offset } else { offset };
13135 let v = if ignore_nulls {
13136 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
13140 let needed: i64 = signed_offset.abs();
13141 if needed == 0 {
13142 values[i].clone()
13143 } else {
13144 let mut j: i64 = i as i64;
13145 let mut hits: i64 = 0;
13146 let mut found: Option<Value> = None;
13147 loop {
13148 j += step;
13149 if j < 0 || j >= n as i64 {
13150 break;
13151 }
13152 #[allow(clippy::cast_sign_loss)]
13153 let v = &values[j as usize];
13154 if !v.is_null() {
13155 hits += 1;
13156 if hits == needed {
13157 found = Some(v.clone());
13158 break;
13159 }
13160 }
13161 }
13162 found.unwrap_or_else(|| default.clone())
13163 }
13164 } else {
13165 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
13166 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
13167 default.clone()
13168 } else {
13169 #[allow(clippy::cast_sign_loss)]
13170 {
13171 values[target_signed as usize].clone()
13172 }
13173 }
13174 };
13175 out_vals[*idx] = v;
13176 }
13177 Ok(())
13178 }
13179 "first_value" | "last_value" | "nth_value" => {
13180 if args.is_empty() {
13181 return Err(EngineError::Unsupported(alloc::format!(
13182 "{lower}() requires at least one argument"
13183 )));
13184 }
13185 let values: Vec<Value> = slice
13186 .iter()
13187 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13188 .collect::<Result<_, _>>()
13189 .map_err(EngineError::Eval)?;
13190 let nth: usize = if lower == "nth_value" {
13191 if args.len() < 2 {
13192 return Err(EngineError::Unsupported(
13193 "nth_value() requires (expr, n)".into(),
13194 ));
13195 }
13196 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13197 .map_err(EngineError::Eval)?;
13198 let raw = match v {
13199 Value::SmallInt(n) => i64::from(n),
13200 Value::Int(n) => i64::from(n),
13201 Value::BigInt(n) => n,
13202 _ => {
13203 return Err(EngineError::Unsupported(
13204 "nth_value() n must be integer".into(),
13205 ));
13206 }
13207 };
13208 if raw < 1 {
13209 return Err(EngineError::Unsupported(
13210 "nth_value() n must be >= 1".into(),
13211 ));
13212 }
13213 #[allow(clippy::cast_sign_loss)]
13214 {
13215 raw as usize
13216 }
13217 } else {
13218 0
13219 };
13220 let eff = effective_frame(frame, ordered)?;
13221 for i in 0..slice.len() {
13222 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13223 let (_, _, idx) = &slice[i];
13224 let v = if lo > hi {
13225 Value::Null
13226 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
13227 if lower == "first_value" {
13230 (lo..=hi)
13231 .find_map(|j| {
13232 let v = &values[j];
13233 (!v.is_null()).then(|| v.clone())
13234 })
13235 .unwrap_or(Value::Null)
13236 } else {
13237 (lo..=hi)
13238 .rev()
13239 .find_map(|j| {
13240 let v = &values[j];
13241 (!v.is_null()).then(|| v.clone())
13242 })
13243 .unwrap_or(Value::Null)
13244 }
13245 } else {
13246 match lower.as_str() {
13247 "first_value" => values[lo].clone(),
13248 "last_value" => values[hi].clone(),
13249 "nth_value" => {
13250 let pos = lo + nth - 1;
13251 if pos > hi {
13252 Value::Null
13253 } else {
13254 values[pos].clone()
13255 }
13256 }
13257 _ => unreachable!(),
13258 }
13259 };
13260 out_vals[*idx] = v;
13261 }
13262 Ok(())
13263 }
13264 "ntile" => {
13265 if args.is_empty() {
13266 return Err(EngineError::Unsupported(
13267 "ntile(n) requires an integer argument".into(),
13268 ));
13269 }
13270 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
13271 .map_err(EngineError::Eval)?;
13272 let bucket_count: i64 = match v {
13273 Value::SmallInt(n) => i64::from(n),
13274 Value::Int(n) => i64::from(n),
13275 Value::BigInt(n) => n,
13276 _ => {
13277 return Err(EngineError::Unsupported(
13278 "ntile() argument must be integer".into(),
13279 ));
13280 }
13281 };
13282 if bucket_count < 1 {
13283 return Err(EngineError::Unsupported(
13284 "ntile() argument must be >= 1".into(),
13285 ));
13286 }
13287 #[allow(clippy::cast_sign_loss)]
13288 let buckets = bucket_count as usize;
13289 let n = slice.len();
13290 let base = n / buckets;
13293 let extras = n % buckets;
13294 let mut bucket: usize = 1;
13295 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
13296 let mut buckets_with_extra_remaining = extras;
13297 for (_, _, idx) in slice {
13298 if remaining_in_bucket == 0 {
13299 bucket += 1;
13300 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
13301 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
13302 base + 1
13303 } else {
13304 base
13305 };
13306 if remaining_in_bucket == 0 {
13309 remaining_in_bucket = 1;
13310 }
13311 }
13312 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13313 remaining_in_bucket -= 1;
13314 }
13315 Ok(())
13316 }
13317 "percent_rank" => {
13318 let n = slice.len();
13321 let mut prev_key: Option<&[(Value, bool, Option<bool>)]> = None;
13322 let mut current_rank: i64 = 1;
13323 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13324 if let Some(p) = prev_key
13325 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13326 {
13327 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13328 }
13329 if prev_key.is_none() {
13330 current_rank = 1;
13331 }
13332 #[allow(clippy::cast_precision_loss)]
13333 let pr = if n <= 1 {
13334 0.0
13335 } else {
13336 (current_rank - 1) as f64 / (n - 1) as f64
13337 };
13338 out_vals[*idx] = Value::Float(pr);
13339 prev_key = Some(okey.as_slice());
13340 }
13341 Ok(())
13342 }
13343 "cume_dist" => {
13344 let n = slice.len();
13346 for i in 0..slice.len() {
13348 let peer_end = peer_group_end(slice, i);
13349 #[allow(clippy::cast_precision_loss)]
13350 let cd = (peer_end + 1) as f64 / n as f64;
13351 let (_, _, idx) = &slice[i];
13352 out_vals[*idx] = Value::Float(cd);
13353 }
13354 Ok(())
13355 }
13356 other => Err(EngineError::Unsupported(alloc::format!(
13357 "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)"
13358 ))),
13359 }
13360}
13361
13362fn effective_frame(
13369 frame: Option<&WindowFrame>,
13370 ordered: bool,
13371) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13372 match frame {
13373 None => {
13374 if ordered {
13375 Ok((
13376 FrameKind::Range,
13377 FrameBound::UnboundedPreceding,
13378 FrameBound::CurrentRow,
13379 ))
13380 } else {
13381 Ok((
13382 FrameKind::Rows,
13383 FrameBound::UnboundedPreceding,
13384 FrameBound::UnboundedFollowing,
13385 ))
13386 }
13387 }
13388 Some(fr) => {
13389 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13390 if matches!(fr.start, FrameBound::UnboundedFollowing)
13392 || matches!(end, FrameBound::UnboundedPreceding)
13393 {
13394 return Err(EngineError::Unsupported(alloc::format!(
13395 "invalid frame: start={:?} end={:?}",
13396 fr.start,
13397 end
13398 )));
13399 }
13400 if fr.kind == FrameKind::Range
13405 && (matches!(
13406 fr.start,
13407 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13408 ) || matches!(
13409 end,
13410 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13411 ))
13412 {
13413 return Err(EngineError::Unsupported(
13414 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13415 ));
13416 }
13417 Ok((fr.kind, fr.start.clone(), end))
13418 }
13419 }
13420}
13421
13422#[allow(clippy::type_complexity)]
13426fn frame_bounds_for_row(
13427 eff: &(FrameKind, FrameBound, FrameBound),
13428 i: usize,
13429 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13430) -> (usize, usize) {
13431 let (kind, start, end) = eff;
13432 let n = slice.len();
13433 let last = n.saturating_sub(1);
13434 let (mut lo, mut hi) = match kind {
13435 FrameKind::Rows => {
13436 let lo = match start {
13437 FrameBound::UnboundedPreceding => 0,
13438 FrameBound::OffsetPreceding(k) => {
13439 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13440 i.saturating_sub(k)
13441 }
13442 FrameBound::CurrentRow => i,
13443 FrameBound::OffsetFollowing(k) => {
13444 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13445 i.saturating_add(k).min(last)
13446 }
13447 FrameBound::UnboundedFollowing => last,
13448 };
13449 let hi = match end {
13450 FrameBound::UnboundedPreceding => 0,
13451 FrameBound::OffsetPreceding(k) => {
13452 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13453 i.saturating_sub(k)
13454 }
13455 FrameBound::CurrentRow => i,
13456 FrameBound::OffsetFollowing(k) => {
13457 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13458 i.saturating_add(k).min(last)
13459 }
13460 FrameBound::UnboundedFollowing => last,
13461 };
13462 (lo, hi)
13463 }
13464 FrameKind::Range => {
13465 let lo = match start {
13471 FrameBound::UnboundedPreceding => 0,
13472 FrameBound::CurrentRow => peer_group_start(slice, i),
13473 FrameBound::UnboundedFollowing => last,
13474 _ => unreachable!("offset bounds rejected for RANGE"),
13475 };
13476 let hi = match end {
13477 FrameBound::UnboundedPreceding => 0,
13478 FrameBound::CurrentRow => peer_group_end(slice, i),
13479 FrameBound::UnboundedFollowing => last,
13480 _ => unreachable!("offset bounds rejected for RANGE"),
13481 };
13482 (lo, hi)
13483 }
13484 };
13485 if hi >= n {
13486 hi = last;
13487 }
13488 if lo >= n {
13489 lo = last;
13490 }
13491 (lo, hi)
13492}
13493
13494#[allow(clippy::type_complexity)]
13498fn peer_group_start(
13499 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13500 i: usize,
13501) -> usize {
13502 let key = &slice[i].1;
13503 let mut j = i;
13504 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13505 j -= 1;
13506 }
13507 j
13508}
13509
13510#[allow(clippy::type_complexity)]
13513fn peer_group_end(
13514 slice: &[(Vec<Value>, Vec<(Value, bool, Option<bool>)>, usize)],
13515 i: usize,
13516) -> usize {
13517 let key = &slice[i].1;
13518 let mut j = i;
13519 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13520 j += 1;
13521 }
13522 j
13523}
13524
13525fn value_to_f64(v: &Value) -> Option<f64> {
13526 match v {
13527 Value::SmallInt(n) => Some(f64::from(*n)),
13528 Value::Int(n) => Some(f64::from(*n)),
13529 #[allow(clippy::cast_precision_loss)]
13530 Value::BigInt(n) => Some(*n as f64),
13531 Value::Float(x) => Some(*x),
13532 _ => None,
13533 }
13534}
13535
13536fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13540 let mut any = false;
13541 for item in &stmt.items {
13542 if let SelectItem::Expr { expr, .. } = item {
13543 any = any || expr_has_subquery(expr);
13544 }
13545 }
13546 if let Some(w) = &stmt.where_ {
13547 any = any || expr_has_subquery(w);
13548 }
13549 if let Some(h) = &stmt.having {
13550 any = any || expr_has_subquery(h);
13551 }
13552 for o in &stmt.order_by {
13553 any = any || expr_has_subquery(&o.expr);
13554 }
13555 for (_, peer) in &stmt.unions {
13556 any = any || expr_tree_has_subquery(peer);
13557 }
13558 any
13559}
13560
13561fn expr_has_subquery(e: &Expr) -> bool {
13562 match e {
13563 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13564 Expr::AggregateOrdered { call, order_by } => {
13565 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
13566 }
13567 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13568 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13569 expr_has_subquery(expr)
13570 }
13571 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13572 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13573 Expr::Extract { source, .. } => expr_has_subquery(source),
13574 Expr::WindowFunction {
13575 args,
13576 partition_by,
13577 order_by,
13578 ..
13579 } => {
13580 args.iter().any(expr_has_subquery)
13581 || partition_by.iter().any(expr_has_subquery)
13582 || order_by.iter().any(|(e, _, _)| expr_has_subquery(e))
13583 }
13584 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13585 Expr::Array(items) => items.iter().any(expr_has_subquery),
13586 Expr::ArraySubscript { target, index } => {
13587 expr_has_subquery(target) || expr_has_subquery(index)
13588 }
13589 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13590 Expr::Case {
13591 operand,
13592 branches,
13593 else_branch,
13594 } => {
13595 operand.as_deref().is_some_and(expr_has_subquery)
13596 || branches
13597 .iter()
13598 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13599 || else_branch.as_deref().is_some_and(expr_has_subquery)
13600 }
13601 }
13602}
13603
13604fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13611 let lit = match v {
13612 Value::Null => Literal::Null,
13613 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13614 Value::Int(n) => Literal::Integer(i64::from(n)),
13615 Value::BigInt(n) => Literal::Integer(n),
13616 Value::Float(x) => Literal::Float(x),
13617 Value::Text(s) | Value::Json(s) => Literal::String(s),
13618 Value::Bool(b) => Literal::Bool(b),
13619 other => {
13620 return Err(EngineError::Unsupported(alloc::format!(
13621 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13622 other.data_type()
13623 )));
13624 }
13625 };
13626 Ok(Expr::Literal(lit))
13627}
13628
13629fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13635 let lit = match v {
13636 Value::Null => Literal::Null,
13637 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13638 Value::Int(n) => Literal::Integer(i64::from(n)),
13639 Value::BigInt(n) => Literal::Integer(n),
13640 Value::Float(x) => Literal::Float(x),
13641 Value::Text(s) | Value::Json(s) => Literal::String(s),
13642 Value::Bool(b) => Literal::Bool(b),
13643 Value::Vector(xs) => Literal::Vector(xs),
13644 Value::Date(days) => {
13648 let micros = (i64::from(days)) * 86_400_000_000;
13649 Literal::String(format_timestamp_micros_as_date(micros))
13650 }
13651 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13652 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13653 other => {
13654 return Err(EngineError::Unsupported(alloc::format!(
13655 "INSERT … SELECT cannot materialise value of type {:?}; \
13656 add an explicit CAST in the inner SELECT",
13657 other.data_type()
13658 )));
13659 }
13660 };
13661 Ok(Expr::Literal(lit))
13662}
13663
13664fn format_timestamp_micros(us: i64) -> String {
13665 let days = us.div_euclid(86_400_000_000);
13667 let intra_day = us.rem_euclid(86_400_000_000);
13668 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13669 let secs = intra_day / 1_000_000;
13670 let us_rem = intra_day % 1_000_000;
13671 let h = (secs / 3600) % 24;
13672 let m = (secs / 60) % 60;
13673 let s = secs % 60;
13674 if us_rem == 0 {
13675 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13676 } else {
13677 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13678 }
13679}
13680
13681fn format_timestamp_micros_as_date(us: i64) -> String {
13682 let days = us.div_euclid(86_400_000_000);
13685 let jdn = days + 2_440_588;
13687 let (y, mo, d) = jdn_to_ymd(jdn);
13688 alloc::format!("{y:04}-{mo:02}-{d:02}")
13689}
13690
13691fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13692 let l = jdn + 68569;
13694 let n = (4 * l) / 146_097;
13695 let l = l - (146_097 * n + 3) / 4;
13696 let i = (4000 * (l + 1)) / 1_461_001;
13697 let l = l - (1461 * i) / 4 + 31;
13698 let j = (80 * l) / 2447;
13699 let day = (l - (2447 * j) / 80) as u32;
13700 let l = j / 11;
13701 let month = (j + 2 - 12 * l) as u32;
13702 let year = 100 * (n - 49) + i + l;
13703 (year, month, day)
13704}
13705
13706fn format_numeric(scaled: i128, scale: u8) -> String {
13707 if scale == 0 {
13708 return alloc::format!("{scaled}");
13709 }
13710 let abs = scaled.unsigned_abs();
13711 let divisor = 10u128.pow(u32::from(scale));
13712 let whole = abs / divisor;
13713 let frac = abs % divisor;
13714 let sign = if scaled < 0 { "-" } else { "" };
13715 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13716}
13717
13718fn rewrite_column_in_source(
13742 src: &str,
13743 old: &str,
13744 new: &str,
13745) -> Result<alloc::string::String, EngineError> {
13746 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13747 EngineError::Unsupported(alloc::format!(
13748 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13749 failed to parse for rewrite ({e})"
13750 ))
13751 })?;
13752 rewrite_column_in_expr(&mut expr, old, new);
13753 Ok(alloc::format!("{expr}"))
13754}
13755
13756fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13764 match e {
13765 Expr::AggregateOrdered { call, order_by } => {
13766 rewrite_column_in_expr(call, old, new);
13767 for o in order_by.iter_mut() {
13768 rewrite_column_in_expr(&mut o.expr, old, new);
13769 }
13770 }
13771 Expr::Column(c) => {
13772 if c.name.eq_ignore_ascii_case(old) {
13773 c.name = new.to_string();
13774 }
13775 }
13776 Expr::Binary { lhs, rhs, .. } => {
13777 rewrite_column_in_expr(lhs, old, new);
13778 rewrite_column_in_expr(rhs, old, new);
13779 }
13780 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13781 rewrite_column_in_expr(expr, old, new);
13782 }
13783 Expr::FunctionCall { args, .. } => {
13784 for a in args {
13785 rewrite_column_in_expr(a, old, new);
13786 }
13787 }
13788 Expr::Like { expr, pattern, .. } => {
13789 rewrite_column_in_expr(expr, old, new);
13790 rewrite_column_in_expr(pattern, old, new);
13791 }
13792 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13793 Expr::WindowFunction {
13794 args,
13795 partition_by,
13796 order_by,
13797 ..
13798 } => {
13799 for a in args {
13800 rewrite_column_in_expr(a, old, new);
13801 }
13802 for p in partition_by {
13803 rewrite_column_in_expr(p, old, new);
13804 }
13805 for (o, _, _) in order_by {
13806 rewrite_column_in_expr(o, old, new);
13807 }
13808 }
13809 Expr::Array(items) => {
13810 for elem in items {
13811 rewrite_column_in_expr(elem, old, new);
13812 }
13813 }
13814 Expr::ArraySubscript { target, index } => {
13815 rewrite_column_in_expr(target, old, new);
13816 rewrite_column_in_expr(index, old, new);
13817 }
13818 Expr::AnyAll { expr, array, .. } => {
13819 rewrite_column_in_expr(expr, old, new);
13820 rewrite_column_in_expr(array, old, new);
13821 }
13822 Expr::Case {
13823 operand,
13824 branches,
13825 else_branch,
13826 } => {
13827 if let Some(o) = operand {
13828 rewrite_column_in_expr(o, old, new);
13829 }
13830 for (w, t) in branches {
13831 rewrite_column_in_expr(w, old, new);
13832 rewrite_column_in_expr(t, old, new);
13833 }
13834 if let Some(e) = else_branch {
13835 rewrite_column_in_expr(e, old, new);
13836 }
13837 }
13838 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13842 Expr::Literal(_) | Expr::Placeholder(_) => {}
13843 }
13844}
13845
13846pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13854 match stmt {
13855 Statement::Select(s) => substitute_select(s, params)?,
13856 Statement::Insert(ins) => {
13857 for row in &mut ins.rows {
13858 for e in row {
13859 substitute_expr(e, params)?;
13860 }
13861 }
13862 if let Some(clause) = &mut ins.on_conflict
13866 && let spg_sql::ast::OnConflictAction::Update {
13867 assignments,
13868 where_,
13869 } = &mut clause.action
13870 {
13871 for (_, e) in assignments.iter_mut() {
13872 substitute_expr(e, params)?;
13873 }
13874 if let Some(w) = where_ {
13875 substitute_expr(w, params)?;
13876 }
13877 }
13878 }
13879 Statement::Update(u) => {
13880 for (_, e) in &mut u.assignments {
13881 substitute_expr(e, params)?;
13882 }
13883 if let Some(w) = &mut u.where_ {
13884 substitute_expr(w, params)?;
13885 }
13886 }
13887 Statement::Delete(d) => {
13888 if let Some(w) = &mut d.where_ {
13889 substitute_expr(w, params)?;
13890 }
13891 }
13892 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13893 _ => {}
13896 }
13897 Ok(())
13898}
13899
13900fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13901 for item in &mut s.items {
13902 if let SelectItem::Expr { expr, .. } = item {
13903 substitute_expr(expr, params)?;
13904 }
13905 }
13906 if let Some(w) = &mut s.where_ {
13907 substitute_expr(w, params)?;
13908 }
13909 if let Some(gs) = &mut s.group_by {
13910 for g in gs {
13911 substitute_expr(g, params)?;
13912 }
13913 }
13914 if let Some(h) = &mut s.having {
13915 substitute_expr(h, params)?;
13916 }
13917 for o in &mut s.order_by {
13918 substitute_expr(&mut o.expr, params)?;
13919 }
13920 for (_, peer) in &mut s.unions {
13921 substitute_select(peer, params)?;
13922 }
13923 if let Some(le) = s.limit {
13928 s.limit = Some(resolve_limit_placeholder(le, params)?);
13929 }
13930 if let Some(le) = s.offset {
13931 s.offset = Some(resolve_limit_placeholder(le, params)?);
13932 }
13933 Ok(())
13934}
13935
13936fn resolve_limit_placeholder(
13937 le: spg_sql::ast::LimitExpr,
13938 params: &[Value],
13939) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13940 use spg_sql::ast::LimitExpr;
13941 match le {
13942 LimitExpr::Literal(_) => Ok(le),
13943 LimitExpr::Placeholder(n) => {
13944 let idx = usize::from(n).saturating_sub(1);
13945 let v = params.get(idx).ok_or_else(|| {
13946 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13947 n,
13948 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13949 })
13950 })?;
13951 let int = match v {
13952 Value::SmallInt(x) => Some(i64::from(*x)),
13953 Value::Int(x) => Some(i64::from(*x)),
13954 Value::BigInt(x) => Some(*x),
13955 _ => None,
13956 }
13957 .ok_or_else(|| {
13958 EngineError::Unsupported(alloc::format!(
13959 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13960 ))
13961 })?;
13962 if int < 0 {
13963 return Err(EngineError::Unsupported(alloc::format!(
13964 "LIMIT/OFFSET ${n} bound to negative value {int}"
13965 )));
13966 }
13967 let bounded = u32::try_from(int).map_err(|_| {
13968 EngineError::Unsupported(alloc::format!(
13969 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13970 ))
13971 })?;
13972 Ok(LimitExpr::Literal(bounded))
13973 }
13974 }
13975}
13976
13977fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13978 if let Expr::Placeholder(n) = e {
13979 let idx = usize::from(*n).saturating_sub(1);
13980 let v = params.get(idx).ok_or_else(|| {
13981 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13982 n: *n,
13983 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13984 })
13985 })?;
13986 *e = Expr::Literal(value_to_literal(v.clone()));
13987 return Ok(());
13988 }
13989 match e {
13990 Expr::AggregateOrdered { call, order_by } => {
13991 substitute_expr(call, params)?;
13992 for o in order_by.iter_mut() {
13993 substitute_expr(&mut o.expr, params)?;
13994 }
13995 }
13996 Expr::Binary { lhs, rhs, .. } => {
13997 substitute_expr(lhs, params)?;
13998 substitute_expr(rhs, params)?;
13999 }
14000 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14001 substitute_expr(expr, params)?;
14002 }
14003 Expr::FunctionCall { args, .. } => {
14004 for a in args {
14005 substitute_expr(a, params)?;
14006 }
14007 }
14008 Expr::Like { expr, pattern, .. } => {
14009 substitute_expr(expr, params)?;
14010 substitute_expr(pattern, params)?;
14011 }
14012 Expr::Extract { source, .. } => substitute_expr(source, params)?,
14013 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
14014 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
14015 Expr::InSubquery { expr, subquery, .. } => {
14016 substitute_expr(expr, params)?;
14017 substitute_select(subquery, params)?;
14018 }
14019 Expr::WindowFunction {
14020 args,
14021 partition_by,
14022 order_by,
14023 ..
14024 } => {
14025 for a in args {
14026 substitute_expr(a, params)?;
14027 }
14028 for p in partition_by {
14029 substitute_expr(p, params)?;
14030 }
14031 for (e, _, _) in order_by {
14032 substitute_expr(e, params)?;
14033 }
14034 }
14035 Expr::Literal(_) | Expr::Column(_) => {}
14036 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
14038 Expr::Array(items) => {
14039 for elem in items {
14040 substitute_expr(elem, params)?;
14041 }
14042 }
14043 Expr::ArraySubscript { target, index } => {
14044 substitute_expr(target, params)?;
14045 substitute_expr(index, params)?;
14046 }
14047 Expr::AnyAll { expr, array, .. } => {
14048 substitute_expr(expr, params)?;
14049 substitute_expr(array, params)?;
14050 }
14051 Expr::Case {
14052 operand,
14053 branches,
14054 else_branch,
14055 } => {
14056 if let Some(o) = operand {
14057 substitute_expr(o, params)?;
14058 }
14059 for (w, t) in branches {
14060 substitute_expr(w, params)?;
14061 substitute_expr(t, params)?;
14062 }
14063 if let Some(e) = else_branch {
14064 substitute_expr(e, params)?;
14065 }
14066 }
14067 }
14068 Ok(())
14069}
14070
14071fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
14089 use core::cmp::Ordering;
14090 match (a, b) {
14091 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
14092 (Value::Int(a), Value::Int(b)) => a.cmp(b),
14093 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
14094 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
14095 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
14096 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14097 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
14098 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14099 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
14100 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14101 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
14102 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
14103 (Value::Date(a), Value::Date(b)) => a.cmp(b),
14104 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
14105 (Value::SmallInt(n), Value::Float(x)) => {
14107 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14108 }
14109 (Value::Float(x), Value::SmallInt(n)) => {
14110 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14111 }
14112 (Value::Int(n), Value::Float(x)) => {
14113 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14114 }
14115 (Value::Float(x), Value::Int(n)) => {
14116 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14117 }
14118 (Value::BigInt(n), Value::Float(x)) => {
14119 #[allow(clippy::cast_precision_loss)]
14120 let nf = *n as f64;
14121 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
14122 }
14123 (Value::Float(x), Value::BigInt(n)) => {
14124 #[allow(clippy::cast_precision_loss)]
14125 let nf = *n as f64;
14126 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
14127 }
14128 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
14131 }
14132}
14133
14134fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
14141 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
14142 out.push('[');
14143 for (i, b) in bounds.iter().enumerate() {
14144 if i > 0 {
14145 out.push_str(", ");
14146 }
14147 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
14148 if needs_quote {
14149 out.push('"');
14150 for ch in b.chars() {
14151 if ch == '"' || ch == '\\' {
14152 out.push('\\');
14153 }
14154 out.push(ch);
14155 }
14156 out.push('"');
14157 } else {
14158 out.push_str(b);
14159 }
14160 }
14161 out.push(']');
14162 out
14163}
14164
14165pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
14175 match v {
14176 Value::Null => "NULL".to_string(),
14177 Value::SmallInt(n) => alloc::format!("{n}"),
14178 Value::Int(n) => alloc::format!("{n}"),
14179 Value::BigInt(n) => alloc::format!("{n}"),
14180 Value::Float(x) => alloc::format!("{x:?}"),
14181 Value::Text(s) | Value::Json(s) => s.clone(),
14182 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
14183 Value::Date(d) => eval::format_date(*d),
14184 Value::Timestamp(t) => eval::format_timestamp(*t),
14185 Value::Time(us) => eval::format_time(*us),
14187 Value::Year(y) => alloc::format!("{y:04}"),
14189 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
14191 Value::Money(c) => eval::format_money(*c),
14193 v @ Value::Range { .. } => format_range_str(v),
14195 Value::Hstore(pairs) => format_hstore_str(pairs),
14197 Value::IntArray2D(rows) => format_int_2d_text(rows),
14199 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
14200 Value::TextArray2D(rows) => format_text_2d_text(rows),
14201 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
14202 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
14203 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
14204 alloc::format!("{v:?}")
14208 }
14209 _ => alloc::format!("{v:?}"),
14213 }
14214}
14215
14216const fn is_internal_table_name(_name: &str) -> bool {
14223 false
14224}
14225
14226fn value_to_literal(v: Value) -> Literal {
14227 match v {
14228 Value::Null => Literal::Null,
14229 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14230 Value::Int(n) => Literal::Integer(i64::from(n)),
14231 Value::BigInt(n) => Literal::Integer(n),
14232 Value::Float(x) => Literal::Float(x),
14233 Value::Text(s) | Value::Json(s) => Literal::String(s),
14234 Value::Bool(b) => Literal::Bool(b),
14235 Value::Vector(v) => Literal::Vector(v),
14236 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
14237 Value::Date(d) => Literal::String(eval::format_date(d)),
14238 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14239 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14245 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14250 Value::TextArray(items) => Literal::TextArray(items),
14255 Value::IntArray(items) => Literal::IntArray(items),
14256 Value::BigIntArray(items) => Literal::BigIntArray(items),
14257 Value::Interval { months, micros } => Literal::Interval {
14258 months,
14259 micros,
14260 text: eval::format_interval(months, micros),
14261 },
14262 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14265 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14266 v => Literal::String(alloc::format!("{v:?}")),
14270 }
14271}
14272
14273fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14274 let Some(now) = now_micros else {
14275 return;
14276 };
14277 match stmt {
14278 Statement::Select(s) => rewrite_select_clock(s, now),
14279 Statement::Insert(ins) => {
14280 for row in &mut ins.rows {
14281 for e in row {
14282 rewrite_expr_clock(e, now);
14283 }
14284 }
14285 if let Some(clause) = &mut ins.on_conflict
14289 && let spg_sql::ast::OnConflictAction::Update {
14290 assignments,
14291 where_,
14292 } = &mut clause.action
14293 {
14294 for (_, e) in assignments.iter_mut() {
14295 rewrite_expr_clock(e, now);
14296 }
14297 if let Some(w) = where_ {
14298 rewrite_expr_clock(w, now);
14299 }
14300 }
14301 }
14302 Statement::Update(u) => {
14306 for (_, e) in &mut u.assignments {
14307 rewrite_expr_clock(e, now);
14308 }
14309 if let Some(w) = &mut u.where_ {
14310 rewrite_expr_clock(w, now);
14311 }
14312 }
14313 Statement::Delete(d) => {
14314 if let Some(w) = &mut d.where_ {
14315 rewrite_expr_clock(w, now);
14316 }
14317 }
14318 _ => {}
14319 }
14320}
14321
14322fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14323 for item in &mut s.items {
14324 if let SelectItem::Expr { expr, .. } = item {
14325 rewrite_expr_clock(expr, now);
14326 }
14327 }
14328 if let Some(w) = &mut s.where_ {
14329 rewrite_expr_clock(w, now);
14330 }
14331 if let Some(gs) = &mut s.group_by {
14332 for g in gs {
14333 rewrite_expr_clock(g, now);
14334 }
14335 }
14336 if let Some(h) = &mut s.having {
14337 rewrite_expr_clock(h, now);
14338 }
14339 for o in &mut s.order_by {
14340 rewrite_expr_clock(&mut o.expr, now);
14341 }
14342 for (_, peer) in &mut s.unions {
14343 rewrite_select_clock(peer, now);
14344 }
14345}
14346
14347fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14355 if let Some(replacement) = clock_replacement_for(e, now) {
14359 *e = replacement;
14360 return;
14361 }
14362 match e {
14363 Expr::AggregateOrdered { call, order_by } => {
14364 rewrite_expr_clock(call, now);
14365 for o in order_by.iter_mut() {
14366 rewrite_expr_clock(&mut o.expr, now);
14367 }
14368 }
14369 Expr::Binary { lhs, rhs, .. } => {
14370 rewrite_expr_clock(lhs, now);
14371 rewrite_expr_clock(rhs, now);
14372 }
14373 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14374 rewrite_expr_clock(expr, now);
14375 }
14376 Expr::FunctionCall { args, .. } => {
14377 for a in args {
14378 rewrite_expr_clock(a, now);
14379 }
14380 }
14381 Expr::Like { expr, pattern, .. } => {
14382 rewrite_expr_clock(expr, now);
14383 rewrite_expr_clock(pattern, now);
14384 }
14385 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14386 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14390 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14391 Expr::InSubquery { expr, subquery, .. } => {
14392 rewrite_expr_clock(expr, now);
14393 rewrite_select_clock(subquery, now);
14394 }
14395 Expr::WindowFunction {
14398 args,
14399 partition_by,
14400 order_by,
14401 ..
14402 } => {
14403 for a in args {
14404 rewrite_expr_clock(a, now);
14405 }
14406 for p in partition_by {
14407 rewrite_expr_clock(p, now);
14408 }
14409 for (e, _, _) in order_by {
14410 rewrite_expr_clock(e, now);
14411 }
14412 }
14413 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14414 Expr::Array(items) => {
14415 for elem in items {
14416 rewrite_expr_clock(elem, now);
14417 }
14418 }
14419 Expr::ArraySubscript { target, index } => {
14420 rewrite_expr_clock(target, now);
14421 rewrite_expr_clock(index, now);
14422 }
14423 Expr::AnyAll { expr, array, .. } => {
14424 rewrite_expr_clock(expr, now);
14425 rewrite_expr_clock(array, now);
14426 }
14427 Expr::Case {
14428 operand,
14429 branches,
14430 else_branch,
14431 } => {
14432 if let Some(o) = operand {
14433 rewrite_expr_clock(o, now);
14434 }
14435 for (w, t) in branches {
14436 rewrite_expr_clock(w, now);
14437 rewrite_expr_clock(t, now);
14438 }
14439 if let Some(e) = else_branch {
14440 rewrite_expr_clock(e, now);
14441 }
14442 }
14443 }
14444}
14445
14446fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14453 let (kind, name) = match e {
14454 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14455 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14456 _ => return None,
14457 };
14458 enum ClockShape {
14466 Timestamp,
14467 Date,
14468 UnixSeconds,
14469 }
14470 let shape = match name.len() {
14471 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14472 Some(ClockShape::Timestamp)
14473 }
14474 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14475 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14476 Some(ClockShape::UnixSeconds)
14477 }
14478 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14479 _ => None,
14480 };
14481 let shape = shape?;
14482 let payload = match shape {
14483 ClockShape::Timestamp => now,
14484 ClockShape::Date => now.div_euclid(86_400_000_000),
14485 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14486 };
14487 let target = match shape {
14488 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14489 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14490 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14491 };
14492 Some(Expr::Cast {
14493 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14494 target,
14495 })
14496}
14497
14498#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14499enum ClockSite {
14500 Fn,
14501 BareIdent,
14502}
14503
14504fn expand_group_by_all(s: &mut SelectStatement) {
14515 if !s.group_by_all {
14516 for (_, peer) in &mut s.unions {
14517 expand_group_by_all(peer);
14518 }
14519 return;
14520 }
14521 let mut groups: Vec<Expr> = Vec::new();
14522 for item in &s.items {
14523 if let SelectItem::Expr { expr, .. } = item
14524 && !aggregate::contains_aggregate(expr)
14525 {
14526 groups.push(expr.clone());
14527 }
14528 }
14529 s.group_by = Some(groups);
14530 s.group_by_all = false;
14531 for (_, peer) in &mut s.unions {
14532 expand_group_by_all(peer);
14533 }
14534}
14535
14536fn resolve_order_by_position(s: &mut SelectStatement) {
14537 for order in &mut s.order_by {
14542 match &order.expr {
14543 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14544 if let Ok(idx_one_based) = usize::try_from(*n) {
14545 let idx = idx_one_based - 1;
14546 if idx < s.items.len()
14547 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14548 {
14549 order.expr = expr.clone();
14550 }
14551 }
14552 }
14553 Expr::Column(c) if c.qualifier.is_none() => {
14554 for item in &s.items {
14556 if let SelectItem::Expr {
14557 expr,
14558 alias: Some(a),
14559 } = item
14560 && a == &c.name
14561 {
14562 order.expr = expr.clone();
14563 break;
14564 }
14565 }
14566 }
14567 _ => {}
14568 }
14569 }
14570 for (_, peer) in &mut s.unions {
14571 resolve_order_by_position(peer);
14572 }
14573}
14574
14575fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14588 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14589 match keep {
14590 Some(k) if k < tagged.len() && k > 0 => {
14591 let pivot = k - 1;
14592 tagged.select_nth_unstable_by(pivot, cmp);
14593 tagged[..k].sort_by(cmp);
14594 tagged.truncate(k);
14595 }
14596 _ => {
14597 tagged.sort_by(cmp);
14598 }
14599 }
14600}
14601
14602fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14603 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14604}
14605
14606fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14610 use core::cmp::Ordering;
14611 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14612 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14613 let ord = if descs.get(i).copied().unwrap_or(false) {
14614 ord.reverse()
14615 } else {
14616 ord
14617 };
14618 if ord != Ordering::Equal {
14619 return ord;
14620 }
14621 }
14622 Ordering::Equal
14623}
14624
14625fn build_order_keys(
14628 order_by: &[OrderBy],
14629 row: &Row,
14630 ctx: &EvalContext,
14631) -> Result<Vec<f64>, EngineError> {
14632 let mut keys = Vec::with_capacity(order_by.len());
14633 for o in order_by {
14634 let v = eval::eval_expr(&o.expr, row, ctx)?;
14635 if matches!(v, Value::Null) {
14642 let nf = o.nulls_first.unwrap_or(o.desc);
14643 keys.push(if nf == o.desc {
14644 f64::INFINITY
14645 } else {
14646 f64::NEG_INFINITY
14647 });
14648 } else {
14649 keys.push(value_to_order_key(&v)?);
14650 }
14651 }
14652 Ok(keys)
14653}
14654
14655fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14659 if let Some(off) = offset {
14660 let off = off as usize;
14661 if off >= rows.len() {
14662 rows.clear();
14663 } else {
14664 rows.drain(..off);
14665 }
14666 }
14667 if let Some(n) = limit {
14668 rows.truncate(n as usize);
14669 }
14670}
14671
14672fn apply_offset_and_limit_tagged(
14683 tagged: &mut Vec<(Vec<f64>, Row)>,
14684 offset: Option<u32>,
14685 limit: Option<u32>,
14686 with_ties: bool,
14687) {
14688 if let Some(off) = offset {
14689 let off = off as usize;
14690 if off >= tagged.len() {
14691 tagged.clear();
14692 } else {
14693 tagged.drain(..off);
14694 }
14695 }
14696 if let Some(n) = limit {
14697 let n = n as usize;
14698 if with_ties && n > 0 && n < tagged.len() {
14699 let cutoff_key = tagged[n - 1].0.clone();
14700 let mut end = n;
14701 while end < tagged.len() && tagged[end].0 == cutoff_key {
14702 end += 1;
14703 }
14704 tagged.truncate(end);
14705 } else {
14706 tagged.truncate(n);
14707 }
14708 }
14709}
14710
14711fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14717 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14718 return Err(EngineError::Unsupported(alloc::string::String::from(
14719 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14720 )));
14721 }
14722 Ok(())
14723}
14724
14725fn resolve_foreign_key(
14739 local_table_name: &str,
14740 local_cols: &[ColumnSchema],
14741 fk: spg_sql::ast::ForeignKeyConstraint,
14742 catalog: &Catalog,
14743) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14744 let mut local_columns = Vec::with_capacity(fk.columns.len());
14746 for name in &fk.columns {
14747 let pos = local_cols
14748 .iter()
14749 .position(|c| c.name == *name)
14750 .ok_or_else(|| {
14751 EngineError::Unsupported(alloc::format!(
14752 "FOREIGN KEY references unknown local column {name:?}"
14753 ))
14754 })?;
14755 local_columns.push(pos);
14756 }
14757 let is_self_ref = fk.parent_table == local_table_name;
14761 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14762 (local_cols, local_table_name)
14763 } else {
14764 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14765 EngineError::Storage(StorageError::TableNotFound {
14766 name: fk.parent_table.clone(),
14767 })
14768 })?;
14769 (
14770 parent_table.schema().columns.as_slice(),
14771 fk.parent_table.as_str(),
14772 )
14773 };
14774 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14779 if fk.columns.len() != 1 {
14780 return Err(EngineError::Unsupported(
14781 "composite FOREIGN KEY without explicit parent column list is not supported \
14782 — list the parent columns explicitly"
14783 .into(),
14784 ));
14785 }
14786 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14788 .ok_or_else(|| {
14789 EngineError::Unsupported(alloc::format!(
14790 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14791 to default the FOREIGN KEY against"
14792 ))
14793 })?;
14794 alloc::vec![pos]
14795 } else {
14796 let mut out = Vec::with_capacity(fk.parent_columns.len());
14797 for name in &fk.parent_columns {
14798 let pos = parent_cols_for_lookup
14799 .iter()
14800 .position(|c| c.name == *name)
14801 .ok_or_else(|| {
14802 EngineError::Unsupported(alloc::format!(
14803 "FOREIGN KEY references unknown parent column \
14804 {name:?} on table {parent_table_str:?}"
14805 ))
14806 })?;
14807 out.push(pos);
14808 }
14809 out
14810 };
14811 if parent_columns.len() != local_columns.len() {
14812 return Err(EngineError::Unsupported(alloc::format!(
14813 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14814 local_columns.len(),
14815 parent_columns.len()
14816 )));
14817 }
14818 if !is_self_ref {
14828 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14829 let primary_parent_col = parent_columns[0];
14830 let has_btree = parent_table
14831 .schema()
14832 .columns
14833 .get(primary_parent_col)
14834 .is_some()
14835 && parent_table.indices().iter().any(|idx| {
14836 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14837 && idx.column_position == primary_parent_col
14838 && idx.partial_predicate.is_none()
14839 });
14840 if !has_btree {
14841 return Err(EngineError::Unsupported(alloc::format!(
14842 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14843 index — create one with `CREATE INDEX ... ON {} ({})` first",
14844 parent_table_str,
14845 parent_table_str,
14846 parent_table.schema().columns[primary_parent_col].name,
14847 )));
14848 }
14849 }
14850 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14851 let on_update = fk_action_sql_to_storage(fk.on_update);
14852 Ok(spg_storage::ForeignKeyConstraint {
14853 name: fk.name,
14854 local_columns,
14855 parent_table: fk.parent_table,
14856 parent_columns,
14857 on_delete,
14858 on_update,
14859 })
14860}
14861
14862fn pick_pk_index_column(
14868 catalog: &Catalog,
14869 parent_name: &str,
14870 is_self_ref: bool,
14871 local_cols: &[ColumnSchema],
14872) -> Option<usize> {
14873 if is_self_ref {
14874 let _ = local_cols;
14878 return Some(0);
14879 }
14880 let parent = catalog.get(parent_name)?;
14881 parent.indices().iter().find_map(|idx| {
14882 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14883 && idx.partial_predicate.is_none()
14884 && idx.included_columns.is_empty()
14885 && idx.expression.is_none()
14886 {
14887 Some(idx.column_position)
14888 } else {
14889 None
14890 }
14891 })
14892}
14893
14894fn resolve_on_conflict_columns(
14901 catalog: &Catalog,
14902 table_name: &str,
14903 target: &[String],
14904) -> Result<Vec<usize>, EngineError> {
14905 let table = catalog.get(table_name).ok_or_else(|| {
14906 EngineError::Storage(StorageError::TableNotFound {
14907 name: table_name.into(),
14908 })
14909 })?;
14910 if target.is_empty() {
14911 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14921 return Ok(uc.columns.clone());
14922 }
14923 let pos = table
14924 .indices()
14925 .iter()
14926 .find_map(|idx| {
14927 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14928 && idx.partial_predicate.is_none()
14929 && idx.included_columns.is_empty()
14930 && idx.expression.is_none()
14931 {
14932 Some(idx.column_position)
14933 } else {
14934 None
14935 }
14936 })
14937 .ok_or_else(|| {
14938 EngineError::Unsupported(alloc::format!(
14939 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14940 ))
14941 })?;
14942 return Ok(alloc::vec![pos]);
14943 }
14944 let mut out = Vec::with_capacity(target.len());
14945 for name in target {
14946 let pos = table
14947 .schema()
14948 .columns
14949 .iter()
14950 .position(|c| c.name == *name)
14951 .ok_or_else(|| {
14952 EngineError::Unsupported(alloc::format!(
14953 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14954 ))
14955 })?;
14956 out.push(pos);
14957 }
14958 Ok(out)
14959}
14960
14961fn on_conflict_key_exists(
14964 catalog: &Catalog,
14965 table_name: &str,
14966 column_pos: usize,
14967 key: &Value,
14968) -> bool {
14969 let Some(table) = catalog.get(table_name) else {
14970 return false;
14971 };
14972 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14973 return false;
14974 };
14975 table.indices().iter().any(|idx| {
14976 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14977 && idx.column_position == column_pos
14978 && idx.partial_predicate.is_none()
14979 && !idx.lookup_eq(&idx_key).is_empty()
14980 })
14981}
14982
14983fn lookup_row_position_by_keys(
14989 catalog: &Catalog,
14990 table_name: &str,
14991 column_positions: &[usize],
14992 key: &[&Value],
14993) -> Option<usize> {
14994 let table = catalog.get(table_name)?;
14995 table.rows().iter().position(|r| {
14996 column_positions
14997 .iter()
14998 .enumerate()
14999 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15000 })
15001}
15002
15003fn on_conflict_keys_exist(
15008 catalog: &Catalog,
15009 table_name: &str,
15010 column_positions: &[usize],
15011 key: &[&Value],
15012) -> bool {
15013 if column_positions.len() == 1 {
15014 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
15015 }
15016 let Some(table) = catalog.get(table_name) else {
15017 return false;
15018 };
15019 table.rows().iter().any(|r| {
15020 column_positions
15021 .iter()
15022 .enumerate()
15023 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15024 })
15025}
15026
15027fn apply_on_conflict_assignments(
15040 catalog: &Catalog,
15041 table_name: &str,
15042 target_pos: usize,
15043 incoming: &[Value],
15044 assignments: &[(String, Expr)],
15045 where_: Option<&Expr>,
15046) -> Result<Option<Vec<Value>>, EngineError> {
15047 let table = catalog.get(table_name).ok_or_else(|| {
15048 EngineError::Storage(StorageError::TableNotFound {
15049 name: table_name.into(),
15050 })
15051 })?;
15052 let schema_cols = table.schema().columns.clone();
15053 let existing = table
15054 .rows()
15055 .get(target_pos)
15056 .ok_or_else(|| {
15057 EngineError::Unsupported(alloc::format!(
15058 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
15059 ))
15060 })?
15061 .clone();
15062 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
15063 if let Some(w) = where_ {
15065 let pred = w.clone();
15066 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
15067 let v = eval::eval_expr(&pred, &existing, &ctx)?;
15068 if !matches!(v, Value::Bool(true)) {
15069 return Ok(None);
15070 }
15071 }
15072 let mut new_values = existing.values.clone();
15073 for (col_name, expr) in assignments {
15074 let target_idx = schema_cols
15075 .iter()
15076 .position(|c| c.name == *col_name)
15077 .ok_or_else(|| {
15078 EngineError::Eval(EvalError::ColumnNotFound {
15079 name: col_name.clone(),
15080 })
15081 })?;
15082 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
15083 let v = eval::eval_expr(&sub, &existing, &ctx)?;
15084 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
15085 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
15086 new_values[target_idx] = coerced;
15087 }
15088 Ok(Some(new_values))
15089}
15090
15091fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
15096 use spg_sql::ast::ColumnName;
15097 match expr {
15098 Expr::Column(ColumnName { qualifier, name })
15099 if qualifier
15100 .as_deref()
15101 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
15102 {
15103 let pos = schema_cols.iter().position(|c| c.name == name);
15104 match pos {
15105 Some(p) => {
15106 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
15107 value_to_literal_expr(v)
15108 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
15109 }
15110 None => Expr::Column(ColumnName { qualifier, name }),
15111 }
15112 }
15113 Expr::Binary { op, lhs, rhs } => Expr::Binary {
15114 op,
15115 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
15116 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
15117 },
15118 Expr::Unary { op, expr } => Expr::Unary {
15119 op,
15120 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
15121 },
15122 Expr::FunctionCall { name, args } => Expr::FunctionCall {
15123 name,
15124 args: args
15125 .into_iter()
15126 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
15127 .collect(),
15128 },
15129 other => other,
15130 }
15131}
15132
15133fn enforce_uniqueness_inserts(
15156 catalog: &Catalog,
15157 child_table: &str,
15158 constraints: &[spg_storage::UniquenessConstraint],
15159 rows: &[Vec<Value>],
15160) -> Result<(), EngineError> {
15161 if constraints.is_empty() {
15162 return Ok(());
15163 }
15164 let table = catalog.get(child_table).ok_or_else(|| {
15165 EngineError::Storage(StorageError::TableNotFound {
15166 name: child_table.into(),
15167 })
15168 })?;
15169 let schema = table.schema();
15170 for uc in constraints {
15171 for (batch_idx, row_values) in rows.iter().enumerate() {
15172 let key: Vec<Value> = uc
15181 .columns
15182 .iter()
15183 .map(|&i| collated_key_cell(&row_values[i], i, schema))
15184 .collect();
15185 let has_null = key.iter().any(|v| matches!(v, Value::Null));
15186 if has_null && !uc.nulls_not_distinct {
15191 continue;
15192 }
15193 let collides_in_table = table.rows().iter().any(|prow| {
15195 uc.columns.iter().enumerate().all(|(i, &p)| {
15196 prow.values
15197 .get(p)
15198 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15199 })
15200 });
15201 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
15203 uc.columns.iter().enumerate().all(|(i, &p)| {
15204 earlier
15205 .get(p)
15206 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15207 })
15208 });
15209 if collides_in_table || collides_in_batch {
15210 let kind = if uc.is_primary_key {
15211 "PRIMARY KEY"
15212 } else {
15213 "UNIQUE"
15214 };
15215 let col_names: Vec<String> = uc
15216 .columns
15217 .iter()
15218 .map(|&i| table.schema().columns[i].name.clone())
15219 .collect();
15220 return Err(EngineError::Unsupported(alloc::format!(
15221 "{kind} violation on {child_table:?} columns {col_names:?}: \
15222 row #{batch_idx} duplicates an existing key"
15223 )));
15224 }
15225 }
15226 }
15227 Ok(())
15228}
15229
15230fn collated_key_cell(
15237 v: &spg_storage::Value,
15238 column_position: usize,
15239 schema: &spg_storage::TableSchema,
15240) -> spg_storage::Value {
15241 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
15242 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
15243 spg_storage::Value::Text(s.to_ascii_lowercase())
15244 }
15245 _ => v.clone(),
15246 }
15247}
15248
15249fn predicate_truthy(v: &spg_storage::Value) -> bool {
15257 use spg_storage::Value as V;
15258 match v {
15259 V::Bool(b) => *b,
15260 V::Int(n) => *n != 0,
15261 V::BigInt(n) => *n != 0,
15262 V::SmallInt(n) => *n != 0,
15263 _ => false,
15264 }
15265}
15266
15267fn check_existing_unique_violation(
15272 idx: &spg_storage::Index,
15273 schema: &spg_storage::TableSchema,
15274 rows: &[spg_storage::Row],
15275) -> Result<(), EngineError> {
15276 let predicate_expr = match idx.partial_predicate.as_deref() {
15277 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15278 EngineError::Unsupported(alloc::format!(
15279 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15280 ))
15281 })?),
15282 None => None,
15283 };
15284 let ctx = eval::EvalContext::new(&schema.columns, None);
15285 let key_positions = unique_key_positions(idx);
15286 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15287 for row in rows {
15288 if let Some(expr) = &predicate_expr {
15289 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15290 EngineError::Unsupported(alloc::format!(
15291 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15292 ))
15293 })?;
15294 if !predicate_truthy(&v) {
15295 continue;
15296 }
15297 }
15298 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15299 .iter()
15300 .map(|&p| {
15301 let v = row
15302 .values
15303 .get(p)
15304 .cloned()
15305 .unwrap_or(spg_storage::Value::Null);
15306 collated_key_cell(&v, p, schema)
15307 })
15308 .collect();
15309 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15310 continue;
15311 }
15312 if seen.iter().any(|other| *other == key) {
15313 return Err(EngineError::Unsupported(alloc::format!(
15314 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15315 idx.name
15316 )));
15317 }
15318 seen.push(key);
15319 }
15320 Ok(())
15321}
15322
15323fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15327 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15328 out.push(idx.column_position);
15329 out.extend_from_slice(&idx.extra_column_positions);
15330 out
15331}
15332
15333fn enforce_unique_index_inserts(
15341 catalog: &Catalog,
15342 table_name: &str,
15343 rows: &[alloc::vec::Vec<spg_storage::Value>],
15344) -> Result<(), EngineError> {
15345 let table = catalog.get(table_name).ok_or_else(|| {
15346 EngineError::Storage(StorageError::TableNotFound {
15347 name: table_name.into(),
15348 })
15349 })?;
15350 let schema = table.schema();
15351 let ctx = eval::EvalContext::new(&schema.columns, None);
15352 for idx in table.indices() {
15353 if !idx.is_unique {
15354 continue;
15355 }
15356 let predicate_expr = match idx.partial_predicate.as_deref() {
15358 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15359 EngineError::Unsupported(alloc::format!(
15360 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15361 idx.name
15362 ))
15363 })?),
15364 None => None,
15365 };
15366 let key_positions = unique_key_positions(idx);
15367 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15368 key_positions
15372 .iter()
15373 .map(|&p| {
15374 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15375 collated_key_cell(&v, p, schema)
15376 })
15377 .collect()
15378 };
15379 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15383 let Some(expr) = &predicate_expr else {
15384 return Ok(true);
15385 };
15386 let tmp_row = spg_storage::Row {
15387 values: values.to_vec(),
15388 };
15389 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15390 EngineError::Unsupported(alloc::format!(
15391 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15392 idx.name
15393 ))
15394 })?;
15395 Ok(predicate_truthy(&v))
15396 };
15397 for (batch_idx, row_values) in rows.iter().enumerate() {
15398 if !participates(row_values)? {
15399 continue;
15400 }
15401 let key = key_of(row_values);
15402 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15403 continue;
15404 }
15405 for prow in table.rows() {
15407 if !participates(&prow.values)? {
15408 continue;
15409 }
15410 if key_of(&prow.values) == key {
15411 return Err(EngineError::Unsupported(alloc::format!(
15412 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15413 row #{batch_idx} duplicates an existing key",
15414 idx.name
15415 )));
15416 }
15417 }
15418 for earlier in &rows[..batch_idx] {
15420 if !participates(earlier)? {
15421 continue;
15422 }
15423 if key_of(earlier) == key {
15424 return Err(EngineError::Unsupported(alloc::format!(
15425 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15426 row #{batch_idx} duplicates an earlier row in the same batch",
15427 idx.name
15428 )));
15429 }
15430 }
15431 }
15432 }
15433 Ok(())
15434}
15435
15436fn any_column_changed(
15444 filter_cols: &[String],
15445 schema_cols: &[ColumnSchema],
15446 old_row: &Row,
15447 new_row: &Row,
15448) -> bool {
15449 for col_name in filter_cols {
15450 let Some(pos) = schema_cols
15451 .iter()
15452 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15453 else {
15454 continue;
15455 };
15456 let old_v = old_row.values.get(pos);
15457 let new_v = new_row.values.get(pos);
15458 if old_v != new_v {
15459 return true;
15460 }
15461 }
15462 false
15463}
15464
15465fn enforce_check_constraints(
15470 catalog: &Catalog,
15471 table_name: &str,
15472 rows: &[alloc::vec::Vec<spg_storage::Value>],
15473) -> Result<(), EngineError> {
15474 let table = catalog.get(table_name).ok_or_else(|| {
15475 EngineError::Storage(StorageError::TableNotFound {
15476 name: table_name.into(),
15477 })
15478 })?;
15479 let schema = table.schema();
15480 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15484 alloc::vec::Vec::new();
15485 for (idx, col) in schema.columns.iter().enumerate() {
15486 let Some(dname) = &col.user_domain_type else {
15487 continue;
15488 };
15489 let Some(dom) = catalog.domain_types().get(dname) else {
15490 continue;
15491 };
15492 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15493 alloc::vec::Vec::with_capacity(dom.checks.len());
15494 for src in &dom.checks {
15495 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15496 EngineError::Unsupported(alloc::format!(
15497 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15498 col.name
15499 ))
15500 })?;
15501 parsed_for_col.push(expr);
15502 }
15503 if !parsed_for_col.is_empty() {
15504 domain_checks_per_col.push((idx, parsed_for_col));
15505 }
15506 }
15507 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15508 return Ok(());
15509 }
15510 let ctx = eval::EvalContext::new(&schema.columns, None);
15511 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15512 for (i, src) in schema.checks.iter().enumerate() {
15513 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15514 EngineError::Unsupported(alloc::format!(
15515 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15516 ))
15517 })?;
15518 parsed.push((i, expr));
15519 }
15520 for (batch_idx, row_values) in rows.iter().enumerate() {
15521 let tmp_row = spg_storage::Row {
15522 values: row_values.clone(),
15523 };
15524 for (i, expr) in &parsed {
15525 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15526 EngineError::Unsupported(alloc::format!(
15527 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15528 ))
15529 })?;
15530 if matches!(v, spg_storage::Value::Bool(false)) {
15532 return Err(EngineError::Unsupported(alloc::format!(
15533 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15534 schema.checks[*i]
15535 )));
15536 }
15537 }
15538 for (col_idx, checks) in &domain_checks_per_col {
15544 let cell = row_values
15545 .get(*col_idx)
15546 .cloned()
15547 .unwrap_or(spg_storage::Value::Null);
15548 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15549 "value",
15550 schema.columns[*col_idx].ty,
15551 schema.columns[*col_idx].nullable,
15552 )];
15553 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15554 let synth_row = spg_storage::Row {
15555 values: alloc::vec![cell],
15556 };
15557 for (ci, expr) in checks.iter().enumerate() {
15558 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15559 EngineError::Unsupported(alloc::format!(
15560 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15561 schema.columns[*col_idx].name
15562 ))
15563 })?;
15564 if matches!(v, spg_storage::Value::Bool(false)) {
15565 return Err(EngineError::Unsupported(alloc::format!(
15566 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15567 schema.columns[*col_idx].name
15568 )));
15569 }
15570 }
15571 }
15572 }
15573 Ok(())
15574}
15575
15576fn enforce_fk_inserts(
15577 catalog: &Catalog,
15578 child_table: &str,
15579 fks: &[spg_storage::ForeignKeyConstraint],
15580 rows: &[Vec<Value>],
15581) -> Result<(), EngineError> {
15582 for fk in fks {
15583 let parent_is_self = fk.parent_table == child_table;
15584 let parent = if parent_is_self {
15585 catalog.get(child_table).ok_or_else(|| {
15588 EngineError::Storage(StorageError::TableNotFound {
15589 name: child_table.into(),
15590 })
15591 })?
15592 } else {
15593 catalog.get(&fk.parent_table).ok_or_else(|| {
15594 EngineError::Storage(StorageError::TableNotFound {
15595 name: fk.parent_table.clone(),
15596 })
15597 })?
15598 };
15599 for (batch_idx, row_values) in rows.iter().enumerate() {
15600 if fk.local_columns.len() == 1 {
15604 let v = &row_values[fk.local_columns[0]];
15605 if matches!(v, Value::Null) {
15606 continue;
15607 }
15608 let parent_col = fk.parent_columns[0];
15609 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15610 EngineError::Unsupported(alloc::format!(
15611 "FOREIGN KEY column value of type {:?} is not index-eligible",
15612 v.data_type()
15613 ))
15614 })?;
15615 let present_committed = parent.indices().iter().any(|idx| {
15616 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15617 && idx.column_position == parent_col
15618 && idx.partial_predicate.is_none()
15619 && !idx.lookup_eq(&key).is_empty()
15620 });
15621 let present_in_batch = parent_is_self
15625 && rows[..batch_idx]
15626 .iter()
15627 .any(|earlier| earlier.get(parent_col) == Some(v));
15628 if !(present_committed || present_in_batch) {
15629 return Err(EngineError::Unsupported(alloc::format!(
15630 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15631 fk.parent_table,
15632 parent
15633 .schema()
15634 .columns
15635 .get(parent_col)
15636 .map_or("?", |c| c.name.as_str()),
15637 v,
15638 )));
15639 }
15640 } else {
15641 if fk
15645 .local_columns
15646 .iter()
15647 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15648 {
15649 continue;
15650 }
15651 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15652 let parent_match_committed = parent.rows().iter().any(|prow| {
15653 fk.parent_columns
15654 .iter()
15655 .enumerate()
15656 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15657 });
15658 let parent_match_in_batch = parent_is_self
15659 && rows[..batch_idx].iter().any(|earlier| {
15660 fk.parent_columns
15661 .iter()
15662 .enumerate()
15663 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15664 });
15665 if !(parent_match_committed || parent_match_in_batch) {
15666 return Err(EngineError::Unsupported(alloc::format!(
15667 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15668 fk.parent_table,
15669 )));
15670 }
15671 }
15672 }
15673 }
15674 Ok(())
15675}
15676
15677#[derive(Debug, Clone)]
15681struct FkChildStep {
15682 child_table: String,
15683 action: FkChildAction,
15684}
15685
15686#[derive(Debug, Clone)]
15687enum FkChildAction {
15688 Delete { positions: Vec<usize> },
15690 SetNull {
15694 positions: Vec<usize>,
15695 columns: Vec<usize>,
15696 },
15697 SetDefault {
15701 positions: Vec<usize>,
15702 columns: Vec<usize>,
15703 defaults: Vec<Value>,
15704 },
15705}
15706
15707fn plan_fk_parent_deletions(
15723 catalog: &Catalog,
15724 parent_table_name: &str,
15725 to_delete_positions: &[usize],
15726 to_delete_rows: &[Vec<Value>],
15727) -> Result<Vec<FkChildStep>, EngineError> {
15728 use alloc::collections::{BTreeMap, BTreeSet};
15729 if to_delete_rows.is_empty() {
15730 return Ok(Vec::new());
15731 }
15732 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15733 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15735 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15736 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15737 for &p in to_delete_positions {
15738 visited.insert((parent_table_name.to_string(), p));
15739 }
15740 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15741 .iter()
15742 .map(|r| (parent_table_name.to_string(), r.clone()))
15743 .collect();
15744 while let Some((cur_parent, parent_row)) = work.pop() {
15745 for child_name in catalog.table_names() {
15746 let child = catalog
15747 .get(&child_name)
15748 .expect("table_names → catalog.get round-trip is total");
15749 for fk in &child.schema().foreign_keys {
15750 if fk.parent_table != cur_parent {
15751 continue;
15752 }
15753 let parent_key: Vec<&Value> = fk
15754 .parent_columns
15755 .iter()
15756 .map(|&pi| &parent_row[pi])
15757 .collect();
15758 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15759 continue;
15760 }
15761 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15762 if child_name == cur_parent
15763 && visited.contains(&(child_name.clone(), child_row_idx))
15764 {
15765 continue;
15766 }
15767 let matches_key = fk
15768 .local_columns
15769 .iter()
15770 .enumerate()
15771 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15772 if !matches_key {
15773 continue;
15774 }
15775 match fk.on_delete {
15776 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15777 return Err(EngineError::Unsupported(alloc::format!(
15778 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15779 restricted by FK from {child_name:?}.{:?}",
15780 fk.local_columns,
15781 )));
15782 }
15783 spg_storage::FkAction::Cascade => {
15784 if visited.insert((child_name.clone(), child_row_idx)) {
15785 delete_plan
15786 .entry(child_name.clone())
15787 .or_default()
15788 .insert(child_row_idx);
15789 work.push((child_name.clone(), child_row.values.clone()));
15790 }
15791 }
15792 spg_storage::FkAction::SetNull => {
15793 for &li in &fk.local_columns {
15795 let col = child.schema().columns.get(li).ok_or_else(|| {
15796 EngineError::Unsupported(alloc::format!(
15797 "FK local column {li} missing in {child_name:?}"
15798 ))
15799 })?;
15800 if !col.nullable {
15801 return Err(EngineError::Unsupported(alloc::format!(
15802 "FOREIGN KEY ON DELETE SET NULL: column \
15803 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15804 col.name,
15805 )));
15806 }
15807 }
15808 let entry = setnull_plan.entry(child_name.clone()).or_default();
15809 for &li in &fk.local_columns {
15810 entry.insert((child_row_idx, li));
15811 }
15812 }
15813 spg_storage::FkAction::SetDefault => {
15814 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15816 for &li in &fk.local_columns {
15817 let col = child.schema().columns.get(li).ok_or_else(|| {
15818 EngineError::Unsupported(alloc::format!(
15819 "FK local column {li} missing in {child_name:?}"
15820 ))
15821 })?;
15822 let default = col.default.clone().ok_or_else(|| {
15823 EngineError::Unsupported(alloc::format!(
15824 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15825 {child_name:?}.{:?} has no DEFAULT declared",
15826 col.name,
15827 ))
15828 })?;
15829 entry.insert((child_row_idx, li), default);
15830 }
15831 }
15832 }
15833 }
15834 }
15835 }
15836 }
15837 let mut steps: Vec<FkChildStep> = Vec::new();
15845 for (child_table, entries) in setnull_plan {
15846 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15847 steps.push(FkChildStep {
15848 child_table,
15849 action: FkChildAction::SetNull { positions, columns },
15850 });
15851 }
15852 for (child_table, entries) in setdefault_plan {
15853 let mut positions = Vec::with_capacity(entries.len());
15854 let mut columns = Vec::with_capacity(entries.len());
15855 let mut defaults = Vec::with_capacity(entries.len());
15856 for ((p, c), v) in entries {
15857 positions.push(p);
15858 columns.push(c);
15859 defaults.push(v);
15860 }
15861 steps.push(FkChildStep {
15862 child_table,
15863 action: FkChildAction::SetDefault {
15864 positions,
15865 columns,
15866 defaults,
15867 },
15868 });
15869 }
15870 for (child_table, positions) in delete_plan {
15871 steps.push(FkChildStep {
15872 child_table,
15873 action: FkChildAction::Delete {
15874 positions: positions.into_iter().collect(),
15875 },
15876 });
15877 }
15878 Ok(steps)
15879}
15880
15881fn plan_fk_parent_updates(
15898 catalog: &Catalog,
15899 parent_table_name: &str,
15900 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15901) -> Result<Vec<FkChildStep>, EngineError> {
15902 use alloc::collections::BTreeMap;
15903 if plan_with_old.is_empty() {
15904 return Ok(Vec::new());
15905 }
15906 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15911 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15912 BTreeMap::new();
15913 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15914 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15916
15917 for child_name in catalog.table_names() {
15918 let child = catalog
15919 .get(&child_name)
15920 .expect("table_names → catalog.get total");
15921 for fk in &child.schema().foreign_keys {
15922 if fk.parent_table != parent_table_name {
15923 continue;
15924 }
15925 for (_pos, old_row, new_row) in plan_with_old {
15926 let key_changed = fk
15928 .parent_columns
15929 .iter()
15930 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15931 if !key_changed {
15932 continue;
15933 }
15934 let old_key: Vec<&Value> =
15936 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15937 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15938 continue;
15940 }
15941 let new_key: Vec<&Value> =
15942 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15943 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15944 if child_name == parent_table_name
15947 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15948 {
15949 continue;
15950 }
15951 let matches_key = fk
15952 .local_columns
15953 .iter()
15954 .enumerate()
15955 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15956 if !matches_key {
15957 continue;
15958 }
15959 match fk.on_update {
15960 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15961 return Err(EngineError::Unsupported(alloc::format!(
15962 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15963 restricted by FK from {child_name:?}.{:?}",
15964 fk.local_columns,
15965 )));
15966 }
15967 spg_storage::FkAction::Cascade => {
15968 let entry = cascade_plan.entry(child_name.clone()).or_default();
15970 for (i, &li) in fk.local_columns.iter().enumerate() {
15971 entry.insert((child_row_idx, li), new_key[i].clone());
15972 }
15973 }
15974 spg_storage::FkAction::SetNull => {
15975 for &li in &fk.local_columns {
15976 let col = child.schema().columns.get(li).ok_or_else(|| {
15977 EngineError::Unsupported(alloc::format!(
15978 "FK local column {li} missing in {child_name:?}"
15979 ))
15980 })?;
15981 if !col.nullable {
15982 return Err(EngineError::Unsupported(alloc::format!(
15983 "FOREIGN KEY ON UPDATE SET NULL: column \
15984 {child_name:?}.{:?} is NOT NULL",
15985 col.name,
15986 )));
15987 }
15988 }
15989 let entry = setnull_plan.entry(child_name.clone()).or_default();
15990 for &li in &fk.local_columns {
15991 entry.insert((child_row_idx, li));
15992 }
15993 }
15994 spg_storage::FkAction::SetDefault => {
15995 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15996 for &li in &fk.local_columns {
15997 let col = child.schema().columns.get(li).ok_or_else(|| {
15998 EngineError::Unsupported(alloc::format!(
15999 "FK local column {li} missing in {child_name:?}"
16000 ))
16001 })?;
16002 let default = col.default.clone().ok_or_else(|| {
16003 EngineError::Unsupported(alloc::format!(
16004 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
16005 {child_name:?}.{:?} has no DEFAULT",
16006 col.name,
16007 ))
16008 })?;
16009 entry.insert((child_row_idx, li), default);
16010 }
16011 }
16012 }
16013 }
16014 }
16015 }
16016 }
16017 let mut steps: Vec<FkChildStep> = Vec::new();
16020 for (child_table, entries) in cascade_plan {
16021 let mut positions = Vec::with_capacity(entries.len());
16022 let mut columns = Vec::with_capacity(entries.len());
16023 let mut defaults = Vec::with_capacity(entries.len());
16024 for ((p, c), v) in entries {
16025 positions.push(p);
16026 columns.push(c);
16027 defaults.push(v);
16028 }
16029 steps.push(FkChildStep {
16034 child_table,
16035 action: FkChildAction::SetDefault {
16036 positions,
16037 columns,
16038 defaults,
16039 },
16040 });
16041 }
16042 for (child_table, entries) in setnull_plan {
16043 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16044 steps.push(FkChildStep {
16045 child_table,
16046 action: FkChildAction::SetNull { positions, columns },
16047 });
16048 }
16049 for (child_table, entries) in setdefault_plan {
16050 let mut positions = Vec::with_capacity(entries.len());
16051 let mut columns = Vec::with_capacity(entries.len());
16052 let mut defaults = Vec::with_capacity(entries.len());
16053 for ((p, c), v) in entries {
16054 positions.push(p);
16055 columns.push(c);
16056 defaults.push(v);
16057 }
16058 steps.push(FkChildStep {
16059 child_table,
16060 action: FkChildAction::SetDefault {
16061 positions,
16062 columns,
16063 defaults,
16064 },
16065 });
16066 }
16067 let _ = delete_plan; Ok(steps)
16069}
16070
16071fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
16075 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
16076 EngineError::Storage(StorageError::TableNotFound {
16077 name: step.child_table.clone(),
16078 })
16079 })?;
16080 match &step.action {
16081 FkChildAction::Delete { positions } => {
16082 let _ = child.delete_rows(positions);
16083 }
16084 FkChildAction::SetNull { positions, columns } => {
16085 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
16086 }
16087 FkChildAction::SetDefault {
16088 positions,
16089 columns,
16090 defaults,
16091 } => {
16092 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
16093 }
16094 }
16095 Ok(())
16096}
16097
16098fn apply_per_cell_writes(
16104 child: &mut spg_storage::Table,
16105 positions: &[usize],
16106 columns: &[usize],
16107 mut value_for: impl FnMut(usize) -> Value,
16108) -> Result<(), EngineError> {
16109 use alloc::collections::BTreeMap;
16110 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
16111 for i in 0..positions.len() {
16112 by_row
16113 .entry(positions[i])
16114 .or_default()
16115 .push((columns[i], value_for(i)));
16116 }
16117 for (pos, mutations) in by_row {
16118 let mut new_values = child.rows()[pos].values.clone();
16119 for (col, v) in mutations {
16120 if let Some(slot) = new_values.get_mut(col) {
16121 *slot = v;
16122 }
16123 }
16124 child
16125 .update_row(pos, new_values)
16126 .map_err(EngineError::Storage)?;
16127 }
16128 Ok(())
16129}
16130
16131fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
16132 match a {
16133 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
16134 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
16135 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
16136 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
16137 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
16138 }
16139}
16140
16141fn resolve_column_default_free(
16147 col: &ColumnSchema,
16148 clock_fn: Option<ClockFn>,
16149) -> Result<Value, EngineError> {
16150 if let Some(rt) = &col.runtime_default {
16151 return eval_runtime_default_free(rt, col.ty, clock_fn);
16152 }
16153 Ok(col.default.clone().unwrap_or(Value::Null))
16154}
16155
16156fn eval_runtime_default_free(
16157 rt: &str,
16158 ty: DataType,
16159 clock_fn: Option<ClockFn>,
16160) -> Result<Value, EngineError> {
16161 let s = rt.trim().to_ascii_lowercase();
16162 let with_no_parens = s.trim_end_matches("()");
16168 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
16169 if with_no_parens.ends_with(')') {
16170 &with_no_parens[..open_idx]
16171 } else {
16172 with_no_parens
16173 }
16174 } else {
16175 with_no_parens
16176 };
16177 let now_us = match clock_fn {
16178 Some(f) => f(),
16179 None => 0,
16180 };
16181 let v = match canonical {
16182 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
16183 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
16184 "current_time" | "localtime" => Value::Timestamp(now_us),
16185 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
16191 other => {
16192 return Err(EngineError::Unsupported(alloc::format!(
16193 "runtime DEFAULT expression {other:?} not supported \
16194 (v7.17.0 whitelist: now() / current_timestamp / \
16195 current_date / current_time / localtimestamp / \
16196 localtime / gen_random_uuid() / \
16197 uuid_generate_v4())"
16198 )));
16199 }
16200 };
16201 coerce_value(v, ty, "DEFAULT", 0)
16202}
16203
16204fn is_runtime_default_expr(expr: &Expr) -> bool {
16210 match expr {
16211 Expr::FunctionCall { .. } => true,
16212 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
16213 _ => false,
16214 }
16215}
16216
16217fn canonicalize_set_value(
16230 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16231 col_idx: usize,
16232 col_name: &str,
16233 value: Value,
16234) -> Result<Value, EngineError> {
16235 let Some(variants) = lookup.get(&col_idx) else {
16236 return Ok(value);
16237 };
16238 match value {
16239 Value::Null => Ok(Value::Null),
16240 Value::Text(s) => {
16241 if s.is_empty() {
16242 return Ok(Value::Text(alloc::string::String::new()));
16243 }
16244 let mut present = alloc::vec![false; variants.len()];
16247 for raw in s.split(',') {
16248 let tok = raw.trim();
16249 if tok.is_empty() {
16250 continue;
16251 }
16252 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
16253 EngineError::Unsupported(alloc::format!(
16254 "column {col_name:?}: invalid SET token {tok:?}; \
16255 allowed: {variants:?}"
16256 ))
16257 })?;
16258 present[idx] = true;
16259 }
16260 let mut out = alloc::string::String::new();
16262 let mut first = true;
16263 for (i, keep) in present.iter().enumerate() {
16264 if !keep {
16265 continue;
16266 }
16267 if !first {
16268 out.push(',');
16269 }
16270 first = false;
16271 out.push_str(&variants[i]);
16272 }
16273 Ok(Value::Text(out))
16274 }
16275 other => Err(EngineError::Unsupported(alloc::format!(
16276 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16277 other.data_type()
16278 ))),
16279 }
16280}
16281
16282fn enforce_enum_label(
16283 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16284 col_idx: usize,
16285 col_name: &str,
16286 value: &Value,
16287) -> Result<(), EngineError> {
16288 if let Some(labels) = lookup.get(&col_idx) {
16289 match value {
16290 Value::Null => Ok(()),
16291 Value::Text(s) => {
16292 if labels.iter().any(|l| l == s) {
16293 Ok(())
16294 } else {
16295 Err(EngineError::Unsupported(alloc::format!(
16296 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16297 )))
16298 }
16299 }
16300 other => Err(EngineError::Unsupported(alloc::format!(
16301 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16302 other.data_type()
16303 ))),
16304 }
16305 } else {
16306 Ok(())
16307 }
16308}
16309
16310fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16311 let ty = column_type_to_data_type(c.ty);
16312 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16313 if let Some(name) = c.user_type_ref {
16320 schema.user_enum_type = Some(name);
16321 }
16322 if let Some(expr) = c.on_update_runtime {
16325 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16326 }
16327 schema.collation = match c.collation {
16331 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16332 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16333 };
16334 schema.is_unsigned = c.is_unsigned;
16337 schema.inline_enum_variants = c.inline_enum_variants;
16341 schema.inline_set_variants = c.inline_set_variants;
16345 if let Some(default_expr) = c.default {
16346 if is_runtime_default_expr(&default_expr) {
16352 let display = alloc::format!("{default_expr}");
16353 schema = schema.with_runtime_default(display);
16354 } else {
16355 let raw = literal_expr_to_value(default_expr)?;
16356 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16357 schema = schema.with_default(coerced);
16358 }
16359 }
16360 if c.auto_increment {
16361 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16363 return Err(EngineError::Unsupported(alloc::format!(
16364 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16365 )));
16366 }
16367 schema = schema.with_auto_increment();
16368 }
16369 Ok(schema)
16370}
16371
16372fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16377 let s = s.trim();
16378 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16379 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16381 if cleaned.len() % 2 != 0 {
16382 return Err("odd-length hex literal");
16383 }
16384 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16385 let cleaned_bytes = cleaned.as_bytes();
16386 for i in (0..cleaned_bytes.len()).step_by(2) {
16387 let hi = hex_nibble(cleaned_bytes[i])?;
16388 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16389 out.push((hi << 4) | lo);
16390 }
16391 return Ok(out);
16392 }
16393 let bytes = s.as_bytes();
16396 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16397 let mut i = 0;
16398 while i < bytes.len() {
16399 let b = bytes[i];
16400 if b == b'\\' && i + 1 < bytes.len() {
16401 let n = bytes[i + 1];
16402 if n == b'\\' {
16403 out.push(b'\\');
16404 i += 2;
16405 continue;
16406 }
16407 if n.is_ascii_digit()
16408 && i + 3 < bytes.len()
16409 && bytes[i + 2].is_ascii_digit()
16410 && bytes[i + 3].is_ascii_digit()
16411 {
16412 let oct = |x: u8| (x - b'0') as u32;
16413 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16414 if v <= 0xFF {
16415 out.push(v as u8);
16416 i += 4;
16417 continue;
16418 }
16419 }
16420 }
16421 out.push(b);
16422 i += 1;
16423 }
16424 Ok(out)
16425}
16426
16427fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16428 match b {
16429 b'0'..=b'9' => Ok(b - b'0'),
16430 b'a'..=b'f' => Ok(b - b'a' + 10),
16431 b'A'..=b'F' => Ok(b - b'A' + 10),
16432 _ => Err("invalid hex digit"),
16433 }
16434}
16435
16436fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16450 let mut has_text = false;
16451 let mut has_bigint = false;
16452 let mut has_int = false;
16453 for v in &items {
16454 match v {
16455 Value::Null => {}
16456 Value::Text(_) | Value::Json(_) => has_text = true,
16457 Value::BigInt(_) => has_bigint = true,
16458 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16459 _ => has_text = true,
16460 }
16461 }
16462 if has_text || (!has_bigint && !has_int) {
16463 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16464 .into_iter()
16465 .map(|v| match v {
16466 Value::Null => None,
16467 Value::Text(s) | Value::Json(s) => Some(s),
16468 other => Some(alloc::format!("{other:?}")),
16469 })
16470 .collect();
16471 return Value::TextArray(out);
16472 }
16473 if has_bigint {
16474 let out: alloc::vec::Vec<Option<i64>> = items
16475 .into_iter()
16476 .map(|v| match v {
16477 Value::Null => None,
16478 Value::Int(n) => Some(i64::from(n)),
16479 Value::SmallInt(n) => Some(i64::from(n)),
16480 Value::BigInt(n) => Some(n),
16481 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16482 })
16483 .collect();
16484 return Value::BigIntArray(out);
16485 }
16486 let out: alloc::vec::Vec<Option<i32>> = items
16487 .into_iter()
16488 .map(|v| match v {
16489 Value::Null => None,
16490 Value::Int(n) => Some(n),
16491 Value::SmallInt(n) => Some(i32::from(n)),
16492 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16493 })
16494 .collect();
16495 Value::IntArray(out)
16496}
16497
16498fn decode_text_array_literal(
16499 s: &str,
16500) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16501 let trimmed = s.trim();
16502 let inner = trimmed
16503 .strip_prefix('{')
16504 .and_then(|x| x.strip_suffix('}'))
16505 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16506 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16507 if inner.trim().is_empty() {
16508 return Ok(out);
16509 }
16510 let bytes = inner.as_bytes();
16511 let mut i = 0;
16512 while i <= bytes.len() {
16513 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16515 i += 1;
16516 }
16517 if i < bytes.len() && bytes[i] == b'"' {
16519 i += 1; let mut buf = alloc::string::String::new();
16521 while i < bytes.len() && bytes[i] != b'"' {
16522 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16523 buf.push(bytes[i + 1] as char);
16524 i += 2;
16525 } else {
16526 buf.push(bytes[i] as char);
16527 i += 1;
16528 }
16529 }
16530 if i >= bytes.len() {
16531 return Err("unterminated quoted element");
16532 }
16533 i += 1; out.push(Some(buf));
16535 } else {
16536 let start = i;
16538 while i < bytes.len() && bytes[i] != b',' {
16539 i += 1;
16540 }
16541 let raw = inner[start..i].trim();
16542 if raw.eq_ignore_ascii_case("NULL") {
16543 out.push(None);
16544 } else {
16545 out.push(Some(alloc::string::ToString::to_string(raw)));
16546 }
16547 }
16548 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16550 i += 1;
16551 }
16552 if i >= bytes.len() {
16553 break;
16554 }
16555 if bytes[i] != b',' {
16556 return Err("expected ',' between TEXT[] elements");
16557 }
16558 i += 1;
16559 }
16560 Ok(out)
16561}
16562
16563fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16568 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16569 out.push('{');
16570 for (i, item) in items.iter().enumerate() {
16571 if i > 0 {
16572 out.push(',');
16573 }
16574 match item {
16575 None => out.push_str("NULL"),
16576 Some(s) => {
16577 let needs_quote = s.is_empty()
16578 || s.eq_ignore_ascii_case("NULL")
16579 || s.chars()
16580 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16581 if needs_quote {
16582 out.push('"');
16583 for c in s.chars() {
16584 if c == '"' || c == '\\' {
16585 out.push('\\');
16586 }
16587 out.push(c);
16588 }
16589 out.push('"');
16590 } else {
16591 out.push_str(s);
16592 }
16593 }
16594 }
16595 }
16596 out.push('}');
16597 out
16598}
16599
16600fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16604 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16605 out.push_str("\\x");
16606 for byte in b {
16607 let hi = byte >> 4;
16608 let lo = byte & 0x0F;
16609 out.push(hex_digit(hi));
16610 out.push(hex_digit(lo));
16611 }
16612 out
16613}
16614
16615const fn hex_digit(n: u8) -> char {
16616 match n {
16617 0..=9 => (b'0' + n) as char,
16618 10..=15 => (b'a' + n - 10) as char,
16619 _ => '?',
16620 }
16621}
16622
16623fn parse_hstore_str(
16635 s: &str,
16636) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16637 let bytes = s.as_bytes();
16638 let mut i = 0;
16639 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16640 let skip_ws = |bytes: &[u8], i: &mut usize| {
16641 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16642 *i += 1;
16643 }
16644 };
16645 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16646 if *i >= bytes.len() {
16647 return None;
16648 }
16649 if bytes[*i] == b'"' {
16650 *i += 1;
16651 let mut out = alloc::string::String::new();
16652 while *i < bytes.len() {
16653 match bytes[*i] {
16654 b'"' => {
16655 *i += 1;
16656 return Some(out);
16657 }
16658 b'\\' if *i + 1 < bytes.len() => {
16659 out.push(bytes[*i + 1] as char);
16660 *i += 2;
16661 }
16662 c => {
16663 out.push(c as char);
16664 *i += 1;
16665 }
16666 }
16667 }
16668 None
16669 } else {
16670 let start = *i;
16671 while *i < bytes.len()
16672 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16673 {
16674 *i += 1;
16675 }
16676 if *i == start {
16677 return None;
16678 }
16679 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16680 }
16681 };
16682 skip_ws(bytes, &mut i);
16683 while i < bytes.len() {
16684 let key = parse_token(bytes, &mut i)?;
16685 skip_ws(bytes, &mut i);
16686 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16687 return None;
16688 }
16689 i += 2;
16690 skip_ws(bytes, &mut i);
16691 let val_token = if i + 4 <= bytes.len()
16693 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16694 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16695 {
16696 i += 4;
16697 None
16698 } else {
16699 Some(parse_token(bytes, &mut i)?)
16700 };
16701 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16703 out[pos] = (key, val_token);
16704 } else {
16705 out.push((key, val_token));
16706 }
16707 skip_ws(bytes, &mut i);
16708 if i >= bytes.len() {
16709 break;
16710 }
16711 if bytes[i] == b',' {
16712 i += 1;
16713 skip_ws(bytes, &mut i);
16714 continue;
16715 }
16716 return None;
16717 }
16718 Some(out)
16719}
16720
16721fn format_hstore_str(
16725 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16726) -> alloc::string::String {
16727 let mut out = alloc::string::String::new();
16728 for (i, (k, v)) in pairs.iter().enumerate() {
16729 if i > 0 {
16730 out.push_str(", ");
16731 }
16732 out.push('"');
16733 out.push_str(k);
16734 out.push_str("\"=>");
16735 match v {
16736 None => out.push_str("NULL"),
16737 Some(val) => {
16738 out.push('"');
16739 out.push_str(val);
16740 out.push('"');
16741 }
16742 }
16743 }
16744 out
16745}
16746
16747pub fn format_hstore_text(
16750 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16751) -> alloc::string::String {
16752 format_hstore_str(pairs)
16753}
16754
16755fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16760 let s = s.trim();
16761 let outer = s
16762 .strip_prefix('{')
16763 .and_then(|x| x.strip_suffix('}'))
16764 .ok_or("missing outer '{...}' braces")?;
16765 let trimmed = outer.trim();
16766 if trimmed.is_empty() {
16767 return Ok(Vec::new());
16768 }
16769 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16770 let mut i = 0;
16771 let bytes = trimmed.as_bytes();
16772 while i < bytes.len() {
16773 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16774 i += 1;
16775 }
16776 if i >= bytes.len() {
16777 break;
16778 }
16779 if bytes[i] != b'{' {
16780 return Err("expected '{' opening a row");
16781 }
16782 i += 1;
16783 let row_start = i;
16784 let mut depth = 1;
16785 while i < bytes.len() && depth > 0 {
16786 match bytes[i] {
16787 b'{' => depth += 1,
16788 b'}' => depth -= 1,
16789 _ => {}
16790 }
16791 if depth > 0 {
16792 i += 1;
16793 }
16794 }
16795 if depth != 0 {
16796 return Err("unbalanced '{...}' in row");
16797 }
16798 let row_text = &trimmed[row_start..i];
16799 i += 1;
16800 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16801 Vec::new()
16802 } else {
16803 row_text.split(',').map(|t| t.trim().to_string()).collect()
16804 };
16805 rows.push(cells);
16806 }
16807 if let Some(first) = rows.first() {
16808 let cols = first.len();
16809 for r in &rows {
16810 if r.len() != cols {
16811 return Err("ragged 2D array (rows have different column counts)");
16812 }
16813 }
16814 }
16815 Ok(rows)
16816}
16817
16818fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16819 let raw = split_2d_literal(s)?;
16820 raw.into_iter()
16821 .map(|row| {
16822 row.into_iter()
16823 .map(|cell| {
16824 if cell.eq_ignore_ascii_case("NULL") {
16825 Ok(None)
16826 } else {
16827 cell.parse::<i32>()
16828 .map(Some)
16829 .map_err(|_| "invalid int element")
16830 }
16831 })
16832 .collect()
16833 })
16834 .collect()
16835}
16836
16837fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16838 let raw = split_2d_literal(s)?;
16839 raw.into_iter()
16840 .map(|row| {
16841 row.into_iter()
16842 .map(|cell| {
16843 if cell.eq_ignore_ascii_case("NULL") {
16844 Ok(None)
16845 } else {
16846 cell.parse::<i64>()
16847 .map(Some)
16848 .map_err(|_| "invalid bigint element")
16849 }
16850 })
16851 .collect()
16852 })
16853 .collect()
16854}
16855
16856fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16857 let raw = split_2d_literal(s)?;
16858 Ok(raw
16859 .into_iter()
16860 .map(|row| {
16861 row.into_iter()
16862 .map(|cell| {
16863 if cell.eq_ignore_ascii_case("NULL") {
16864 None
16865 } else {
16866 Some(cell.trim_matches('"').to_string())
16867 }
16868 })
16869 .collect()
16870 })
16871 .collect())
16872}
16873
16874fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16875 let mut out = alloc::string::String::from("{");
16876 for (i, row) in rows.iter().enumerate() {
16877 if i > 0 {
16878 out.push(',');
16879 }
16880 out.push('{');
16881 for (j, cell) in row.iter().enumerate() {
16882 if j > 0 {
16883 out.push(',');
16884 }
16885 match cell {
16886 None => out.push_str("NULL"),
16887 Some(n) => out.push_str(&alloc::format!("{n}")),
16888 }
16889 }
16890 out.push('}');
16891 }
16892 out.push('}');
16893 out
16894}
16895
16896fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16897 let mut out = alloc::string::String::from("{");
16898 for (i, row) in rows.iter().enumerate() {
16899 if i > 0 {
16900 out.push(',');
16901 }
16902 out.push('{');
16903 for (j, cell) in row.iter().enumerate() {
16904 if j > 0 {
16905 out.push(',');
16906 }
16907 match cell {
16908 None => out.push_str("NULL"),
16909 Some(n) => out.push_str(&alloc::format!("{n}")),
16910 }
16911 }
16912 out.push('}');
16913 }
16914 out.push('}');
16915 out
16916}
16917
16918fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16919 let mut out = alloc::string::String::from("{");
16920 for (i, row) in rows.iter().enumerate() {
16921 if i > 0 {
16922 out.push(',');
16923 }
16924 out.push('{');
16925 for (j, cell) in row.iter().enumerate() {
16926 if j > 0 {
16927 out.push(',');
16928 }
16929 match cell {
16930 None => out.push_str("NULL"),
16931 Some(s) => out.push_str(s),
16932 }
16933 }
16934 out.push('}');
16935 }
16936 out.push('}');
16937 out
16938}
16939
16940pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16943 format_int_2d_text(rows)
16944}
16945pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16946 format_bigint_2d_text(rows)
16947}
16948pub fn format_text_2d_text_pub(
16949 rows: &[Vec<Option<alloc::string::String>>],
16950) -> alloc::string::String {
16951 format_text_2d_text(rows)
16952}
16953
16954fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16959 let s = s.trim();
16960 if s.eq_ignore_ascii_case("empty") {
16961 return Some(Value::Range {
16962 kind,
16963 lower: None,
16964 upper: None,
16965 lower_inc: false,
16966 upper_inc: false,
16967 empty: true,
16968 });
16969 }
16970 let bytes = s.as_bytes();
16971 if bytes.len() < 3 {
16972 return None;
16973 }
16974 let lower_inc = match bytes[0] {
16975 b'[' => true,
16976 b'(' => false,
16977 _ => return None,
16978 };
16979 let upper_inc = match bytes[bytes.len() - 1] {
16980 b']' => true,
16981 b')' => false,
16982 _ => return None,
16983 };
16984 let inner = &s[1..s.len() - 1];
16985 let (lo_text, up_text) = inner.split_once(',')?;
16986 let lower = if lo_text.is_empty() {
16987 None
16988 } else {
16989 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16990 };
16991 let upper = if up_text.is_empty() {
16992 None
16993 } else {
16994 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16995 };
16996 Some(Value::Range {
16997 kind,
16998 lower,
16999 upper,
17000 lower_inc,
17001 upper_inc,
17002 empty: false,
17003 })
17004}
17005
17006fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
17009 let text = text.trim().trim_matches('"');
17010 use spg_storage::RangeKind as K;
17011 match kind {
17012 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
17013 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
17014 K::Num => {
17015 let dot = text.find('.');
17018 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
17019 let digits: alloc::string::String = text
17020 .chars()
17021 .filter(|c| *c == '-' || c.is_ascii_digit())
17022 .collect();
17023 let scaled: i128 = digits.parse().ok()?;
17024 Some(Value::Numeric { scaled, scale })
17025 }
17026 K::Ts | K::TsTz => {
17027 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
17032 }
17033 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
17034 }
17035}
17036
17037pub fn format_range_text(v: &Value) -> alloc::string::String {
17041 format_range_str(v)
17042}
17043
17044fn format_range_str(v: &Value) -> alloc::string::String {
17045 let Value::Range {
17046 lower,
17047 upper,
17048 lower_inc,
17049 upper_inc,
17050 empty,
17051 ..
17052 } = v
17053 else {
17054 return alloc::string::String::new();
17055 };
17056 if *empty {
17057 return "empty".into();
17058 }
17059 let mut out = alloc::string::String::new();
17060 out.push(if *lower_inc { '[' } else { '(' });
17061 if let Some(l) = lower {
17062 out.push_str(&format_range_element(l));
17063 }
17064 out.push(',');
17065 if let Some(u) = upper {
17066 out.push_str(&format_range_element(u));
17067 }
17068 out.push(if *upper_inc { ']' } else { ')' });
17069 out
17070}
17071
17072fn format_range_element(v: &Value) -> alloc::string::String {
17073 match v {
17074 Value::Int(n) => alloc::format!("{n}"),
17075 Value::BigInt(n) => alloc::format!("{n}"),
17076 Value::Date(d) => crate::eval::format_date(*d),
17077 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
17078 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
17079 other => alloc::format!("{other:?}"),
17080 }
17081}
17082
17083fn parse_money_str(s: &str) -> Option<i64> {
17094 let s = s.trim();
17095 let (neg, rest) = match s.strip_prefix('-') {
17096 Some(r) => (true, r.trim_start()),
17097 None => (false, s),
17098 };
17099 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
17100 let (int_part, frac_part) = match rest.split_once('.') {
17101 Some((i, f)) => (i, Some(f)),
17102 None => (rest, None),
17103 };
17104 if int_part.is_empty() {
17105 return None;
17106 }
17107 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
17109 for b in int_part.bytes() {
17110 match b {
17111 b',' => {}
17112 b'0'..=b'9' => int_digits.push(b as char),
17113 _ => return None,
17114 }
17115 }
17116 if int_digits.is_empty() {
17117 return None;
17118 }
17119 let dollars: i64 = int_digits.parse().ok()?;
17120 let cents: i64 = match frac_part {
17121 None => 0,
17122 Some(f) => {
17123 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
17124 return None;
17125 }
17126 let padded = if f.len() == 1 {
17127 alloc::format!("{f}0")
17128 } else {
17129 f.to_string()
17130 };
17131 padded.parse().ok()?
17132 }
17133 };
17134 let total = dollars.checked_mul(100)?.checked_add(cents)?;
17135 Some(if neg { -total } else { total })
17136}
17137
17138fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
17149 let s = s.trim();
17150 let bytes = s.as_bytes();
17154 let sign_pos = bytes
17155 .iter()
17156 .enumerate()
17157 .rev()
17158 .find(|&(_, &b)| b == b'+' || b == b'-')
17159 .map(|(i, _)| i)?;
17160 if sign_pos == 0 {
17161 return None; }
17163 let time_part = &s[..sign_pos];
17164 let offset_part = &s[sign_pos..];
17165 let us = parse_time_str(time_part)?;
17166 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
17167 let offset_body = &offset_part[1..];
17168 let (hh_str, mm_str) = match offset_body.split_once(':') {
17169 Some((h, m)) => (h, m),
17170 None => (offset_body, "0"),
17171 };
17172 let hh: i32 = hh_str.parse().ok()?;
17173 let mm: i32 = mm_str.parse().ok()?;
17174 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
17175 return None;
17176 }
17177 let total = sign * (hh * 3600 + mm * 60);
17178 if total.abs() > 50_400 {
17179 return None;
17180 }
17181 Some((us, total))
17182}
17183
17184fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
17189 if n == 0 || (1901..=2155).contains(&n) {
17190 return Ok(Value::Year(n as u16));
17193 }
17194 Err(EngineError::Eval(EvalError::TypeMismatch {
17195 detail: alloc::format!(
17196 "year value out of range: {n} (column `{col_name}`; \
17197 MySQL accepts 0 or 1901..=2155)"
17198 ),
17199 }))
17200}
17201
17202fn parse_time_str(s: &str) -> Option<i64> {
17214 let s = s.trim();
17215 let (hms, frac) = match s.split_once('.') {
17216 Some((h, f)) => (h, Some(f)),
17217 None => (s, None),
17218 };
17219 let mut parts = hms.split(':');
17220 let hh: u32 = parts.next()?.parse().ok()?;
17221 let mm: u32 = parts.next()?.parse().ok()?;
17222 let ss: u32 = parts.next()?.parse().ok()?;
17223 if parts.next().is_some() {
17224 return None;
17225 }
17226 if hh > 23 || mm > 59 || ss > 59 {
17227 return None;
17228 }
17229 let frac_us: i64 = match frac {
17230 None => 0,
17231 Some(f) => {
17232 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
17233 return None;
17234 }
17235 let mut padded = alloc::string::String::with_capacity(6);
17237 padded.push_str(f);
17238 while padded.len() < 6 {
17239 padded.push('0');
17240 }
17241 padded.parse().ok()?
17242 }
17243 };
17244 Some(
17245 i64::from(hh) * 3_600_000_000
17246 + i64::from(mm) * 60_000_000
17247 + i64::from(ss) * 1_000_000
17248 + frac_us,
17249 )
17250}
17251
17252const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
17253 match t {
17254 ColumnTypeName::SmallInt => DataType::SmallInt,
17255 ColumnTypeName::Int => DataType::Int,
17256 ColumnTypeName::BigInt => DataType::BigInt,
17257 ColumnTypeName::Float => DataType::Float,
17258 ColumnTypeName::Text => DataType::Text,
17259 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17260 ColumnTypeName::Char(n) => DataType::Char(n),
17261 ColumnTypeName::Bool => DataType::Bool,
17262 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17263 dim,
17264 encoding: match encoding {
17265 SqlVecEncoding::F32 => VecEncoding::F32,
17266 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17267 SqlVecEncoding::F16 => VecEncoding::F16,
17268 },
17269 },
17270 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17271 ColumnTypeName::Date => DataType::Date,
17272 ColumnTypeName::Timestamp => DataType::Timestamp,
17273 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17274 ColumnTypeName::Json => DataType::Json,
17275 ColumnTypeName::Jsonb => DataType::Jsonb,
17276 ColumnTypeName::Bytes => DataType::Bytes,
17277 ColumnTypeName::TextArray => DataType::TextArray,
17278 ColumnTypeName::IntArray => DataType::IntArray,
17279 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17280 ColumnTypeName::TsVector => DataType::TsVector,
17281 ColumnTypeName::TsQuery => DataType::TsQuery,
17282 ColumnTypeName::Uuid => DataType::Uuid,
17283 ColumnTypeName::Time => DataType::Time,
17284 ColumnTypeName::Year => DataType::Year,
17285 ColumnTypeName::TimeTz => DataType::TimeTz,
17286 ColumnTypeName::Money => DataType::Money,
17287 ColumnTypeName::Range(k) => DataType::Range(match k {
17288 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17289 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17290 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17291 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17292 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17293 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17294 }),
17295 ColumnTypeName::Hstore => DataType::Hstore,
17296 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17297 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17298 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17299 }
17300}
17301
17302fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17306 match expr {
17307 Expr::Literal(l) => Ok(literal_to_value(l)),
17308 Expr::Cast { expr, target } => {
17309 let inner_value = literal_expr_to_value(*expr)?;
17310 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17311 }
17312 Expr::Unary {
17313 op: UnOp::Neg,
17314 expr,
17315 } => match *expr {
17316 Expr::Literal(Literal::Integer(n)) => {
17317 let neg = n.checked_neg().ok_or_else(|| {
17320 EngineError::Unsupported("integer literal overflow on negation".into())
17321 })?;
17322 Ok(int_value_for(neg))
17323 }
17324 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17325 other => Err(EngineError::Unsupported(alloc::format!(
17326 "unary minus over non-literal expression: {other:?}"
17327 ))),
17328 },
17329 Expr::Array(items) => {
17337 let mut materialised: alloc::vec::Vec<Value> =
17338 alloc::vec::Vec::with_capacity(items.len());
17339 for elem in items {
17340 materialised.push(literal_expr_to_value(elem)?);
17341 }
17342 Ok(array_literal_widen(materialised))
17343 }
17344 other => {
17357 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17358 let ctx = EvalContext::new(&empty_schema, None);
17359 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17360 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17361 }
17362 }
17363}
17364
17365fn literal_to_value(l: Literal) -> Value {
17366 match l {
17367 Literal::Integer(n) => int_value_for(n),
17368 Literal::Float(x) => Value::Float(x),
17369 Literal::String(s) => Value::Text(s),
17370 Literal::Bool(b) => Value::Bool(b),
17371 Literal::Null => Value::Null,
17372 Literal::Vector(v) => Value::Vector(v),
17373 Literal::TextArray(items) => Value::TextArray(items),
17374 Literal::IntArray(items) => Value::IntArray(items),
17375 Literal::BigIntArray(items) => Value::BigIntArray(items),
17376 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17377 }
17378}
17379
17380fn int_value_for(n: i64) -> Value {
17384 if let Ok(small) = i32::try_from(n) {
17385 Value::Int(small)
17386 } else {
17387 Value::BigInt(n)
17388 }
17389}
17390
17391#[allow(clippy::too_many_lines)]
17397fn check_unsigned_range(
17402 v: &Value,
17403 schema: &ColumnSchema,
17404 position: usize,
17405) -> Result<(), EngineError> {
17406 if !schema.is_unsigned {
17407 return Ok(());
17408 }
17409 let n = match v {
17410 Value::SmallInt(x) => i64::from(*x),
17411 Value::Int(x) => i64::from(*x),
17412 Value::BigInt(x) => *x,
17413 _ => return Ok(()), };
17415 if n < 0 {
17416 return Err(EngineError::Unsupported(alloc::format!(
17417 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17418 schema.name
17419 )));
17420 }
17421 Ok(())
17422}
17423
17424fn coerce_value(
17425 v: Value,
17426 expected: DataType,
17427 col_name: &str,
17428 position: usize,
17429) -> Result<Value, EngineError> {
17430 if v.is_null() {
17431 return Ok(Value::Null);
17432 }
17433 let actual = v.data_type().expect("non-null");
17434 if actual == expected {
17435 return Ok(v);
17436 }
17437 let coerced = match (v, expected) {
17438 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17439 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17440 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17441 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17442 i128::from(n),
17443 precision,
17444 scale,
17445 col_name,
17446 )?),
17447 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17448 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17449 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17450 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17451 i128::from(n),
17452 precision,
17453 scale,
17454 col_name,
17455 )?),
17456 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17457 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17458 #[allow(clippy::cast_precision_loss)]
17459 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17460 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17461 i128::from(n),
17462 precision,
17463 scale,
17464 col_name,
17465 )?),
17466 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17467 Some(numeric_from_float(x, precision, scale, col_name)?)
17468 }
17469 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17480 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17481 return Err(EngineError::Eval(EvalError::TypeMismatch {
17482 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17483 }));
17484 };
17485 Some(numeric_rescale(
17486 mantissa, src_scale, precision, scale, col_name,
17487 )?)
17488 }
17489 (Value::Text(s), DataType::Date) => {
17491 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17492 EngineError::Eval(EvalError::TypeMismatch {
17493 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17494 })
17495 })?;
17496 Some(Value::Date(d))
17497 }
17498 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17505 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17506 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17507 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17508 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17509 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17510 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17511 _ => None,
17512 },
17513 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17522 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17523 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17524 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17528 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17529 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17537 (Value::Text(s), DataType::Bytes) => {
17544 let bytes = decode_bytea_literal(&s).map_err(|e| {
17545 EngineError::Eval(EvalError::TypeMismatch {
17546 detail: alloc::format!(
17547 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17548 ),
17549 })
17550 })?;
17551 Some(Value::Bytes(bytes))
17552 }
17553 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17557 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17565 Some(b) => Some(Value::Uuid(b)),
17566 None => {
17567 return Err(EngineError::Eval(EvalError::TypeMismatch {
17568 detail: alloc::format!(
17569 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17570 ),
17571 }));
17572 }
17573 },
17574 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17579 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17585 Some(us) => Some(Value::Time(us)),
17586 None => {
17587 return Err(EngineError::Eval(EvalError::TypeMismatch {
17588 detail: alloc::format!(
17589 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17590 ),
17591 }));
17592 }
17593 },
17594 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17596 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17601 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17602 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17603 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17607 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17608 Err(_) => {
17609 return Err(EngineError::Eval(EvalError::TypeMismatch {
17610 detail: alloc::format!(
17611 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17612 ),
17613 }));
17614 }
17615 },
17616 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17618 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17622 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17623 None => {
17624 return Err(EngineError::Eval(EvalError::TypeMismatch {
17625 detail: alloc::format!(
17626 "invalid input syntax for type time with time zone: \
17627 {s:?} (column `{col_name}`)"
17628 ),
17629 }));
17630 }
17631 },
17632 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17634 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17635 }
17636 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17640 Some(c) => Some(Value::Money(c)),
17641 None => {
17642 return Err(EngineError::Eval(EvalError::TypeMismatch {
17643 detail: alloc::format!(
17644 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17645 ),
17646 }));
17647 }
17648 },
17649 (Value::SmallInt(n), DataType::Money) => {
17653 Some(Value::Money(i64::from(n).saturating_mul(100)))
17654 }
17655 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17656 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17657 (Value::Float(x), DataType::Money) => {
17658 let scaled = x * 100.0;
17661 let cents = if scaled >= 0.0 {
17662 (scaled + 0.5) as i64
17663 } else {
17664 (scaled - 0.5) as i64
17665 };
17666 Some(Value::Money(cents))
17667 }
17668 (Value::Numeric { scaled, scale }, DataType::Money) => {
17669 let cents = if scale == 2 {
17672 scaled
17673 } else if scale < 2 {
17674 let mult = 10_i128.pow(u32::from(2 - scale));
17675 scaled.saturating_mul(mult)
17676 } else {
17677 let div = 10_i128.pow(u32::from(scale - 2));
17678 let half = div / 2;
17679 let bias = if scaled >= 0 { half } else { -half };
17680 (scaled + bias) / div
17681 };
17682 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17683 }
17684 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17686 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17690 Some(v) => Some(v),
17691 None => {
17692 return Err(EngineError::Eval(EvalError::TypeMismatch {
17693 detail: alloc::format!(
17694 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17695 ),
17696 }));
17697 }
17698 },
17699 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17701 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17703 Some(pairs) => Some(Value::Hstore(pairs)),
17704 None => {
17705 return Err(EngineError::Eval(EvalError::TypeMismatch {
17706 detail: alloc::format!(
17707 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17708 ),
17709 }));
17710 }
17711 },
17712 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17714 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17717 Ok(m) => Some(Value::IntArray2D(m)),
17718 Err(e) => {
17719 return Err(EngineError::Eval(EvalError::TypeMismatch {
17720 detail: alloc::format!(
17721 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17722 ),
17723 }));
17724 }
17725 },
17726 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17727 Ok(m) => Some(Value::BigIntArray2D(m)),
17728 Err(e) => {
17729 return Err(EngineError::Eval(EvalError::TypeMismatch {
17730 detail: alloc::format!(
17731 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17732 ),
17733 }));
17734 }
17735 },
17736 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17737 Ok(m) => Some(Value::TextArray2D(m)),
17738 Err(e) => {
17739 return Err(EngineError::Eval(EvalError::TypeMismatch {
17740 detail: alloc::format!(
17741 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17742 ),
17743 }));
17744 }
17745 },
17746 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17748 (Value::BigIntArray2D(rows), DataType::Text) => {
17749 Some(Value::Text(format_bigint_2d_text(&rows)))
17750 }
17751 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17752 (Value::Text(s), DataType::TextArray) => {
17757 let arr = decode_text_array_literal(&s).map_err(|e| {
17758 EngineError::Eval(EvalError::TypeMismatch {
17759 detail: alloc::format!(
17760 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17761 ),
17762 })
17763 })?;
17764 Some(Value::TextArray(arr))
17765 }
17766 (Value::Text(s), DataType::IntArray) => {
17772 let arr = decode_text_array_literal(&s).map_err(|e| {
17773 EngineError::Eval(EvalError::TypeMismatch {
17774 detail: alloc::format!(
17775 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17776 ),
17777 })
17778 })?;
17779 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17780 for elem in arr {
17781 match elem {
17782 None => out.push(None),
17783 Some(t) => {
17784 let n: i32 = t.parse().map_err(|_| {
17785 EngineError::Eval(EvalError::TypeMismatch {
17786 detail: alloc::format!(
17787 "cannot parse {t:?} as INT element for `{col_name}`"
17788 ),
17789 })
17790 })?;
17791 out.push(Some(n));
17792 }
17793 }
17794 }
17795 Some(Value::IntArray(out))
17796 }
17797 (Value::Text(s), DataType::BigIntArray) => {
17798 let arr = decode_text_array_literal(&s).map_err(|e| {
17799 EngineError::Eval(EvalError::TypeMismatch {
17800 detail: alloc::format!(
17801 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17802 ),
17803 })
17804 })?;
17805 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17806 for elem in arr {
17807 match elem {
17808 None => out.push(None),
17809 Some(t) => {
17810 let n: i64 = t.parse().map_err(|_| {
17811 EngineError::Eval(EvalError::TypeMismatch {
17812 detail: alloc::format!(
17813 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17814 ),
17815 })
17816 })?;
17817 out.push(Some(n));
17818 }
17819 }
17820 }
17821 Some(Value::BigIntArray(out))
17822 }
17823 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17827 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17836 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17837 EngineError::Eval(EvalError::TypeMismatch {
17838 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17839 })
17840 })?;
17841 if parsed.len() != dim as usize {
17842 return Err(EngineError::Eval(EvalError::TypeMismatch {
17843 detail: alloc::format!(
17844 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17845 parsed.len()
17846 ),
17847 }));
17848 }
17849 Some(match encoding {
17850 VecEncoding::F32 => Value::Vector(parsed),
17851 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17852 VecEncoding::F16 => {
17853 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17854 }
17855 })
17856 }
17857 (Value::Text(s), DataType::TsVector) => {
17867 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17868 EngineError::Eval(EvalError::TypeMismatch {
17869 detail: alloc::format!(
17870 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17871 ),
17872 })
17873 })?;
17874 Some(Value::TsVector(lexs))
17875 }
17876 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17877 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17878 EngineError::Eval(EvalError::TypeMismatch {
17879 detail: alloc::format!(
17880 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17881 ),
17882 })
17883 })?;
17884 Some(Value::Timestamp(t))
17885 }
17886 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17889 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17890 }
17891 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17895 (Value::Timestamp(t), DataType::Date) => {
17896 let days = t.div_euclid(86_400_000_000);
17897 i32::try_from(days).ok().map(Value::Date)
17898 }
17899 (
17900 Value::Numeric {
17901 scaled,
17902 scale: src_scale,
17903 },
17904 DataType::Numeric { precision, scale },
17905 ) => Some(numeric_rescale(
17906 scaled, src_scale, precision, scale, col_name,
17907 )?),
17908 #[allow(clippy::cast_precision_loss)]
17909 (Value::Numeric { scaled, scale }, DataType::Float) => {
17910 let mut div = 1.0_f64;
17911 for _ in 0..scale {
17912 div *= 10.0;
17913 }
17914 Some(Value::Float((scaled as f64) / div))
17915 }
17916 (Value::Numeric { scaled, scale }, DataType::Int) => {
17917 let truncated = numeric_truncate_to_integer(scaled, scale);
17918 i32::try_from(truncated).ok().map(Value::Int)
17919 }
17920 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17921 let truncated = numeric_truncate_to_integer(scaled, scale);
17922 i64::try_from(truncated).ok().map(Value::BigInt)
17923 }
17924 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17925 let truncated = numeric_truncate_to_integer(scaled, scale);
17926 i16::try_from(truncated).ok().map(Value::SmallInt)
17927 }
17928 (Value::Text(s), DataType::Varchar(max)) => {
17930 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17931 Some(Value::Text(s))
17932 } else {
17933 return Err(EngineError::Unsupported(alloc::format!(
17934 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17935 {} chars",
17936 s.chars().count()
17937 )));
17938 }
17939 }
17940 (
17948 Value::Vector(v),
17949 DataType::Vector {
17950 dim,
17951 encoding: VecEncoding::Sq8,
17952 },
17953 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17954 (
17959 Value::Vector(v),
17960 DataType::Vector {
17961 dim,
17962 encoding: VecEncoding::F16,
17963 },
17964 ) if v.len() == dim as usize => Some(Value::HalfVector(
17965 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17966 )),
17967 (Value::Text(s), DataType::Char(size)) => {
17971 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17972 if len > size {
17973 return Err(EngineError::Unsupported(alloc::format!(
17974 "value for CHAR({size}) column `{col_name}` exceeds length: \
17975 {len} chars"
17976 )));
17977 }
17978 let need = (size - len) as usize;
17979 let mut padded = s;
17980 padded.reserve(need);
17981 for _ in 0..need {
17982 padded.push(' ');
17983 }
17984 Some(Value::Text(padded))
17985 }
17986 _ => None,
17987 };
17988 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17989 column: col_name.into(),
17990 expected,
17991 actual,
17992 position,
17993 }))
17994}
17995
17996fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
18002 use core::fmt::Write;
18003 let mut out = alloc::string::String::from("(");
18004 for (i, a) in args.iter().enumerate() {
18005 if i > 0 {
18006 out.push_str(", ");
18007 }
18008 match a.mode {
18009 spg_sql::ast::FunctionArgMode::In => {}
18010 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
18011 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
18012 }
18013 if let Some(n) = &a.name {
18014 out.push_str(n);
18015 out.push(' ');
18016 }
18017 match &a.ty {
18018 spg_sql::ast::FunctionArgType::Typed(t) => {
18019 let _ = write!(out, "{t}");
18020 }
18021 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
18022 }
18023 }
18024 out.push(')');
18025 out
18026}
18027
18028fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
18037 match expr {
18038 spg_sql::ast::Expr::FunctionCall { name, args } => {
18039 name.eq_ignore_ascii_case("unnest") && args.len() == 1
18040 }
18041 _ => false,
18042 }
18043}
18044
18045fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
18049 match expr {
18050 spg_sql::ast::Expr::FunctionCall { name, args }
18051 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
18052 {
18053 Some(&args[0])
18054 }
18055 _ => None,
18056 }
18057}
18058
18059fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
18064 match v {
18065 Value::Null => Ok(Vec::new()),
18066 Value::TextArray(items) => Ok(items
18067 .iter()
18068 .map(|opt| {
18069 opt.as_ref()
18070 .map(|s| Value::Text(s.clone()))
18071 .unwrap_or(Value::Null)
18072 })
18073 .collect()),
18074 Value::IntArray(items) => Ok(items
18075 .iter()
18076 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
18077 .collect()),
18078 Value::BigIntArray(items) => Ok(items
18079 .iter()
18080 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
18081 .collect()),
18082 other => Err(EngineError::Eval(EvalError::TypeMismatch {
18083 detail: alloc::format!(
18084 "unnest() expects an array argument, got {:?}",
18085 other.data_type()
18086 ),
18087 })),
18088 }
18089}
18090
18091#[cfg(test)]
18092mod tests {
18093 use super::*;
18094 use alloc::vec;
18095
18096 fn unwrap_command_ok(r: &QueryResult) -> usize {
18097 match r {
18098 QueryResult::CommandOk { affected, .. } => *affected,
18099 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
18100 }
18101 }
18102
18103 #[test]
18104 fn update_seek_positions_engages_on_indexed_eq() {
18105 let mut e = Engine::new();
18106 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
18107 .unwrap();
18108 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
18109 for i in 0..100 {
18110 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
18111 .unwrap();
18112 }
18113 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
18114 .expect("parse");
18115 let Statement::Update(u) = stmt else {
18116 panic!("expected Update, got {stmt:?}");
18117 };
18118 let w = u.where_.as_ref().expect("where");
18119 let table = e.catalog().get("b").unwrap();
18120 let schema_cols = table.schema().columns.clone();
18121 let Expr::Binary { lhs, op, rhs } = w else {
18123 panic!("WHERE not Binary: {w:?}");
18124 };
18125 assert_eq!(*op, BinOp::Eq, "op not Eq");
18126 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
18127 assert!(
18128 pair.is_some(),
18129 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
18130 );
18131 let (col_pos, value) = pair.unwrap();
18132 assert!(
18133 table.index_on(col_pos).is_some(),
18134 "no index on col {col_pos}"
18135 );
18136 assert!(
18137 IndexKey::from_value(&value).is_some(),
18138 "IndexKey::from_value None for {value:?}"
18139 );
18140 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
18141 assert_eq!(positions, Some(vec![42]), "seek did not engage");
18142 }
18143
18144 #[test]
18145 fn create_table_registers_schema() {
18146 let mut e = Engine::new();
18147 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
18148 .unwrap();
18149 assert_eq!(e.catalog().table_count(), 1);
18150 let t = e.catalog().get("foo").unwrap();
18151 assert_eq!(t.schema().columns.len(), 2);
18152 assert_eq!(t.schema().columns[0].ty, DataType::Int);
18153 assert!(!t.schema().columns[0].nullable);
18154 assert_eq!(t.schema().columns[1].ty, DataType::Text);
18155 }
18156
18157 #[test]
18158 fn create_table_vector_default_is_f32_encoded() {
18159 let mut e = Engine::new();
18160 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
18161 let t = e.catalog().get("t").unwrap();
18162 assert_eq!(
18163 t.schema().columns[0].ty,
18164 DataType::Vector {
18165 dim: 8,
18166 encoding: VecEncoding::F32,
18167 },
18168 );
18169 }
18170
18171 #[test]
18172 fn create_table_vector_using_sq8_succeeds() {
18173 let mut e = Engine::new();
18177 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
18178 let t = e.catalog().get("t").unwrap();
18179 assert_eq!(
18180 t.schema().columns[0].ty,
18181 DataType::Vector {
18182 dim: 8,
18183 encoding: VecEncoding::Sq8,
18184 },
18185 );
18186 }
18187
18188 #[test]
18189 fn insert_into_sq8_column_quantises_f32_payload() {
18190 let mut e = Engine::new();
18197 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18198 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18199 .unwrap();
18200 let t = e.catalog().get("t").unwrap();
18201 assert_eq!(t.rows().len(), 1);
18202 match &t.rows()[0].values[0] {
18203 Value::Sq8Vector(q) => {
18204 assert_eq!(q.bytes.len(), 4);
18205 assert!((q.min - 0.0).abs() < 1e-6);
18207 assert!((q.max - 1.0).abs() < 1e-6);
18208 }
18209 other => panic!("expected Sq8Vector cell, got {other:?}"),
18210 }
18211 }
18212
18213 #[test]
18214 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
18215 let mut e = Engine::new();
18222 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18223 .unwrap();
18224 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18225 .unwrap();
18226 let t = e.catalog().get("t").unwrap();
18227 assert_eq!(t.rows().len(), 1);
18228 match &t.rows()[0].values[0] {
18229 Value::HalfVector(h) => {
18230 assert_eq!(h.dim(), 4);
18231 let back = h.to_f32_vec();
18232 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
18233 for (g, e) in back.iter().zip(expected.iter()) {
18234 assert!(
18235 (g - e).abs() < 1e-6,
18236 "{g} vs {e} should be exact on f16 grid"
18237 );
18238 }
18239 }
18240 other => panic!("expected HalfVector cell, got {other:?}"),
18241 }
18242 }
18243
18244 #[test]
18245 fn alter_index_rebuild_in_place_succeeds() {
18246 let mut e = Engine::new();
18251 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18252 .unwrap();
18253 for i in 0..8_i32 {
18254 #[allow(clippy::cast_precision_loss)]
18255 let base = (i as f32) * 0.1;
18256 e.execute(&alloc::format!(
18257 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
18258 b1 = base + 0.01,
18259 b2 = base + 0.02,
18260 ))
18261 .unwrap();
18262 }
18263 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18264 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18265 assert_eq!(
18267 e.catalog().get("t").unwrap().schema().columns[1].ty,
18268 DataType::Vector {
18269 dim: 3,
18270 encoding: VecEncoding::F32,
18271 },
18272 );
18273 }
18274
18275 #[test]
18276 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18277 let mut e = Engine::new();
18282 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18283 .unwrap();
18284 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18285 .unwrap();
18286 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18287 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18288 .unwrap();
18289 let t = e.catalog().get("t").unwrap();
18290 assert_eq!(
18291 t.schema().columns[1].ty,
18292 DataType::Vector {
18293 dim: 4,
18294 encoding: VecEncoding::Sq8,
18295 },
18296 );
18297 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18298 }
18299
18300 #[test]
18301 fn alter_index_rebuild_unknown_index_errors() {
18302 let mut e = Engine::new();
18303 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18304 assert!(
18305 matches!(
18306 &err,
18307 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18308 ),
18309 "got: {err}"
18310 );
18311 }
18312
18313 #[test]
18314 fn alter_index_rebuild_on_btree_index_errors() {
18315 let mut e = Engine::new();
18318 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18319 e.execute("INSERT INTO t VALUES (1)").unwrap();
18320 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18321 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18322 assert!(
18323 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18324 "got: {err}"
18325 );
18326 }
18327
18328 #[test]
18329 fn prepared_insert_substitutes_placeholders() {
18330 let mut e = Engine::new();
18336 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18337 .unwrap();
18338 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18339 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18340 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18341 .unwrap();
18342 }
18343 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18345 let QueryResult::Rows { rows, .. } = rows_result else {
18346 panic!("expected Rows")
18347 };
18348 assert_eq!(rows.len(), 3);
18349 }
18350
18351 #[test]
18352 fn prepared_select_with_placeholder_filters_rows() {
18353 let mut e = Engine::new();
18354 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18355 .unwrap();
18356 for i in 0..10_i32 {
18357 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18358 .unwrap();
18359 }
18360 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18361 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18362 else {
18363 panic!("expected Rows")
18364 };
18365 assert_eq!(rows.len(), 1);
18367 assert_eq!(rows[0].values[0], Value::Int(5));
18368 }
18369
18370 #[test]
18371 fn prepared_too_few_params_errors() {
18372 let mut e = Engine::new();
18373 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18374 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18375 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18376 assert!(
18377 matches!(
18378 &err,
18379 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18380 ),
18381 "got: {err}"
18382 );
18383 }
18384
18385 #[test]
18386 fn bytea_cast_round_trips_text_input() {
18387 let e = Engine::new();
18390 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18391 let QueryResult::Rows { rows, .. } = r else {
18392 panic!("expected Rows")
18393 };
18394 assert_eq!(rows.len(), 1);
18395 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18396 }
18397
18398 #[test]
18399 fn bytea_cast_pg_escape_hex_form() {
18400 let e = Engine::new();
18404 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18405 let QueryResult::Rows { rows, .. } = r else {
18406 panic!("expected Rows")
18407 };
18408 assert_eq!(
18409 rows[0].values[0],
18410 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18411 );
18412 }
18413
18414 #[test]
18415 fn bytea_cast_chains_through_octet_length() {
18416 let e = Engine::new();
18420 let r = e
18421 .execute_readonly("SELECT octet_length('hello'::bytea)")
18422 .unwrap();
18423 let QueryResult::Rows { rows, .. } = r else {
18424 panic!("expected Rows")
18425 };
18426 match &rows[0].values[0] {
18427 Value::Int(n) => assert_eq!(*n, 5),
18428 Value::BigInt(n) => assert_eq!(*n, 5),
18429 other => panic!("expected integer length, got {other:?}"),
18430 }
18431 }
18432
18433 #[test]
18434 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18435 let mut e = Engine::new();
18441 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18442 .unwrap();
18443 for i in 0..10_i32 {
18444 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18445 .unwrap();
18446 }
18447 let snapshot = e.clone_snapshot();
18448 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18449 let QueryResult::Rows { rows, .. } =
18450 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18451 .unwrap()
18452 else {
18453 panic!("expected Rows")
18454 };
18455 assert_eq!(rows.len(), 1);
18456 assert_eq!(rows[0].values[0], Value::Int(5));
18457 }
18458
18459 #[test]
18460 fn readonly_prepared_on_snapshot_rejects_writes() {
18461 let mut e = Engine::new();
18465 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18466 let snapshot = e.clone_snapshot();
18467 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18468 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18469 .unwrap_err();
18470 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18471 }
18472
18473 #[test]
18474 fn readonly_prepared_on_snapshot_frozen_view() {
18475 let mut e = Engine::new();
18481 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18482 e.execute("INSERT INTO t VALUES (1)").unwrap();
18483 let snapshot = e.clone_snapshot();
18484 e.execute("INSERT INTO t VALUES (2)").unwrap();
18485 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18486 let QueryResult::Rows { rows, .. } =
18487 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18488 .unwrap()
18489 else {
18490 panic!("expected Rows")
18491 };
18492 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18493 }
18494
18495 #[test]
18496 fn describe_prepared_on_snapshot_resolves_columns() {
18497 let mut e = Engine::new();
18502 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18503 .unwrap();
18504 let snapshot = e.clone_snapshot();
18505 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18506 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18507 assert_eq!(cols.len(), 2);
18508 assert_eq!(cols[0].name, "id");
18509 assert_eq!(cols[0].ty, DataType::Int);
18510 assert_eq!(cols[1].name, "name");
18511 assert_eq!(cols[1].ty, DataType::Text);
18512 }
18513
18514 #[test]
18515 fn insert_into_half_column_dim_mismatch_errors() {
18516 let mut e = Engine::new();
18517 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18518 .unwrap();
18519 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18520 assert!(matches!(
18521 &err,
18522 EngineError::Storage(StorageError::TypeMismatch { .. })
18523 ));
18524 }
18525
18526 #[test]
18527 fn insert_into_sq8_column_dim_mismatch_errors() {
18528 let mut e = Engine::new();
18533 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18534 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18535 assert!(
18536 matches!(
18537 &err,
18538 EngineError::Storage(StorageError::TypeMismatch { .. })
18539 ),
18540 "got: {err}",
18541 );
18542 }
18543
18544 #[test]
18545 fn create_table_duplicate_errors() {
18546 let mut e = Engine::new();
18547 e.execute("CREATE TABLE foo (a INT)").unwrap();
18548 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18549 assert!(matches!(
18550 err,
18551 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18552 ));
18553 }
18554
18555 #[test]
18556 fn insert_into_unknown_table_errors() {
18557 let mut e = Engine::new();
18558 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18559 assert!(matches!(
18560 err,
18561 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18562 ));
18563 }
18564
18565 #[test]
18566 fn insert_happy_path_reports_one_affected() {
18567 let mut e = Engine::new();
18568 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18569 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18570 assert_eq!(unwrap_command_ok(&r), 1);
18571 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18572 }
18573
18574 #[test]
18575 fn insert_arity_mismatch_propagates() {
18576 let mut e = Engine::new();
18577 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18578 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18579 assert!(matches!(
18580 err,
18581 EngineError::Storage(StorageError::ArityMismatch { .. })
18582 ));
18583 }
18584
18585 #[test]
18586 fn insert_negative_integer_via_unary_minus() {
18587 let mut e = Engine::new();
18588 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18589 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18590 let rows = e.catalog().get("foo").unwrap().rows();
18591 assert_eq!(rows[0].values[0], Value::Int(-7));
18592 }
18593
18594 #[test]
18595 fn insert_expression_evaluated_against_empty_context() {
18596 let mut e = Engine::new();
18601 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18602 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18603 let rows = e.catalog().get("foo").unwrap().rows();
18604 assert_eq!(rows[0].values[0], Value::Int(3));
18605 }
18606
18607 #[test]
18608 fn select_star_returns_all_rows_in_insertion_order() {
18609 let mut e = Engine::new();
18610 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18611 .unwrap();
18612 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18613 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18614 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18615
18616 let r = e.execute("SELECT * FROM foo").unwrap();
18617 let QueryResult::Rows { columns, rows } = r else {
18618 panic!("expected Rows")
18619 };
18620 assert_eq!(columns.len(), 2);
18621 assert_eq!(columns[0].name, "a");
18622 assert_eq!(rows.len(), 3);
18623 assert_eq!(
18624 rows[1].values,
18625 vec![Value::Int(2), Value::Text("two".into())]
18626 );
18627 }
18628
18629 #[test]
18630 fn select_star_on_empty_table_returns_zero_rows() {
18631 let mut e = Engine::new();
18632 e.execute("CREATE TABLE foo (a INT)").unwrap();
18633 let r = e.execute("SELECT * FROM foo").unwrap();
18634 match r {
18635 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18636 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18637 }
18638 }
18639
18640 fn make_three_row_users(e: &mut Engine) {
18643 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18644 .unwrap();
18645 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18646 .unwrap();
18647 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18648 .unwrap();
18649 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18650 .unwrap();
18651 }
18652
18653 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18654 match r {
18655 QueryResult::Rows { columns, rows } => (columns, rows),
18656 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18657 }
18658 }
18659
18660 #[test]
18661 fn where_filter_passes_only_true_rows() {
18662 let mut e = Engine::new();
18663 make_three_row_users(&mut e);
18664 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18665 let (_, rows) = unwrap_rows(r);
18666 assert_eq!(rows.len(), 2);
18667 assert_eq!(rows[0].values[0], Value::Int(2));
18668 assert_eq!(rows[1].values[0], Value::Int(3));
18669 }
18670
18671 #[test]
18672 fn where_with_null_result_filters_out_row() {
18673 let mut e = Engine::new();
18674 make_three_row_users(&mut e);
18675 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18677 let (_, rows) = unwrap_rows(r);
18678 assert_eq!(rows.len(), 1);
18679 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18680 }
18681
18682 #[test]
18683 fn projection_named_columns() {
18684 let mut e = Engine::new();
18685 make_three_row_users(&mut e);
18686 let r = e.execute("SELECT name, score FROM users").unwrap();
18687 let (cols, rows) = unwrap_rows(r);
18688 assert_eq!(cols.len(), 2);
18689 assert_eq!(cols[0].name, "name");
18690 assert_eq!(cols[1].name, "score");
18691 assert_eq!(rows.len(), 3);
18692 assert_eq!(
18693 rows[0].values,
18694 vec![Value::Text("alice".into()), Value::Int(90)]
18695 );
18696 }
18697
18698 #[test]
18699 fn projection_with_column_alias() {
18700 let mut e = Engine::new();
18701 make_three_row_users(&mut e);
18702 let r = e
18703 .execute("SELECT name AS who FROM users WHERE id = 1")
18704 .unwrap();
18705 let (cols, rows) = unwrap_rows(r);
18706 assert_eq!(cols[0].name, "who");
18707 assert_eq!(rows.len(), 1);
18708 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18709 }
18710
18711 #[test]
18712 fn qualified_column_with_table_alias_resolves() {
18713 let mut e = Engine::new();
18714 make_three_row_users(&mut e);
18715 let r = e
18716 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18717 .unwrap();
18718 let (cols, rows) = unwrap_rows(r);
18719 assert_eq!(cols.len(), 2);
18720 assert_eq!(rows.len(), 2);
18721 }
18722
18723 #[test]
18724 fn qualified_column_with_wrong_alias_errors() {
18725 let mut e = Engine::new();
18726 make_three_row_users(&mut e);
18727 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18728 assert!(matches!(
18729 err,
18730 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18731 ));
18732 }
18733
18734 #[test]
18735 fn select_unknown_column_errors_in_projection() {
18736 let mut e = Engine::new();
18737 make_three_row_users(&mut e);
18738 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18739 assert!(matches!(
18740 err,
18741 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18742 ));
18743 }
18744
18745 #[test]
18746 fn where_unknown_column_errors() {
18747 let mut e = Engine::new();
18748 make_three_row_users(&mut e);
18749 let err = e
18750 .execute("SELECT * FROM users WHERE ghost = 1")
18751 .unwrap_err();
18752 assert!(matches!(
18753 err,
18754 EngineError::Eval(EvalError::ColumnNotFound { .. })
18755 ));
18756 }
18757
18758 #[test]
18759 fn expression_projection_evaluates_and_renders() {
18760 let mut e = Engine::new();
18763 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18764 e.execute("INSERT INTO t VALUES (3)").unwrap();
18765 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18766 assert_eq!(rows.len(), 1);
18767 assert_eq!(rows[0].values[0], Value::Int(3));
18770 }
18771
18772 #[test]
18773 fn select_unknown_table_errors() {
18774 let mut e = Engine::new();
18775 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18776 assert!(matches!(
18777 err,
18778 EngineError::Storage(StorageError::TableNotFound { .. })
18779 ));
18780 }
18781
18782 #[test]
18783 fn invalid_sql_returns_parse_error() {
18784 let mut e = Engine::new();
18787 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18788 assert!(matches!(err, EngineError::Parse(_)));
18789 }
18790
18791 #[test]
18794 fn create_index_registers_on_table() {
18795 let mut e = Engine::new();
18796 make_three_row_users(&mut e);
18797 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18798 let t = e.catalog().get("users").unwrap();
18799 assert_eq!(t.indices().len(), 1);
18800 assert_eq!(t.indices()[0].name, "by_name");
18801 }
18802
18803 #[test]
18804 fn create_index_on_unknown_table_errors() {
18805 let mut e = Engine::new();
18806 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18807 assert!(matches!(
18808 err,
18809 EngineError::Storage(StorageError::TableNotFound { .. })
18810 ));
18811 }
18812
18813 #[test]
18814 fn create_index_on_unknown_column_errors() {
18815 let mut e = Engine::new();
18816 make_three_row_users(&mut e);
18817 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18818 assert!(matches!(
18819 err,
18820 EngineError::Storage(StorageError::ColumnNotFound { .. })
18821 ));
18822 }
18823
18824 #[test]
18825 fn select_eq_uses_index_returns_same_rows_as_scan() {
18826 let mut without = Engine::new();
18830 make_three_row_users(&mut without);
18831 let mut with = Engine::new();
18832 make_three_row_users(&mut with);
18833 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18834
18835 let q = "SELECT * FROM users WHERE id = 2";
18836 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18837 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18838 assert_eq!(no_idx_rows, idx_rows);
18839 assert_eq!(idx_rows.len(), 1);
18840 }
18841
18842 #[test]
18843 fn select_eq_with_no_matching_index_value_returns_empty() {
18844 let mut e = Engine::new();
18845 make_three_row_users(&mut e);
18846 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18847 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18848 assert_eq!(rows.len(), 0);
18849 }
18850
18851 #[test]
18854 fn begin_sets_in_transaction_flag() {
18855 let mut e = Engine::new();
18856 assert!(!e.in_transaction());
18857 e.execute("BEGIN").unwrap();
18858 assert!(e.in_transaction());
18859 }
18860
18861 #[test]
18862 fn double_begin_errors() {
18863 let mut e = Engine::new();
18864 e.execute("BEGIN").unwrap();
18865 let err = e.execute("BEGIN").unwrap_err();
18866 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18867 }
18868
18869 #[test]
18870 fn commit_without_begin_errors() {
18871 let mut e = Engine::new();
18872 let err = e.execute("COMMIT").unwrap_err();
18873 assert_eq!(err, EngineError::NoActiveTransaction);
18874 }
18875
18876 #[test]
18877 fn rollback_without_begin_errors() {
18878 let mut e = Engine::new();
18879 let err = e.execute("ROLLBACK").unwrap_err();
18880 assert_eq!(err, EngineError::NoActiveTransaction);
18881 }
18882
18883 #[test]
18884 fn commit_applies_shadow_to_committed_catalog() {
18885 let mut e = Engine::new();
18886 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18887 e.execute("BEGIN").unwrap();
18888 e.execute("INSERT INTO t VALUES (1)").unwrap();
18889 e.execute("INSERT INTO t VALUES (2)").unwrap();
18890 e.execute("COMMIT").unwrap();
18891 assert!(!e.in_transaction());
18892 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18893 }
18894
18895 #[test]
18896 fn rollback_discards_shadow() {
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("ROLLBACK").unwrap();
18903 assert!(!e.in_transaction());
18904 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18905 }
18906
18907 #[test]
18908 fn select_during_tx_sees_uncommitted_writes_own_session() {
18909 let mut e = Engine::new();
18912 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18913 e.execute("BEGIN").unwrap();
18914 e.execute("INSERT INTO t VALUES (42)").unwrap();
18915 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18916 assert_eq!(rows.len(), 1);
18917 assert_eq!(rows[0].values[0], Value::Int(42));
18918 }
18919
18920 #[test]
18921 fn snapshot_with_no_users_is_bare_catalog_format() {
18922 let mut e = Engine::new();
18923 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18924 let bytes = e.snapshot();
18925 assert_eq!(
18926 &bytes[..8],
18927 b"SPGDB001",
18928 "must be the bare v3.x catalog magic"
18929 );
18930 let e2 = Engine::restore_envelope(&bytes).unwrap();
18931 assert!(e2.users().is_empty());
18932 assert_eq!(e2.catalog().table_count(), 1);
18933 }
18934
18935 #[test]
18936 fn snapshot_with_users_round_trips_both_via_envelope() {
18937 let mut e = Engine::new();
18938 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18939 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18940 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18941 .unwrap();
18942 let bytes = e.snapshot();
18943 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18944 let e2 = Engine::restore_envelope(&bytes).unwrap();
18945 assert_eq!(e2.users().len(), 2);
18946 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18947 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18948 assert_eq!(e2.verify_user("alice", "wrong"), None);
18949 assert_eq!(e2.catalog().table_count(), 1);
18950 }
18951
18952 #[test]
18953 fn ddl_inside_tx_also_rolled_back() {
18954 let mut e = Engine::new();
18955 e.execute("BEGIN").unwrap();
18956 e.execute("CREATE TABLE t (v INT)").unwrap();
18957 e.execute("SELECT * FROM t").unwrap();
18959 e.execute("ROLLBACK").unwrap();
18960 let err = e.execute("SELECT * FROM t").unwrap_err();
18962 assert!(matches!(
18963 err,
18964 EngineError::Storage(StorageError::TableNotFound { .. })
18965 ));
18966 }
18967
18968 #[test]
18971 fn create_publication_lands_in_catalog() {
18972 let mut e = Engine::new();
18973 assert!(e.publications().is_empty());
18974 e.execute("CREATE PUBLICATION pub_a").unwrap();
18975 assert_eq!(e.publications().len(), 1);
18976 assert!(e.publications().contains("pub_a"));
18977 }
18978
18979 #[test]
18980 fn create_publication_duplicate_errors() {
18981 let mut e = Engine::new();
18982 e.execute("CREATE PUBLICATION pub_a").unwrap();
18983 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18984 assert!(
18985 alloc::format!("{err:?}").contains("DuplicateName"),
18986 "got {err:?}"
18987 );
18988 }
18989
18990 #[test]
18991 fn drop_publication_silent_when_absent() {
18992 let mut e = Engine::new();
18993 let r = e.execute("DROP PUBLICATION nope").unwrap();
18996 match r {
18997 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18998 other => panic!("expected CommandOk, got {other:?}"),
18999 }
19000 }
19001
19002 #[test]
19003 fn drop_publication_present_reports_one_affected() {
19004 let mut e = Engine::new();
19005 e.execute("CREATE PUBLICATION pub_a").unwrap();
19006 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
19007 match r {
19008 QueryResult::CommandOk {
19009 affected,
19010 modified_catalog,
19011 } => {
19012 assert_eq!(affected, 1);
19013 assert!(modified_catalog);
19014 }
19015 other => panic!("expected CommandOk, got {other:?}"),
19016 }
19017 assert!(e.publications().is_empty());
19018 }
19019
19020 #[test]
19021 fn publications_persist_across_snapshot_restore() {
19022 let mut e = Engine::new();
19027 e.execute("CREATE PUBLICATION pub_a").unwrap();
19028 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
19029 .unwrap();
19030 let snap = e.snapshot();
19031 let e2 = Engine::restore_envelope(&snap).unwrap();
19032 assert_eq!(e2.publications().len(), 2);
19033 assert!(e2.publications().contains("pub_a"));
19034 assert!(e2.publications().contains("pub_b"));
19035 }
19036
19037 #[test]
19038 fn create_publication_allowed_inside_transaction() {
19039 let mut e = Engine::new();
19043 e.execute("BEGIN").unwrap();
19044 e.execute("CREATE PUBLICATION pub_a").unwrap();
19045 e.execute("COMMIT").unwrap();
19046 assert!(e.publications().contains("pub_a"));
19047 }
19048
19049 #[test]
19052 fn create_publication_for_table_list_lands_with_scope() {
19053 let mut e = Engine::new();
19054 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19055 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
19056 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
19057 .unwrap();
19058 let scope = e.publications().get("pub_a").cloned();
19059 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
19060 panic!("expected ForTables scope, got {scope:?}")
19061 };
19062 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19063 }
19064
19065 #[test]
19066 fn create_publication_all_tables_except_lands_with_scope() {
19067 let mut e = Engine::new();
19068 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
19069 .unwrap();
19070 let scope = e.publications().get("pub_a").cloned();
19071 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
19072 panic!("expected AllTablesExcept scope, got {scope:?}")
19073 };
19074 assert_eq!(ts, alloc::vec!["t3".to_string()]);
19075 }
19076
19077 #[test]
19078 fn show_publications_empty_returns_zero_rows() {
19079 let e = Engine::new();
19080 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19081 let QueryResult::Rows { rows, columns } = r else {
19082 panic!()
19083 };
19084 assert!(rows.is_empty());
19085 assert_eq!(columns.len(), 3);
19086 assert_eq!(columns[0].name, "name");
19087 assert_eq!(columns[1].name, "scope");
19088 assert_eq!(columns[2].name, "table_count");
19089 }
19090
19091 #[test]
19092 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
19093 let mut e = Engine::new();
19094 e.execute("CREATE PUBLICATION z_pub").unwrap();
19095 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
19096 .unwrap();
19097 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
19098 .unwrap();
19099 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19100 let QueryResult::Rows { rows, .. } = r else {
19101 panic!()
19102 };
19103 assert_eq!(rows.len(), 3);
19104 let names: Vec<&str> = rows
19106 .iter()
19107 .map(|r| {
19108 if let Value::Text(s) = &r.values[0] {
19109 s.as_str()
19110 } else {
19111 panic!()
19112 }
19113 })
19114 .collect();
19115 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
19116 match &rows[0].values[1] {
19118 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
19119 other => panic!("expected Text, got {other:?}"),
19120 }
19121 assert_eq!(rows[0].values[2], Value::Int(2));
19122 match &rows[1].values[1] {
19124 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
19125 other => panic!("expected Text, got {other:?}"),
19126 }
19127 assert_eq!(rows[1].values[2], Value::Int(1));
19128 match &rows[2].values[1] {
19130 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
19131 other => panic!("expected Text, got {other:?}"),
19132 }
19133 assert_eq!(rows[2].values[2], Value::Null);
19134 }
19135
19136 #[test]
19137 fn for_list_scopes_persist_across_snapshot() {
19138 let mut e = Engine::new();
19141 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
19142 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
19143 .unwrap();
19144 let snap = e.snapshot();
19145 let e2 = Engine::restore_envelope(&snap).unwrap();
19146 assert_eq!(e2.publications().len(), 2);
19147 let p1 = e2.publications().get("p1").cloned();
19148 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
19149 panic!("p1 scope lost: {p1:?}")
19150 };
19151 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19152 let p2 = e2.publications().get("p2").cloned();
19153 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
19154 panic!("p2 scope lost: {p2:?}")
19155 };
19156 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
19157 }
19158
19159 #[test]
19162 fn create_subscription_lands_in_catalog_with_defaults() {
19163 let mut e = Engine::new();
19164 e.execute(
19165 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
19166 )
19167 .unwrap();
19168 let s = e.subscriptions().get("sub_a").cloned().expect("present");
19169 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
19170 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
19171 assert!(s.enabled);
19172 assert_eq!(s.last_received_pos, 0);
19173 }
19174
19175 #[test]
19176 fn create_subscription_duplicate_name_errors() {
19177 let mut e = Engine::new();
19178 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
19179 .unwrap();
19180 let err = e
19181 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
19182 .unwrap_err();
19183 assert!(
19184 alloc::format!("{err:?}").contains("DuplicateName"),
19185 "got {err:?}"
19186 );
19187 }
19188
19189 #[test]
19190 fn drop_subscription_silent_when_absent() {
19191 let mut e = Engine::new();
19192 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
19193 match r {
19194 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19195 other => panic!("expected CommandOk, got {other:?}"),
19196 }
19197 }
19198
19199 #[test]
19200 fn subscription_advance_updates_last_pos_monotone() {
19201 let mut e = Engine::new();
19202 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19203 .unwrap();
19204 assert!(e.subscription_advance("s", 100));
19205 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19206 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19208 assert!(e.subscription_advance("s", 200));
19209 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
19210 assert!(!e.subscription_advance("missing", 1));
19211 }
19212
19213 #[test]
19214 fn show_subscriptions_returns_rows_ordered_by_name() {
19215 let mut e = Engine::new();
19216 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
19217 .unwrap();
19218 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
19219 .unwrap();
19220 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
19221 let QueryResult::Rows { rows, columns } = r else {
19222 panic!()
19223 };
19224 assert_eq!(rows.len(), 2);
19225 assert_eq!(columns.len(), 5);
19226 assert_eq!(columns[0].name, "name");
19227 assert_eq!(columns[4].name, "last_received_pos");
19228 let names: Vec<&str> = rows
19230 .iter()
19231 .map(|r| {
19232 if let Value::Text(s) = &r.values[0] {
19233 s.as_str()
19234 } else {
19235 panic!()
19236 }
19237 })
19238 .collect();
19239 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
19240 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
19242 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
19243 assert_eq!(rows[0].values[3], Value::Bool(true));
19244 assert_eq!(rows[0].values[4], Value::BigInt(0));
19245 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
19247 }
19248
19249 #[test]
19250 fn subscriptions_persist_across_snapshot_envelope_v4() {
19251 let mut e = Engine::new();
19252 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
19253 .unwrap();
19254 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
19255 .unwrap();
19256 e.subscription_advance("s2", 42);
19257 let snap = e.snapshot();
19258 let e2 = Engine::restore_envelope(&snap).unwrap();
19259 assert_eq!(e2.subscriptions().len(), 2);
19260 let s1 = e2.subscriptions().get("s1").unwrap();
19261 assert_eq!(s1.conn_str, "h=A");
19262 assert_eq!(
19263 s1.publications,
19264 alloc::vec!["p1".to_string(), "p2".to_string()]
19265 );
19266 assert_eq!(s1.last_received_pos, 0);
19267 let s2 = e2.subscriptions().get("s2").unwrap();
19268 assert_eq!(s2.last_received_pos, 42);
19269 }
19270
19271 #[test]
19272 fn v3_envelope_loads_with_empty_subscriptions() {
19273 let mut e = Engine::new();
19277 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19278 let catalog = e.catalog.serialize();
19279 let users = crate::users::serialize_users(&e.users);
19280 let pubs = e.publications.serialize();
19281 let mut buf = Vec::new();
19282 buf.extend_from_slice(b"SPGENV01");
19283 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19285 buf.extend_from_slice(&catalog);
19286 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19287 buf.extend_from_slice(&users);
19288 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19289 buf.extend_from_slice(&pubs);
19290 let crc = spg_crypto::crc32::crc32(&buf);
19291 buf.extend_from_slice(&crc.to_le_bytes());
19292
19293 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19294 assert!(e2.subscriptions().is_empty());
19295 assert!(e2.publications().contains("pub_legacy"));
19296 }
19297
19298 #[test]
19299 fn create_subscription_allowed_inside_transaction() {
19300 let mut e = Engine::new();
19301 e.execute("BEGIN").unwrap();
19302 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19303 .unwrap();
19304 e.execute("COMMIT").unwrap();
19305 assert!(e.subscriptions().contains("s"));
19306 }
19307
19308 #[test]
19310 fn analyze_populates_histogram_bounds() {
19311 let mut e = Engine::new();
19312 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19313 .unwrap();
19314 for i in 0..50 {
19315 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19316 .unwrap();
19317 }
19318 e.execute("ANALYZE t").unwrap();
19319 let stats = e.statistics();
19320 let id_stats = stats.get("t", "id").unwrap();
19321 assert!(id_stats.histogram_bounds.len() >= 2);
19322 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19323 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19324 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19325 assert_eq!(id_stats.n_distinct, 50);
19326 }
19327
19328 #[test]
19329 fn reanalyze_overwrites_prior_stats() {
19330 let mut e = Engine::new();
19331 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19332 for i in 0..10 {
19333 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19334 .unwrap();
19335 }
19336 e.execute("ANALYZE t").unwrap();
19337 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19338 assert_eq!(n1, 10);
19339 for i in 10..30 {
19340 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19341 .unwrap();
19342 }
19343 e.execute("ANALYZE t").unwrap();
19344 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19345 assert_eq!(n2, 30);
19346 }
19347
19348 #[test]
19349 fn analyze_unknown_table_errors() {
19350 let mut e = Engine::new();
19351 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19352 assert!(matches!(
19353 err,
19354 EngineError::Storage(StorageError::TableNotFound { .. })
19355 ));
19356 }
19357
19358 #[test]
19359 fn bare_analyze_covers_all_user_tables() {
19360 let mut e = Engine::new();
19361 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19362 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19363 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19364 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19365 let r = e.execute("ANALYZE").unwrap();
19366 match r {
19367 QueryResult::CommandOk {
19368 affected,
19369 modified_catalog,
19370 } => {
19371 assert_eq!(affected, 2);
19372 assert!(modified_catalog);
19373 }
19374 other => panic!("expected CommandOk, got {other:?}"),
19375 }
19376 assert!(e.statistics().get("t1", "id").is_some());
19377 assert!(e.statistics().get("t2", "name").is_some());
19378 }
19379
19380 #[test]
19381 fn select_from_spg_statistic_returns_rows_per_column() {
19382 let mut e = Engine::new();
19383 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19384 .unwrap();
19385 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19386 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19387 e.execute("ANALYZE t").unwrap();
19388 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19389 let QueryResult::Rows { rows, columns } = r else {
19390 panic!()
19391 };
19392 assert_eq!(columns.len(), 6);
19394 assert_eq!(columns[0].name, "table_name");
19395 assert_eq!(columns[4].name, "histogram_bounds");
19396 assert_eq!(columns[5].name, "cold_row_count");
19397 assert_eq!(rows.len(), 2, "one row per column of t");
19398 match (&rows[0].values[0], &rows[0].values[1]) {
19400 (Value::Text(t), Value::Text(c)) => {
19401 assert_eq!(t, "t");
19402 assert_eq!(c, "id");
19404 }
19405 _ => panic!(),
19406 }
19407 }
19408
19409 #[test]
19410 fn analyze_skips_vector_columns() {
19411 let mut e = Engine::new();
19414 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19415 .unwrap();
19416 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19417 e.execute("ANALYZE t").unwrap();
19418 assert!(e.statistics().get("t", "id").is_some());
19419 assert!(e.statistics().get("t", "v").is_none());
19420 }
19421
19422 #[test]
19423 fn statistics_persist_across_envelope_v5_round_trip() {
19424 let mut e = Engine::new();
19425 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19426 for i in 0..20 {
19427 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19428 .unwrap();
19429 }
19430 e.execute("ANALYZE").unwrap();
19431 let snap = e.snapshot();
19432 let e2 = Engine::restore_envelope(&snap).unwrap();
19433 let s = e2.statistics().get("t", "id").unwrap();
19434 assert_eq!(s.n_distinct, 20);
19435 }
19436
19437 #[test]
19440 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19441 let mut e = Engine::new();
19445 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19446 for i in 0..9 {
19447 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19448 .unwrap();
19449 }
19450 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19451 e.execute("INSERT INTO t VALUES (9)").unwrap();
19452 let needs = e.tables_needing_analyze();
19453 assert_eq!(needs, alloc::vec!["t".to_string()]);
19454 }
19455
19456 #[test]
19457 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19458 let mut e = Engine::new();
19464 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19465 for i in 0..1000 {
19466 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19467 .unwrap();
19468 }
19469 e.execute("ANALYZE t").unwrap();
19470 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19471 for i in 1000..1050 {
19472 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19473 .unwrap();
19474 }
19475 assert!(
19476 e.tables_needing_analyze().is_empty(),
19477 "50 inserts < threshold of ~105"
19478 );
19479 for i in 1050..1200 {
19480 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19481 .unwrap();
19482 }
19483 assert_eq!(
19484 e.tables_needing_analyze(),
19485 alloc::vec!["t".to_string()],
19486 "200 inserts > 0.1 × 1200 threshold"
19487 );
19488 }
19489
19490 #[test]
19491 fn auto_analyze_threshold_resets_after_analyze() {
19492 let mut e = Engine::new();
19493 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19494 for i in 0..200 {
19495 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19496 .unwrap();
19497 }
19498 assert!(!e.tables_needing_analyze().is_empty());
19499 e.execute("ANALYZE").unwrap();
19500 assert!(
19501 e.tables_needing_analyze().is_empty(),
19502 "ANALYZE must reset the counter"
19503 );
19504 }
19505
19506 #[test]
19507 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19508 let mut e = Engine::new();
19509 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19510 .unwrap();
19511 for i in 0..50 {
19512 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19513 .unwrap();
19514 }
19515 e.execute("ANALYZE t").unwrap();
19516 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19519 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19520 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19521 }
19522
19523 #[test]
19524 fn v4_envelope_loads_with_empty_statistics() {
19525 let mut e = Engine::new();
19529 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19530 .unwrap();
19531 let catalog = e.catalog.serialize();
19532 let users = crate::users::serialize_users(&e.users);
19533 let pubs = e.publications.serialize();
19534 let subs = e.subscriptions.serialize();
19535 let mut buf = Vec::new();
19536 buf.extend_from_slice(b"SPGENV01");
19537 buf.push(4u8);
19538 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19539 buf.extend_from_slice(&catalog);
19540 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19541 buf.extend_from_slice(&users);
19542 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19543 buf.extend_from_slice(&pubs);
19544 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19545 buf.extend_from_slice(&subs);
19546 let crc = spg_crypto::crc32::crc32(&buf);
19547 buf.extend_from_slice(&crc.to_le_bytes());
19548 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19549 assert!(e2.statistics().is_empty());
19550 }
19551
19552 #[test]
19553 fn v1_v2_envelope_loads_with_empty_publications() {
19554 let mut e = Engine::new();
19561 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19564 .unwrap();
19565
19566 let catalog = e.catalog.serialize();
19568 let users = crate::users::serialize_users(&e.users);
19569 let mut buf = Vec::new();
19570 buf.extend_from_slice(b"SPGENV01");
19571 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19573 buf.extend_from_slice(&catalog);
19574 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19575 buf.extend_from_slice(&users);
19576 let crc = spg_crypto::crc32::crc32(&buf);
19577 buf.extend_from_slice(&crc.to_le_bytes());
19578
19579 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19580 assert!(e2.publications().is_empty());
19581 }
19582}