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 = eval::eval_expr(on_expr, &combined, &ctx)?;
8388 matches!(cond, Value::Bool(true))
8389 } else {
8390 true
8391 };
8392 if keep {
8393 next.push(combined);
8394 left_matched = true;
8395 }
8396 }
8397 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8398 let mut combined_vals = left.values.clone();
8399 for _ in 0..right_arity {
8400 combined_vals.push(Value::Null);
8401 }
8402 next.push(Row::new(combined_vals));
8403 }
8404 }
8405 working = next;
8406 consumed_cols += right_arity;
8407 debug_assert!(consumed_cols <= combined_schema.len());
8408 }
8409 let mut filtered: Vec<Row> = Vec::new();
8410 let mut memo = memoize::MemoizeCache::default();
8416 for row in working {
8417 if let Some(where_expr) = where_ {
8418 let cond = self.eval_expr_with_correlated(
8419 where_expr,
8420 &row,
8421 &ctx,
8422 cancel,
8423 Some(&mut memo),
8424 )?;
8425 if !matches!(cond, Value::Bool(true)) {
8426 continue;
8427 }
8428 }
8429 filtered.push(row);
8430 }
8431 Ok((combined_schema, filtered))
8432 }
8433
8434 fn lateral_probe_schema(
8440 &self,
8441 inner: &SelectStatement,
8442 ) -> Result<Vec<ColumnSchema>, EngineError> {
8443 match self.execute_readonly_select_for_lateral_probe(inner) {
8453 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8454 _ => {
8460 let mut out: Vec<ColumnSchema> = Vec::new();
8461 for (i, item) in inner.items.iter().enumerate() {
8462 let name = match item {
8463 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8464 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8465 SelectItem::Wildcard => alloc::format!("col{i}"),
8466 };
8467 out.push(ColumnSchema::new(name, DataType::Text, true));
8468 }
8469 Ok(out)
8470 }
8471 }
8472 }
8473
8474 fn execute_readonly_select_for_lateral_probe(
8480 &self,
8481 inner: &SelectStatement,
8482 ) -> Result<QueryResult, EngineError> {
8483 self.exec_bare_select_cancel(inner, CancelToken::none())
8484 }
8485
8486 fn materialise_lateral_for_outer(
8492 &self,
8493 inner: &SelectStatement,
8494 outer_schema: &[ColumnSchema],
8495 outer_row: &Row,
8496 ) -> Result<Vec<Row>, EngineError> {
8497 let mut substituted = inner.clone();
8498 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8499 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8500 match result {
8501 QueryResult::Rows { rows, .. } => Ok(rows),
8502 _ => Err(EngineError::Unsupported(
8503 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8504 )),
8505 }
8506 }
8507
8508 fn exec_joined_select(
8509 &self,
8510 stmt: &SelectStatement,
8511 from: &FromClause,
8512 cancel: CancelToken<'_>,
8513 ) -> Result<QueryResult, EngineError> {
8514 let (combined_schema, filtered) =
8522 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel)?;
8523 let ctx = EvalContext::new(&combined_schema, None);
8524 if aggregate::uses_aggregate(stmt) {
8527 let refs: Vec<&Row> = filtered.iter().collect();
8528 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8529 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8530 return Ok(QueryResult::Rows {
8531 columns: agg.columns,
8532 rows: agg.rows,
8533 });
8534 }
8535
8536 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8537 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8538 let mut proj_memo = memoize::MemoizeCache::default();
8539 for row in &filtered {
8540 let mut values = Vec::with_capacity(projection.len());
8541 for p in &projection {
8542 values.push(self.eval_expr_with_correlated(
8545 &p.expr,
8546 row,
8547 &ctx,
8548 cancel,
8549 Some(&mut proj_memo),
8550 )?);
8551 }
8552 let order_keys = if stmt.order_by.is_empty() {
8553 Vec::new()
8554 } else {
8555 build_order_keys(&stmt.order_by, row, &ctx)?
8556 };
8557 tagged.push((order_keys, Row::new(values)));
8558 }
8559 if !stmt.order_by.is_empty() {
8560 let keep = if stmt.distinct {
8561 None
8562 } else {
8563 stmt.limit_literal()
8564 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8565 };
8566 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8567 partial_sort_tagged(&mut tagged, keep, &descs);
8568 }
8569 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8570 if stmt.distinct {
8571 output_rows = dedup_rows(output_rows);
8572 }
8573 apply_offset_and_limit(
8574 &mut output_rows,
8575 stmt.offset_literal(),
8576 stmt.limit_literal(),
8577 );
8578 let columns: Vec<ColumnSchema> = projection
8579 .into_iter()
8580 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8581 .collect();
8582 Ok(QueryResult::Rows {
8583 columns,
8584 rows: output_rows,
8585 })
8586 }
8587}
8588
8589#[derive(Debug, Clone)]
8592struct ProjectedItem {
8593 expr: Expr,
8594 output_name: String,
8595 ty: DataType,
8596 nullable: bool,
8597}
8598
8599fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8605 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8606 for r in rows {
8607 if !out.iter().any(|seen| seen == &r) {
8608 out.push(r);
8609 }
8610 }
8611 out
8612}
8613
8614fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8618 match v {
8619 Value::Null => Ok(f64::INFINITY),
8620 Value::SmallInt(n) => Ok(f64::from(*n)),
8621 Value::Int(n) => Ok(f64::from(*n)),
8622 Value::Date(d) => Ok(f64::from(*d)),
8623 #[allow(clippy::cast_precision_loss)]
8624 Value::Timestamp(t) => Ok(*t as f64),
8625 #[allow(clippy::cast_precision_loss)]
8628 Value::Time(us) => Ok(*us as f64),
8629 Value::Year(y) => Ok(f64::from(*y)),
8633 #[allow(clippy::cast_precision_loss)]
8638 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8639 #[allow(clippy::cast_precision_loss)]
8641 Value::Money(c) => Ok(*c as f64),
8642 Value::Range { .. } => Err(EngineError::Unsupported(
8645 "ORDER BY of a range value is not supported in v7.17.0".into(),
8646 )),
8647 Value::Hstore(_) => Err(EngineError::Unsupported(
8649 "ORDER BY of a hstore value is not supported".into(),
8650 )),
8651 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8653 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8654 ),
8655 #[allow(clippy::cast_precision_loss)]
8656 Value::Numeric { scaled, scale } => {
8657 let mut divisor = 1.0_f64;
8663 for _ in 0..*scale {
8664 divisor *= 10.0;
8665 }
8666 Ok((*scaled as f64) / divisor)
8667 }
8668 #[allow(clippy::cast_precision_loss)]
8669 Value::BigInt(n) => Ok(*n as f64),
8670 Value::Float(x) => Ok(*x),
8671 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8672 Value::Text(s) => {
8673 let mut key: u64 = 0;
8677 for &b in s.as_bytes().iter().take(8) {
8678 key = (key << 8) | u64::from(b);
8679 }
8680 #[allow(clippy::cast_precision_loss)]
8681 Ok(key as f64)
8682 }
8683 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8684 Err(EngineError::Unsupported(
8685 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8686 ))
8687 }
8688 Value::Interval { .. } => Err(EngineError::Unsupported(
8689 "ORDER BY of an INTERVAL is not supported in v2.11 \
8690 (months vs micros has no single canonical ordering)"
8691 .into(),
8692 )),
8693 Value::Json(_) => Err(EngineError::Unsupported(
8694 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8695 )),
8696 _ => Err(EngineError::Unsupported(
8700 "ORDER BY of this value type is not supported".into(),
8701 )),
8702 }
8703}
8704
8705fn try_nsw_knn(
8719 stmt: &SelectStatement,
8720 table: &Table,
8721 schema_cols: &[ColumnSchema],
8722 table_alias: &str,
8723) -> Option<Vec<usize>> {
8724 if stmt.distinct {
8725 return None;
8726 }
8727 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8728 if limit == 0 {
8729 return None;
8730 }
8731 if stmt.order_by.len() != 1 {
8735 return None;
8736 }
8737 let order = &stmt.order_by[0];
8738 if order.desc {
8742 return None;
8743 }
8744 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8745 return None;
8746 };
8747 let metric = match op {
8748 BinOp::L2Distance => spg_storage::NswMetric::L2,
8749 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8750 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8751 _ => return None,
8752 };
8753 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8755 (lhs.as_ref(), rhs.as_ref())
8756 else {
8757 return None;
8758 };
8759 if let Some(q) = &col.qualifier
8760 && q != table_alias
8761 {
8762 return None;
8763 }
8764 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8765 let query = literal_to_vector(literal)?;
8766 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8767 if let Some(where_expr) = &stmt.where_ {
8768 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8772 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8773 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8774 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8775 for i in candidates {
8776 let row = &table.rows()[i];
8777 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8778 if matches!(cond, Value::Bool(true)) {
8779 kept.push(i);
8780 if kept.len() >= limit {
8781 break;
8782 }
8783 }
8784 }
8785 Some(kept)
8786 } else {
8787 Some(spg_storage::nsw_query(
8788 table, &idx.name, &query, limit, metric,
8789 ))
8790 }
8791}
8792
8793const NSW_OVER_FETCH_FLOOR: usize = 32;
8797
8798fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8801 match e {
8802 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8803 Expr::Cast { expr, .. } => literal_to_vector(expr),
8804 _ => None,
8805 }
8806}
8807
8808fn materialise_in_order(
8812 stmt: &SelectStatement,
8813 table: &Table,
8814 schema_cols: &[ColumnSchema],
8815 table_alias: &str,
8816 ordered_rows: &[usize],
8817) -> Result<QueryResult, EngineError> {
8818 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8819 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8820 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8821 for &i in ordered_rows {
8822 let row = &table.rows()[i];
8823 let mut values = Vec::with_capacity(projection.len());
8824 for p in &projection {
8825 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8826 }
8827 output_rows.push(Row::new(values));
8828 }
8829 apply_offset_and_limit(
8830 &mut output_rows,
8831 stmt.offset_literal(),
8832 stmt.limit_literal(),
8833 );
8834 let columns: Vec<ColumnSchema> = projection
8835 .into_iter()
8836 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8837 .collect();
8838 Ok(QueryResult::Rows {
8839 columns,
8840 rows: output_rows,
8841 })
8842}
8843
8844fn try_index_seek_positions(
8857 where_expr: &Expr,
8858 schema_cols: &[ColumnSchema],
8859 table: &Table,
8860 table_alias: &str,
8861) -> Option<Vec<usize>> {
8862 if let Expr::Binary {
8863 lhs,
8864 op: BinOp::And,
8865 rhs,
8866 } = where_expr
8867 {
8868 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8869 return Some(p);
8870 }
8871 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8872 }
8873 let Expr::Binary {
8874 lhs,
8875 op: BinOp::Eq,
8876 rhs,
8877 } = where_expr
8878 else {
8879 return None;
8880 };
8881 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8882 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8883 let idx = table.index_on(col_pos)?;
8884 let key = IndexKey::from_value(&value)?;
8885 let locators = idx.lookup_eq(&key);
8886 let mut out = Vec::with_capacity(locators.len());
8887 for loc in locators {
8888 match *loc {
8889 spg_storage::RowLocator::Hot(i) => out.push(i),
8890 spg_storage::RowLocator::Cold { .. } => return None,
8891 }
8892 }
8893 Some(out)
8894}
8895
8896fn try_index_seek<'a>(
8897 where_expr: &Expr,
8898 schema_cols: &[ColumnSchema],
8899 catalog: &'a Catalog,
8900 table: &'a Table,
8901 table_alias: &str,
8902) -> Option<Vec<Cow<'a, Row>>> {
8903 if let Expr::Binary {
8910 lhs,
8911 op: BinOp::And,
8912 rhs,
8913 } = where_expr
8914 {
8915 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8918 return Some(rows);
8919 }
8920 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8921 }
8922 let Expr::Binary {
8923 lhs,
8924 op: BinOp::Eq,
8925 rhs,
8926 } = where_expr
8927 else {
8928 return None;
8929 };
8930 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8931 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8932 let idx = table.index_on(col_pos)?;
8933 let key = IndexKey::from_value(&value)?;
8934 let locators = idx.lookup_eq(&key);
8935 let table_name = table.schema().name.as_str();
8936 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8944 for loc in locators {
8945 match *loc {
8946 spg_storage::RowLocator::Hot(i) => {
8947 if let Some(row) = table.rows().get(i) {
8948 out.push(Cow::Borrowed(row));
8949 }
8950 }
8951 spg_storage::RowLocator::Cold { segment_id, .. } => {
8952 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8953 out.push(Cow::Owned(row));
8954 }
8955 }
8956 }
8957 }
8958 Some(out)
8959}
8960
8961fn try_gin_seek<'a>(
8980 where_expr: &Expr,
8981 schema_cols: &[ColumnSchema],
8982 catalog: &'a Catalog,
8983 table: &'a Table,
8984 table_alias: &str,
8985 ctx: &eval::EvalContext<'_>,
8986) -> Option<Vec<Cow<'a, Row>>> {
8987 if let Expr::Binary {
8988 lhs,
8989 op: BinOp::And,
8990 rhs,
8991 } = where_expr
8992 {
8993 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8994 return Some(rows);
8995 }
8996 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8997 }
8998 if let Expr::Binary {
9007 lhs,
9008 op: BinOp::Or,
9009 rhs,
9010 } = where_expr
9011 {
9012 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
9013 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
9014 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
9015 out.extend(left);
9016 out.extend(right);
9017 return Some(out);
9018 }
9019 let Expr::Binary {
9020 lhs,
9021 op: BinOp::TsMatch,
9022 rhs,
9023 } = where_expr
9024 else {
9025 return None;
9026 };
9027 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
9032 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
9033 let idx = table
9040 .indices()
9041 .iter()
9042 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
9043 let candidates = gin_query_candidates(idx, &query)?;
9044 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
9046 for loc in candidates {
9047 match loc {
9048 spg_storage::RowLocator::Hot(i) => {
9049 if let Some(row) = table.rows().get(i) {
9050 out.push(Cow::Borrowed(row));
9051 }
9052 }
9053 spg_storage::RowLocator::Cold { .. } => {}
9060 }
9061 }
9062 Some(out)
9063}
9064
9065fn try_trgm_seek<'a>(
9081 where_expr: &Expr,
9082 schema_cols: &[ColumnSchema],
9083 table: &'a Table,
9084 table_alias: &str,
9085) -> Option<Vec<Cow<'a, Row>>> {
9086 if let Expr::Binary {
9087 lhs,
9088 op: BinOp::And,
9089 rhs,
9090 } = where_expr
9091 {
9092 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9093 return Some(rows);
9094 }
9095 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9096 }
9097 let Expr::Like { expr, pattern, .. } = where_expr else {
9103 return None;
9104 };
9105 let Expr::Column(c) = expr.as_ref() else {
9107 return None;
9108 };
9109 if let Some(q) = &c.qualifier
9110 && q != table_alias
9111 {
9112 return None;
9113 }
9114 let col_pos = schema_cols
9115 .iter()
9116 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9117 let idx = table
9119 .indices()
9120 .iter()
9121 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9122 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9126 return None;
9127 };
9128 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9129 let mut iter = trigrams.iter();
9132 let first = iter.next()?;
9133 let mut acc: Vec<spg_storage::RowLocator> = {
9134 let mut v = idx.gin_trgm_lookup(first).to_vec();
9135 v.sort_by_key(locator_sort_key);
9136 v.dedup_by_key(|l| locator_sort_key(l));
9137 v
9138 };
9139 for tri in iter {
9140 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9141 next.sort_by_key(locator_sort_key);
9142 next.dedup_by_key(|l| locator_sort_key(l));
9143 let mut merged: Vec<spg_storage::RowLocator> =
9145 Vec::with_capacity(acc.len().min(next.len()));
9146 let (mut i, mut j) = (0usize, 0usize);
9147 while i < acc.len() && j < next.len() {
9148 let lk = locator_sort_key(&acc[i]);
9149 let rk = locator_sort_key(&next[j]);
9150 match lk.cmp(&rk) {
9151 core::cmp::Ordering::Less => i += 1,
9152 core::cmp::Ordering::Greater => j += 1,
9153 core::cmp::Ordering::Equal => {
9154 merged.push(acc[i]);
9155 i += 1;
9156 j += 1;
9157 }
9158 }
9159 }
9160 acc = merged;
9161 if acc.is_empty() {
9162 break;
9163 }
9164 }
9165 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9166 for loc in acc {
9167 if let spg_storage::RowLocator::Hot(i) = loc
9168 && let Some(row) = table.rows().get(i)
9169 {
9170 out.push(Cow::Borrowed(row));
9171 }
9172 }
9174 Some(out)
9175}
9176
9177fn resolve_gin_col_query(
9183 col_side: &Expr,
9184 query_side: &Expr,
9185 schema_cols: &[ColumnSchema],
9186 table_alias: &str,
9187 ctx: &eval::EvalContext<'_>,
9188) -> Option<(usize, spg_storage::TsQueryAst)> {
9189 let column = match col_side {
9194 Expr::Column(c) => c,
9195 Expr::FunctionCall { name, args }
9196 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9197 {
9198 if let Expr::Column(c) = args.last().unwrap() {
9202 c
9203 } else {
9204 return None;
9205 }
9206 }
9207 _ => return None,
9208 };
9209 let c = column;
9210 if let Some(q) = &c.qualifier
9211 && q != table_alias
9212 {
9213 return None;
9214 }
9215 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9216 let empty_row = Row::new(Vec::new());
9220 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9221 let Value::TsQuery(q) = v else { return None };
9222 Some((pos, q))
9223}
9224
9225fn gin_query_candidates(
9236 idx: &spg_storage::Index,
9237 query: &spg_storage::TsQueryAst,
9238) -> Option<Vec<spg_storage::RowLocator>> {
9239 use spg_storage::TsQueryAst;
9240 match query {
9241 TsQueryAst::Term { word, .. } => {
9242 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9243 v.sort_by_key(locator_sort_key);
9244 v.dedup_by_key(|l| locator_sort_key(l));
9245 Some(v)
9246 }
9247 TsQueryAst::And(l, r) => {
9248 let mut left = gin_query_candidates(idx, l)?;
9249 let mut right = gin_query_candidates(idx, r)?;
9250 left.sort_by_key(locator_sort_key);
9251 right.sort_by_key(locator_sort_key);
9252 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9254 let (mut i, mut j) = (0usize, 0usize);
9255 while i < left.len() && j < right.len() {
9256 let lk = locator_sort_key(&left[i]);
9257 let rk = locator_sort_key(&right[j]);
9258 match lk.cmp(&rk) {
9259 core::cmp::Ordering::Less => i += 1,
9260 core::cmp::Ordering::Greater => j += 1,
9261 core::cmp::Ordering::Equal => {
9262 out.push(left[i]);
9263 i += 1;
9264 j += 1;
9265 }
9266 }
9267 }
9268 Some(out)
9269 }
9270 TsQueryAst::Or(l, r) => {
9271 let mut out = gin_query_candidates(idx, l)?;
9272 out.extend(gin_query_candidates(idx, r)?);
9273 out.sort_by_key(locator_sort_key);
9274 out.dedup_by_key(|l| locator_sort_key(l));
9275 Some(out)
9276 }
9277 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9282 }
9283}
9284
9285fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9290 match *l {
9291 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9292 spg_storage::RowLocator::Cold {
9293 segment_id,
9294 page_offset,
9295 } => (1, u64::from(segment_id), u64::from(page_offset)),
9296 }
9297}
9298
9299fn try_pk_predicate(
9311 where_expr: &Expr,
9312 schema_cols: &[ColumnSchema],
9313 table_alias: &str,
9314) -> Option<(usize, IndexKey)> {
9315 let Expr::Binary {
9316 lhs,
9317 op: BinOp::Eq,
9318 rhs,
9319 } = where_expr
9320 else {
9321 return None;
9322 };
9323 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9324 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9325 let key = IndexKey::from_value(&value)?;
9326 Some((col_pos, key))
9327}
9328
9329fn resolve_col_literal_pair(
9330 col_side: &Expr,
9331 lit_side: &Expr,
9332 schema_cols: &[ColumnSchema],
9333 table_alias: &str,
9334) -> Option<(usize, Value)> {
9335 let Expr::Column(c) = col_side else {
9336 return None;
9337 };
9338 if let Some(q) = &c.qualifier
9339 && q != table_alias
9340 {
9341 return None;
9342 }
9343 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9344 let Expr::Literal(l) = lit_side else {
9345 return None;
9346 };
9347 let v = match l {
9348 Literal::Integer(n) => {
9349 if let Ok(small) = i32::try_from(*n) {
9350 Value::Int(small)
9351 } else {
9352 Value::BigInt(*n)
9353 }
9354 }
9355 Literal::Float(x) => Value::Float(*x),
9356 Literal::String(s) => Value::Text(s.clone()),
9357 Literal::Bool(b) => Value::Bool(*b),
9358 Literal::Null => Value::Null,
9359 Literal::Vector(_)
9362 | Literal::Interval { .. }
9363 | Literal::TextArray(_)
9364 | Literal::IntArray(_)
9365 | Literal::BigIntArray(_) => return None,
9366 };
9367 Some((pos, v))
9368}
9369
9370fn resolve_projection_column<'a>(
9375 c: &ColumnName,
9376 schema_cols: &'a [ColumnSchema],
9377 table_alias: &str,
9378) -> Result<&'a ColumnSchema, EngineError> {
9379 if let Some(q) = &c.qualifier {
9380 let composite = alloc::format!("{q}.{name}", name = c.name);
9381 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9382 return Ok(s);
9383 }
9384 if q == table_alias
9387 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9388 {
9389 return Ok(s);
9390 }
9391 let prefix = alloc::format!("{q}.");
9395 let qualifier_known =
9396 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9397 if !qualifier_known {
9398 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9399 qualifier: q.clone(),
9400 }));
9401 }
9402 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9403 name: c.name.clone(),
9404 }));
9405 }
9406 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9407 return Ok(s);
9408 }
9409 let suffix = alloc::format!(".{name}", name = c.name);
9410 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9411 let first = matches.next();
9412 let extra = matches.next();
9413 match (first, extra) {
9414 (Some(s), None) => Ok(s),
9415 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9416 detail: alloc::format!("ambiguous column reference: {}", c.name),
9417 })),
9418 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9419 name: c.name.clone(),
9420 })),
9421 }
9422}
9423
9424fn build_projection(
9425 items: &[SelectItem],
9426 schema_cols: &[ColumnSchema],
9427 table_alias: &str,
9428) -> Result<Vec<ProjectedItem>, EngineError> {
9429 let mut out = Vec::new();
9430 for item in items {
9431 match item {
9432 SelectItem::Wildcard => {
9433 for col in schema_cols {
9434 out.push(ProjectedItem {
9435 expr: Expr::Column(ColumnName {
9436 qualifier: None,
9437 name: col.name.clone(),
9438 }),
9439 output_name: col.name.clone(),
9440 ty: col.ty,
9441 nullable: col.nullable,
9442 });
9443 }
9444 }
9445 SelectItem::Expr { expr, alias } => {
9446 if let Expr::Column(c) = expr {
9453 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9454 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9455 out.push(ProjectedItem {
9456 expr: expr.clone(),
9457 output_name,
9458 ty: sch.ty,
9459 nullable: sch.nullable,
9460 });
9461 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9462 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9463 out.push(ProjectedItem {
9464 expr: expr.clone(),
9465 output_name,
9466 ty: shape.ty,
9467 nullable: shape.nullable,
9468 });
9469 } else {
9470 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9471 out.push(ProjectedItem {
9472 expr: expr.clone(),
9473 output_name,
9474 ty: DataType::Text,
9475 nullable: true,
9476 });
9477 }
9478 }
9479 }
9480 }
9481 Ok(out)
9482}
9483
9484fn numeric_from_integer(
9488 n: i128,
9489 precision: u8,
9490 scale: u8,
9491 col_name: &str,
9492) -> Result<Value, EngineError> {
9493 let factor = pow10_i128(scale);
9494 let scaled = n.checked_mul(factor).ok_or_else(|| {
9495 EngineError::Unsupported(alloc::format!(
9496 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9497 ))
9498 })?;
9499 check_precision(scaled, precision, col_name)?;
9500 Ok(Value::Numeric { scaled, scale })
9501}
9502
9503#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9506fn numeric_from_float(
9507 x: f64,
9508 precision: u8,
9509 scale: u8,
9510 col_name: &str,
9511) -> Result<Value, EngineError> {
9512 if !x.is_finite() {
9513 return Err(EngineError::Unsupported(alloc::format!(
9514 "cannot store non-finite float in NUMERIC column `{col_name}`"
9515 )));
9516 }
9517 let mut factor = 1.0_f64;
9518 for _ in 0..scale {
9519 factor *= 10.0;
9520 }
9521 let shifted = x * factor;
9526 let biased = if shifted >= 0.0 {
9527 shifted + 0.5
9528 } else {
9529 shifted - 0.5
9530 };
9531 if !(-1e38..=1e38).contains(&biased) {
9534 return Err(EngineError::Unsupported(alloc::format!(
9535 "value {x} overflows NUMERIC range for column `{col_name}`"
9536 )));
9537 }
9538 let scaled = biased as i128;
9539 check_precision(scaled, precision, col_name)?;
9540 Ok(Value::Numeric { scaled, scale })
9541}
9542
9543fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9550 let s = s.trim();
9551 if s.is_empty() {
9552 return None;
9553 }
9554 let (negative, rest) = match s.as_bytes()[0] {
9555 b'-' => (true, &s[1..]),
9556 b'+' => (false, &s[1..]),
9557 _ => (false, s),
9558 };
9559 if rest.is_empty() {
9560 return None;
9561 }
9562 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9566 return None;
9567 }
9568 let (int_part, frac_part) = match rest.find('.') {
9569 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9570 None => (rest, ""),
9571 };
9572 if int_part.is_empty() && frac_part.is_empty() {
9573 return None;
9574 }
9575 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9576 return None;
9577 }
9578 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9579 return None;
9580 }
9581 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9582 if scale_u32 > u32::from(u8::MAX) {
9583 return None;
9584 }
9585 let scale = scale_u32 as u8;
9586 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9587 if negative {
9588 digits.push('-');
9589 }
9590 digits.push_str(int_part);
9591 digits.push_str(frac_part);
9592 let digits = if digits == "-" {
9594 return None;
9595 } else if digits.is_empty() {
9596 "0"
9597 } else {
9598 digits.as_str()
9599 };
9600 let mantissa: i128 = digits.parse().ok()?;
9601 Some((mantissa, scale))
9602}
9603
9604fn numeric_rescale(
9607 scaled: i128,
9608 src_scale: u8,
9609 precision: u8,
9610 dst_scale: u8,
9611 col_name: &str,
9612) -> Result<Value, EngineError> {
9613 let new_scaled = if dst_scale >= src_scale {
9614 let bump = pow10_i128(dst_scale - src_scale);
9615 scaled.checked_mul(bump).ok_or_else(|| {
9616 EngineError::Unsupported(alloc::format!(
9617 "overflow rescaling NUMERIC for column `{col_name}`"
9618 ))
9619 })?
9620 } else {
9621 let drop = pow10_i128(src_scale - dst_scale);
9622 let half = drop / 2;
9623 if scaled >= 0 {
9624 (scaled + half) / drop
9625 } else {
9626 (scaled - half) / drop
9627 }
9628 };
9629 check_precision(new_scaled, precision, col_name)?;
9630 Ok(Value::Numeric {
9631 scaled: new_scaled,
9632 scale: dst_scale,
9633 })
9634}
9635
9636const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9639 if scale == 0 {
9640 return scaled;
9641 }
9642 let factor = pow10_i128_const(scale);
9643 scaled / factor
9644}
9645
9646fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9650 if precision == 0 {
9651 return Ok(());
9652 }
9653 let limit = pow10_i128(precision);
9654 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9655 return Err(EngineError::Unsupported(alloc::format!(
9656 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9657 )));
9658 }
9659 Ok(())
9660}
9661
9662const fn pow10_i128_const(p: u8) -> i128 {
9663 let mut acc: i128 = 1;
9664 let mut i = 0;
9665 while i < p {
9666 acc *= 10;
9667 i += 1;
9668 }
9669 acc
9670}
9671
9672fn pow10_i128(p: u8) -> i128 {
9673 pow10_i128_const(p)
9674}
9675
9676impl Engine {
9691 #[allow(
9702 clippy::too_many_lines,
9703 clippy::type_complexity,
9704 clippy::needless_range_loop
9705 )] fn exec_select_with_window(
9707 &self,
9708 stmt: &SelectStatement,
9709 cancel: CancelToken<'_>,
9710 ) -> Result<QueryResult, EngineError> {
9711 let from = stmt.from.as_ref().ok_or_else(|| {
9712 EngineError::Unsupported("window functions require a FROM clause".into())
9713 })?;
9714 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9723 let filtered: Vec<Row>;
9724 if from.joins.is_empty() {
9725 let primary = &from.primary;
9726 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9727 StorageError::TableNotFound {
9728 name: primary.name.clone(),
9729 }
9730 })?;
9731 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9732 schema_cols_owned = table.schema().columns.clone();
9733 alias_opt = Some(alias);
9734 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9739 let mut owned: Vec<Row> = Vec::new();
9740 for (i, row) in table.rows().iter().enumerate() {
9741 if i.is_multiple_of(256) {
9742 cancel.check()?;
9743 }
9744 if let Some(w) = &stmt.where_ {
9745 let cond = eval::eval_expr(w, row, &ctx)?;
9746 if !matches!(cond, Value::Bool(true)) {
9747 continue;
9748 }
9749 }
9750 owned.push(row.clone());
9751 }
9752 filtered = owned;
9753 } else {
9754 let (combined_schema, rows) =
9755 self.build_joined_filtered_rows(from, stmt.where_.as_ref(), cancel)?;
9756 schema_cols_owned = combined_schema;
9757 alias_opt = None;
9758 filtered = rows;
9759 }
9760 let schema_cols = &schema_cols_owned;
9761 let ctx = self.ev_ctx(schema_cols, alias_opt);
9762 let alias = alias_opt.unwrap_or("");
9763 let n_rows = filtered.len();
9764 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9768
9769 let mut window_nodes: Vec<Expr> = Vec::new();
9771 for item in &stmt.items {
9772 if let SelectItem::Expr { expr, .. } = item {
9773 collect_window_nodes(expr, &mut window_nodes);
9774 }
9775 }
9776
9777 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9780 for wnode in &window_nodes {
9781 let Expr::WindowFunction {
9782 name,
9783 args,
9784 partition_by,
9785 order_by,
9786 frame,
9787 null_treatment,
9788 } = wnode
9789 else {
9790 unreachable!("collect_window_nodes pushes only WindowFunction");
9791 };
9792 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9794 Vec::with_capacity(n_rows);
9795 for (i, row) in filtered.iter().enumerate() {
9796 let pkey: Vec<Value> = partition_by
9797 .iter()
9798 .map(|p| eval::eval_expr(p, row, &ctx))
9799 .collect::<Result<_, _>>()?;
9800 let okey: Vec<(Value, bool)> = order_by
9801 .iter()
9802 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9803 .collect::<Result<_, _>>()?;
9804 indexed.push((pkey, okey, i));
9805 }
9806 indexed.sort_by(|a, b| {
9809 let p_cmp = partition_key_cmp(&a.0, &b.0);
9810 if p_cmp != core::cmp::Ordering::Equal {
9811 return p_cmp;
9812 }
9813 order_key_cmp(&a.1, &b.1)
9814 });
9815 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9817 let mut p_start = 0;
9818 while p_start < indexed.len() {
9819 let mut p_end = p_start + 1;
9820 while p_end < indexed.len()
9821 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9822 == core::cmp::Ordering::Equal
9823 {
9824 p_end += 1;
9825 }
9826 compute_window_partition(
9828 name,
9829 args,
9830 !order_by.is_empty(),
9831 frame.as_ref(),
9832 *null_treatment,
9833 &indexed[p_start..p_end],
9834 &filtered_refs,
9835 &ctx,
9836 &mut out_vals,
9837 )?;
9838 p_start = p_end;
9839 }
9840 win_vals.push(out_vals);
9841 }
9842
9843 let mut ext_cols = schema_cols.clone();
9845 for i in 0..window_nodes.len() {
9846 ext_cols.push(ColumnSchema::new(
9847 alloc::format!("__win_{i}"),
9848 DataType::Text, true,
9850 ));
9851 }
9852 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9854 for i in 0..n_rows {
9855 let mut values = filtered[i].values.clone();
9856 for w in 0..window_nodes.len() {
9857 values.push(win_vals[w][i].clone());
9858 }
9859 ext_rows.push(Row::new(values));
9860 }
9861 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9863 for item in &stmt.items {
9864 let new_item = match item {
9865 SelectItem::Wildcard => SelectItem::Wildcard,
9866 SelectItem::Expr { expr, alias } => {
9867 let mut e = expr.clone();
9868 rewrite_window_to_columns(&mut e, &window_nodes);
9869 SelectItem::Expr {
9870 expr: e,
9871 alias: alias.clone(),
9872 }
9873 }
9874 };
9875 rewritten_items.push(new_item);
9876 }
9877
9878 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9884 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9885 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9886 for (i, row) in ext_rows.iter().enumerate() {
9887 if i.is_multiple_of(256) {
9888 cancel.check()?;
9889 }
9890 let mut values = Vec::with_capacity(projection.len());
9891 for p in &projection {
9892 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9893 }
9894 let order_keys = if stmt.order_by.is_empty() {
9895 Vec::new()
9896 } else {
9897 let mut keys = Vec::with_capacity(stmt.order_by.len());
9898 for o in &stmt.order_by {
9899 let mut e = o.expr.clone();
9900 rewrite_window_to_columns(&mut e, &window_nodes);
9901 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9902 keys.push(value_to_order_key(&key)?);
9903 }
9904 keys
9905 };
9906 tagged.push((order_keys, Row::new(values)));
9907 }
9908 if !stmt.order_by.is_empty() {
9910 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9911 sort_by_keys(&mut tagged, &descs);
9912 }
9913 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9914 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9915 let final_cols: Vec<ColumnSchema> = projection
9916 .into_iter()
9917 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9918 .collect();
9919 Ok(QueryResult::Rows {
9920 columns: final_cols,
9921 rows: out_rows,
9922 })
9923 }
9924
9925 fn exec_select_with_meta_views(
9942 &self,
9943 stmt: &SelectStatement,
9944 cancel: CancelToken<'_>,
9945 ) -> Result<QueryResult, EngineError> {
9946 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9947 collect_meta_view_names(stmt, &mut needed);
9948 let mut catalog = self.active_catalog().clone();
9949 for view in &needed {
9950 if catalog.get(view).is_some() {
9951 continue;
9952 }
9953 match view.as_str() {
9954 "__spg_info_columns" => {
9955 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9956 materialise_meta_view(&mut catalog, view, schema, rows)?;
9957 }
9958 "__spg_info_tables" => {
9959 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9960 materialise_meta_view(&mut catalog, view, schema, rows)?;
9961 }
9962 "__spg_pg_class" => {
9963 let (schema, rows) = synth_pg_class(self.active_catalog());
9964 materialise_meta_view(&mut catalog, view, schema, rows)?;
9965 }
9966 "__spg_pg_attribute" => {
9967 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9968 materialise_meta_view(&mut catalog, view, schema, rows)?;
9969 }
9970 "__spg_pg_type" => {
9973 let (schema, rows) = synth_pg_type(self.active_catalog());
9974 materialise_meta_view(&mut catalog, view, schema, rows)?;
9975 }
9976 "__spg_pg_proc" => {
9979 let (schema, rows) = synth_pg_proc(self.active_catalog());
9980 materialise_meta_view(&mut catalog, view, schema, rows)?;
9981 }
9982 "__spg_pg_trigger" => {
9989 let (schema, rows) = synth_pg_trigger(self.active_catalog());
9990 materialise_meta_view(&mut catalog, view, schema, rows)?;
9991 }
9992 "__spg_pg_namespace" => {
9995 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9996 materialise_meta_view(&mut catalog, view, schema, rows)?;
9997 }
9998 "__spg_pg_indexes" => {
10001 let (schema, rows) = synth_pg_indexes(self.active_catalog());
10002 materialise_meta_view(&mut catalog, view, schema, rows)?;
10003 }
10004 "__spg_pg_index" => {
10007 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
10008 materialise_meta_view(&mut catalog, view, schema, rows)?;
10009 }
10010 "__spg_pg_constraint" => {
10013 let (schema, rows) = synth_pg_constraint(self.active_catalog());
10014 materialise_meta_view(&mut catalog, view, schema, rows)?;
10015 }
10016 "__spg_pg_database" => {
10021 let (schema, rows) = synth_pg_database(self.active_catalog());
10022 materialise_meta_view(&mut catalog, view, schema, rows)?;
10023 }
10024 "__spg_pg_roles" | "__spg_pg_user" => {
10025 let (schema, rows) = synth_pg_roles(self);
10026 materialise_meta_view(&mut catalog, view, schema, rows)?;
10027 }
10028 "__spg_pg_views" => {
10032 let (schema, rows) = synth_pg_views(self.active_catalog());
10033 materialise_meta_view(&mut catalog, view, schema, rows)?;
10034 }
10035 "__spg_pg_matviews" => {
10039 let (schema, _) = synth_pg_views(self.active_catalog());
10040 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
10041 }
10042 "__spg_pg_extension" => {
10045 let (schema, rows) = synth_pg_extension();
10046 materialise_meta_view(&mut catalog, view, schema, rows)?;
10047 }
10048 "__spg_pg_settings" => {
10050 let (schema, rows) = synth_pg_settings(self);
10051 materialise_meta_view(&mut catalog, view, schema, rows)?;
10052 }
10053 "__spg_info_key_column_usage" => {
10055 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
10056 materialise_meta_view(&mut catalog, view, schema, rows)?;
10057 }
10058 "__spg_info_referential_constraints" => {
10060 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
10061 materialise_meta_view(&mut catalog, view, schema, rows)?;
10062 }
10063 "__spg_info_statistics" => {
10065 let (schema, rows) = synth_info_statistics(self.active_catalog());
10066 materialise_meta_view(&mut catalog, view, schema, rows)?;
10067 }
10068 "__spg_info_routines" => {
10070 let (schema, rows) = synth_info_routines();
10071 materialise_meta_view(&mut catalog, view, schema, rows)?;
10072 }
10073 "__spg_mysql_user" => {
10075 let (schema, rows) = synth_mysql_user(self);
10076 materialise_meta_view(&mut catalog, view, schema, rows)?;
10077 }
10078 "__spg_mysql_db" => {
10079 let (schema, rows) = synth_mysql_db();
10080 materialise_meta_view(&mut catalog, view, schema, rows)?;
10081 }
10082 _ => {
10083 return Err(EngineError::Unsupported(alloc::format!(
10084 "meta view {view:?} is not yet materialisable; \
10085 v7.16.2 covers information_schema.columns / .tables \
10086 and pg_catalog.pg_class / pg_attribute; \
10087 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
10088 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
10089 pg_user / pg_views / pg_matviews / pg_settings"
10090 )));
10091 }
10092 }
10093 }
10094 let mut temp = Engine::restore(catalog);
10095 if let Some(c) = self.clock {
10096 temp = temp.with_clock(c);
10097 }
10098 if let Some(f) = self.salt_fn {
10099 temp = temp.with_salt_fn(f);
10100 }
10101 temp.meta_views_materialised = true;
10102 temp.exec_select_cancel(stmt, cancel)
10103 }
10104
10105 fn exec_with_ctes(
10106 &self,
10107 stmt: &SelectStatement,
10108 cancel: CancelToken<'_>,
10109 ) -> Result<QueryResult, EngineError> {
10110 cancel.check()?;
10111 let mut catalog = self.active_catalog().clone();
10112 for cte in &stmt.ctes {
10113 if catalog.get(&cte.name).is_some() {
10114 return Err(EngineError::Unsupported(alloc::format!(
10115 "CTE name {:?} shadows an existing table; rename the CTE",
10116 cte.name
10117 )));
10118 }
10119 let (columns, rows) = if cte.recursive {
10120 self.materialise_recursive_cte(cte, &catalog, cancel)?
10121 } else {
10122 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
10123 let QueryResult::Rows { columns, rows } = body_result else {
10124 return Err(EngineError::Unsupported(alloc::format!(
10125 "CTE {:?} body did not return rows",
10126 cte.name
10127 )));
10128 };
10129 (columns, rows)
10130 };
10131 let inferred = infer_column_types(&columns, &rows);
10136 let mut columns = inferred;
10137 if !cte.column_overrides.is_empty() {
10139 if cte.column_overrides.len() != columns.len() {
10140 return Err(EngineError::Unsupported(alloc::format!(
10141 "CTE {:?} column list has {} names but body returns {} columns",
10142 cte.name,
10143 cte.column_overrides.len(),
10144 columns.len()
10145 )));
10146 }
10147 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10148 col.name.clone_from(name);
10149 }
10150 }
10151 let schema = TableSchema::new(cte.name.clone(), columns);
10152 catalog.create_table(schema).map_err(EngineError::Storage)?;
10153 let table = catalog
10154 .get_mut(&cte.name)
10155 .expect("just-created CTE table must exist");
10156 for row in rows {
10157 table.insert(row).map_err(EngineError::Storage)?;
10158 }
10159 }
10160 let mut body = stmt.clone();
10163 body.ctes = Vec::new();
10164 let mut temp = Engine::restore(catalog);
10165 if let Some(c) = self.clock {
10166 temp = temp.with_clock(c);
10167 }
10168 if let Some(f) = self.salt_fn {
10169 temp = temp.with_salt_fn(f);
10170 }
10171 temp.exec_select_cancel(&body, cancel)
10172 }
10173
10174 #[allow(clippy::too_many_lines)]
10184 fn materialise_recursive_cte(
10185 &self,
10186 cte: &spg_sql::ast::Cte,
10187 base_catalog: &Catalog,
10188 cancel: CancelToken<'_>,
10189 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10190 const MAX_TOTAL_ROWS: usize = 1_000_000;
10191 const MAX_ITERATIONS: usize = 100_000;
10192 cancel.check()?;
10193 if cte.body.unions.is_empty() {
10194 return Err(EngineError::Unsupported(alloc::format!(
10195 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10196 cte.name
10197 )));
10198 }
10199 let mut anchor = cte.body.clone();
10201 let union_terms = core::mem::take(&mut anchor.unions);
10202 anchor.ctes = Vec::new();
10203 if select_refers_to(&anchor, &cte.name) {
10205 return Err(EngineError::Unsupported(alloc::format!(
10206 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10207 cte.name
10208 )));
10209 }
10210 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10211 let QueryResult::Rows {
10212 columns: anchor_cols,
10213 rows: anchor_rows,
10214 } = anchor_result
10215 else {
10216 return Err(EngineError::Unsupported(alloc::format!(
10217 "WITH RECURSIVE {:?}: anchor did not return rows",
10218 cte.name
10219 )));
10220 };
10221 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10225 if !cte.column_overrides.is_empty() {
10226 if cte.column_overrides.len() != columns.len() {
10227 return Err(EngineError::Unsupported(alloc::format!(
10228 "CTE {:?} column list has {} names but anchor returns {} columns",
10229 cte.name,
10230 cte.column_overrides.len(),
10231 columns.len()
10232 )));
10233 }
10234 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10235 col.name.clone_from(name);
10236 }
10237 }
10238 let mut all_rows: Vec<Row> = anchor_rows.clone();
10239 let mut working_set: Vec<Row> = anchor_rows;
10240 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10241 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10244 if !all_union_all {
10245 for r in &all_rows {
10246 seen.insert(encode_row_key(r));
10247 }
10248 }
10249 for iter in 0..MAX_ITERATIONS {
10250 cancel.check()?;
10251 if working_set.is_empty() {
10252 break;
10253 }
10254 let mut iter_catalog = base_catalog.clone();
10256 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10257 iter_catalog
10258 .create_table(schema)
10259 .map_err(EngineError::Storage)?;
10260 {
10261 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10262 for row in &working_set {
10263 table.insert(row.clone()).map_err(EngineError::Storage)?;
10264 }
10265 }
10266 let mut iter_engine = Engine::restore(iter_catalog);
10267 if let Some(c) = self.clock {
10268 iter_engine = iter_engine.with_clock(c);
10269 }
10270 if let Some(f) = self.salt_fn {
10271 iter_engine = iter_engine.with_salt_fn(f);
10272 }
10273 let mut next_set: Vec<Row> = Vec::new();
10275 for (_, term) in &union_terms {
10276 let mut term = term.clone();
10277 term.ctes = Vec::new();
10278 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10279 let QueryResult::Rows {
10280 columns: rc,
10281 rows: rs,
10282 } = r
10283 else {
10284 return Err(EngineError::Unsupported(alloc::format!(
10285 "WITH RECURSIVE {:?}: recursive term did not return rows",
10286 cte.name
10287 )));
10288 };
10289 if rc.len() != columns.len() {
10290 return Err(EngineError::Unsupported(alloc::format!(
10291 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10292 cte.name,
10293 rc.len(),
10294 columns.len()
10295 )));
10296 }
10297 for row in rs {
10298 if !all_union_all {
10299 let key = encode_row_key(&row);
10300 if !seen.insert(key) {
10301 continue;
10302 }
10303 }
10304 next_set.push(row);
10305 }
10306 }
10307 if next_set.is_empty() {
10308 break;
10309 }
10310 all_rows.extend(next_set.iter().cloned());
10311 working_set = next_set;
10312 if all_rows.len() > MAX_TOTAL_ROWS {
10313 return Err(EngineError::Unsupported(alloc::format!(
10314 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10315 cte.name
10316 )));
10317 }
10318 if iter + 1 == MAX_ITERATIONS {
10319 return Err(EngineError::Unsupported(alloc::format!(
10320 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10321 cte.name
10322 )));
10323 }
10324 }
10325 Ok((columns, all_rows))
10326 }
10327
10328 fn resolve_select_subqueries(
10329 &self,
10330 stmt: &mut SelectStatement,
10331 cancel: CancelToken<'_>,
10332 ) -> Result<(), EngineError> {
10333 for item in &mut stmt.items {
10334 if let SelectItem::Expr { expr, .. } = item {
10335 self.resolve_expr_subqueries(expr, cancel)?;
10336 }
10337 }
10338 if let Some(w) = &mut stmt.where_ {
10339 self.resolve_expr_subqueries(w, cancel)?;
10340 }
10341 if let Some(gs) = &mut stmt.group_by {
10342 for g in gs {
10343 self.resolve_expr_subqueries(g, cancel)?;
10344 }
10345 }
10346 if let Some(h) = &mut stmt.having {
10347 self.resolve_expr_subqueries(h, cancel)?;
10348 }
10349 for o in &mut stmt.order_by {
10350 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10351 }
10352 for (_, peer) in &mut stmt.unions {
10353 self.resolve_select_subqueries(peer, cancel)?;
10354 }
10355 Ok(())
10356 }
10357
10358 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10360 &self,
10361 e: &mut Expr,
10362 cancel: CancelToken<'_>,
10363 ) -> Result<(), EngineError> {
10364 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10366 *e = replacement;
10367 return Ok(());
10368 }
10369 match e {
10370 Expr::AggregateOrdered { call, order_by } => {
10371 self.resolve_expr_subqueries(call, cancel)?;
10372 for o in order_by.iter_mut() {
10373 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10374 }
10375 }
10376 Expr::Binary { lhs, rhs, .. } => {
10377 self.resolve_expr_subqueries(lhs, cancel)?;
10378 self.resolve_expr_subqueries(rhs, cancel)?;
10379 }
10380 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10381 self.resolve_expr_subqueries(expr, cancel)?;
10382 }
10383 Expr::FunctionCall { args, .. } => {
10384 for a in args {
10385 self.resolve_expr_subqueries(a, cancel)?;
10386 }
10387 }
10388 Expr::Like { expr, pattern, .. } => {
10389 self.resolve_expr_subqueries(expr, cancel)?;
10390 self.resolve_expr_subqueries(pattern, cancel)?;
10391 }
10392 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10393 Expr::WindowFunction {
10396 args,
10397 partition_by,
10398 order_by,
10399 ..
10400 } => {
10401 for a in args {
10402 self.resolve_expr_subqueries(a, cancel)?;
10403 }
10404 for p in partition_by {
10405 self.resolve_expr_subqueries(p, cancel)?;
10406 }
10407 for (e, _) in order_by {
10408 self.resolve_expr_subqueries(e, cancel)?;
10409 }
10410 }
10411 Expr::ScalarSubquery(_)
10415 | Expr::Exists { .. }
10416 | Expr::InSubquery { .. }
10417 | Expr::Literal(_)
10418 | Expr::Placeholder(_)
10419 | Expr::Column(_) => {}
10420 Expr::Array(items) => {
10422 for elem in items {
10423 self.resolve_expr_subqueries(elem, cancel)?;
10424 }
10425 }
10426 Expr::ArraySubscript { target, index } => {
10427 self.resolve_expr_subqueries(target, cancel)?;
10428 self.resolve_expr_subqueries(index, cancel)?;
10429 }
10430 Expr::AnyAll { expr, array, .. } => {
10431 self.resolve_expr_subqueries(expr, cancel)?;
10432 self.resolve_expr_subqueries(array, cancel)?;
10433 }
10434 Expr::Case {
10435 operand,
10436 branches,
10437 else_branch,
10438 } => {
10439 if let Some(o) = operand {
10440 self.resolve_expr_subqueries(o, cancel)?;
10441 }
10442 for (w, t) in branches {
10443 self.resolve_expr_subqueries(w, cancel)?;
10444 self.resolve_expr_subqueries(t, cancel)?;
10445 }
10446 if let Some(e) = else_branch {
10447 self.resolve_expr_subqueries(e, cancel)?;
10448 }
10449 }
10450 }
10451 Ok(())
10452 }
10453
10454 fn eval_expr_with_correlated(
10462 &self,
10463 expr: &Expr,
10464 row: &Row,
10465 ctx: &EvalContext<'_>,
10466 cancel: CancelToken<'_>,
10467 memo: Option<&mut memoize::MemoizeCache>,
10468 ) -> Result<Value, EngineError> {
10469 if !expr_has_subquery(expr) {
10470 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10471 }
10472 let mut e = expr.clone();
10473 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10474 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10475 }
10476
10477 fn resolve_correlated_in_expr(
10478 &self,
10479 e: &mut Expr,
10480 row: &Row,
10481 ctx: &EvalContext<'_>,
10482 cancel: CancelToken<'_>,
10483 mut memo: Option<&mut memoize::MemoizeCache>,
10484 ) -> Result<(), EngineError> {
10485 match e {
10486 Expr::AggregateOrdered { call, order_by } => {
10487 self.resolve_correlated_in_expr(call, row, ctx, cancel, memo.as_deref_mut())?;
10488 for o in order_by.iter_mut() {
10489 self.resolve_correlated_in_expr(
10490 &mut o.expr,
10491 row,
10492 ctx,
10493 cancel,
10494 memo.as_deref_mut(),
10495 )?;
10496 }
10497 }
10498 Expr::ScalarSubquery(inner) => {
10499 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10504 subquery_repr: alloc::format!("{}", **inner),
10505 outer_values: row.values.clone(),
10506 });
10507 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10508 && let Some(cached) = cache.get(k)
10509 {
10510 *e = value_to_literal_expr(cached)?;
10511 return Ok(());
10512 }
10513 let mut s = (**inner).clone();
10514 substitute_outer_columns(&mut s, row, ctx);
10515 let r = self.exec_select_cancel(&s, cancel)?;
10516 let QueryResult::Rows { rows, .. } = r else {
10517 return Err(EngineError::Unsupported(
10518 "scalar subquery: inner did not return rows".into(),
10519 ));
10520 };
10521 let value = match rows.as_slice() {
10522 [] => Value::Null,
10523 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10524 _ => {
10525 return Err(EngineError::Unsupported(alloc::format!(
10526 "scalar subquery returned {} rows; expected 0 or 1",
10527 rows.len()
10528 )));
10529 }
10530 };
10531 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10532 cache.insert(k, value.clone());
10533 }
10534 *e = value_to_literal_expr(value)?;
10535 }
10536 Expr::Exists { subquery, negated } => {
10537 let mut s = (**subquery).clone();
10538 substitute_outer_columns(&mut s, row, ctx);
10539 let r = self.exec_select_cancel(&s, cancel)?;
10540 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10541 let bit = if *negated { !exists } else { exists };
10542 *e = Expr::Literal(Literal::Bool(bit));
10543 }
10544 Expr::InSubquery {
10545 expr: lhs,
10546 subquery,
10547 negated,
10548 } => {
10549 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10550 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10551 let mut s = (**subquery).clone();
10552 substitute_outer_columns(&mut s, row, ctx);
10553 let r = self.exec_select_cancel(&s, cancel)?;
10554 let QueryResult::Rows { columns, rows, .. } = r else {
10555 return Err(EngineError::Unsupported(
10556 "IN-subquery: inner did not return rows".into(),
10557 ));
10558 };
10559 if columns.len() != 1 {
10560 return Err(EngineError::Unsupported(alloc::format!(
10561 "IN-subquery must project exactly one column; got {}",
10562 columns.len()
10563 )));
10564 }
10565 let mut found = false;
10566 let mut any_null = false;
10567 for r0 in rows {
10568 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10569 if v.is_null() {
10570 any_null = true;
10571 continue;
10572 }
10573 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10574 found = true;
10575 break;
10576 }
10577 }
10578 let bit = if found {
10579 !*negated
10580 } else if any_null {
10581 return Err(EngineError::Unsupported(
10582 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10583 ));
10584 } else {
10585 *negated
10586 };
10587 *e = Expr::Literal(Literal::Bool(bit));
10588 }
10589 Expr::Binary { lhs, rhs, .. } => {
10590 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10591 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10592 }
10593 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10594 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10595 }
10596 Expr::Like { expr, pattern, .. } => {
10597 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10598 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10599 }
10600 Expr::FunctionCall { args, .. } => {
10601 for a in args {
10602 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10603 }
10604 }
10605 Expr::Extract { source, .. } => {
10606 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10607 }
10608 Expr::WindowFunction { .. }
10609 | Expr::Literal(_)
10610 | Expr::Placeholder(_)
10611 | Expr::Column(_) => {}
10612 Expr::Array(items) => {
10614 for elem in items {
10615 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10616 }
10617 }
10618 Expr::ArraySubscript { target, index } => {
10619 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10620 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10621 }
10622 Expr::AnyAll { expr, array, .. } => {
10623 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10624 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10625 }
10626 Expr::Case {
10627 operand,
10628 branches,
10629 else_branch,
10630 } => {
10631 if let Some(o) = operand {
10632 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10633 }
10634 for (w, t) in branches {
10635 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10636 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10637 }
10638 if let Some(e) = else_branch {
10639 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10640 }
10641 }
10642 }
10643 Ok(())
10644 }
10645
10646 fn subquery_replacement(
10647 &self,
10648 e: &Expr,
10649 cancel: CancelToken<'_>,
10650 ) -> Result<Option<Expr>, EngineError> {
10651 match e {
10652 Expr::ScalarSubquery(inner) => {
10653 let mut s = (**inner).clone();
10654 self.resolve_select_subqueries(&mut s, cancel)?;
10657 let r = match self.exec_bare_select_cancel(&s, cancel) {
10658 Ok(r) => r,
10659 Err(e) if is_correlation_error(&e) => return Ok(None),
10660 Err(e) => return Err(e),
10661 };
10662 let QueryResult::Rows { rows, .. } = r else {
10663 return Err(EngineError::Unsupported(
10664 "scalar subquery: inner statement did not return rows".into(),
10665 ));
10666 };
10667 let value = match rows.as_slice() {
10668 [] => Value::Null,
10669 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10670 _ => {
10671 return Err(EngineError::Unsupported(alloc::format!(
10672 "scalar subquery returned {} rows; expected 0 or 1",
10673 rows.len()
10674 )));
10675 }
10676 };
10677 Ok(Some(value_to_literal_expr(value)?))
10678 }
10679 Expr::Exists { subquery, negated } => {
10680 let mut s = (**subquery).clone();
10681 self.resolve_select_subqueries(&mut s, cancel)?;
10682 let r = match self.exec_bare_select_cancel(&s, cancel) {
10683 Ok(r) => r,
10684 Err(e) if is_correlation_error(&e) => return Ok(None),
10685 Err(e) => return Err(e),
10686 };
10687 let exists = match r {
10688 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10689 QueryResult::CommandOk { .. } => false,
10690 };
10691 let bit = if *negated { !exists } else { exists };
10692 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10693 }
10694 Expr::InSubquery {
10695 expr,
10696 subquery,
10697 negated,
10698 } => {
10699 let mut s = (**subquery).clone();
10700 self.resolve_select_subqueries(&mut s, cancel)?;
10701 let r = match self.exec_bare_select_cancel(&s, cancel) {
10702 Ok(r) => r,
10703 Err(e) if is_correlation_error(&e) => return Ok(None),
10704 Err(e) => return Err(e),
10705 };
10706 let QueryResult::Rows { columns, rows, .. } = r else {
10707 return Err(EngineError::Unsupported(
10708 "IN-subquery: inner statement did not return rows".into(),
10709 ));
10710 };
10711 if columns.len() != 1 {
10712 return Err(EngineError::Unsupported(alloc::format!(
10713 "IN-subquery must project exactly one column; got {}",
10714 columns.len()
10715 )));
10716 }
10717 let mut acc: Option<Expr> = None;
10720 for row in rows {
10721 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10722 let lit = value_to_literal_expr(v)?;
10723 let cmp = Expr::Binary {
10724 lhs: expr.clone(),
10725 op: BinOp::Eq,
10726 rhs: Box::new(lit),
10727 };
10728 acc = Some(match acc {
10729 None => cmp,
10730 Some(prev) => Expr::Binary {
10731 lhs: Box::new(prev),
10732 op: BinOp::Or,
10733 rhs: Box::new(cmp),
10734 },
10735 });
10736 }
10737 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10738 let final_expr = if *negated {
10739 Expr::Unary {
10740 op: UnOp::Not,
10741 expr: Box::new(combined),
10742 }
10743 } else {
10744 combined
10745 };
10746 Ok(Some(final_expr))
10747 }
10748 _ => Ok(None),
10749 }
10750 }
10751}
10752
10753fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10765 if let Some(from) = &stmt.from
10766 && from_refers_to(from, target)
10767 {
10768 return true;
10769 }
10770 for (_, peer) in &stmt.unions {
10771 if select_refers_to(peer, target) {
10772 return true;
10773 }
10774 }
10775 for item in &stmt.items {
10776 if let SelectItem::Expr { expr, .. } = item
10777 && expr_refers_to(expr, target)
10778 {
10779 return true;
10780 }
10781 }
10782 if let Some(w) = &stmt.where_
10783 && expr_refers_to(w, target)
10784 {
10785 return true;
10786 }
10787 false
10788}
10789
10790fn from_refers_to(from: &FromClause, target: &str) -> bool {
10791 if from.primary.name.eq_ignore_ascii_case(target) {
10792 return true;
10793 }
10794 from.joins
10795 .iter()
10796 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10797}
10798
10799fn expr_refers_to(e: &Expr, target: &str) -> bool {
10800 match e {
10801 Expr::AggregateOrdered { call, order_by } => {
10802 expr_refers_to(call, target) || order_by.iter().any(|o| expr_refers_to(&o.expr, target))
10803 }
10804 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10805 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10806 select_refers_to(subquery, target)
10807 }
10808 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10809 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10810 expr_refers_to(expr, target)
10811 }
10812 Expr::Like { expr, pattern, .. } => {
10813 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10814 }
10815 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10816 Expr::Extract { source, .. } => expr_refers_to(source, target),
10817 Expr::WindowFunction {
10818 args,
10819 partition_by,
10820 order_by,
10821 ..
10822 } => {
10823 args.iter().any(|a| expr_refers_to(a, target))
10824 || partition_by.iter().any(|p| expr_refers_to(p, target))
10825 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10826 }
10827 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10828 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10829 Expr::ArraySubscript { target: t, index } => {
10830 expr_refers_to(t, target) || expr_refers_to(index, target)
10831 }
10832 Expr::AnyAll { expr, array, .. } => {
10833 expr_refers_to(expr, target) || expr_refers_to(array, target)
10834 }
10835 Expr::Case {
10836 operand,
10837 branches,
10838 else_branch,
10839 } => {
10840 operand
10841 .as_deref()
10842 .is_some_and(|o| expr_refers_to(o, target))
10843 || branches
10844 .iter()
10845 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10846 || else_branch
10847 .as_deref()
10848 .is_some_and(|e| expr_refers_to(e, target))
10849 }
10850 }
10851}
10852
10853fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10864 let s = match ty {
10865 DataType::Int => "integer",
10866 DataType::BigInt => "bigint",
10867 DataType::SmallInt => "smallint",
10868 DataType::Float => "double precision",
10869 DataType::Bool => "boolean",
10870 DataType::Text => "text",
10871 DataType::Varchar(_) => "character varying",
10872 DataType::Date => "date",
10873 DataType::Timestamp => "timestamp without time zone",
10874 DataType::Timestamptz => "timestamp with time zone",
10875 DataType::Json => "jsonb",
10876 DataType::Bytes => "bytea",
10877 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10878 DataType::TsVector => "tsvector",
10879 DataType::TsQuery => "tsquery",
10880 DataType::Vector { .. } => "USER-DEFINED",
10881 _ => "USER-DEFINED",
10884 };
10885 alloc::string::String::from(s)
10886}
10887
10888fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10895 let schema = alloc::vec![
10896 ColumnSchema::new("table_catalog", DataType::Text, false),
10897 ColumnSchema::new("table_schema", DataType::Text, false),
10898 ColumnSchema::new("table_name", DataType::Text, false),
10899 ColumnSchema::new("column_name", DataType::Text, false),
10900 ColumnSchema::new("ordinal_position", DataType::Int, false),
10901 ColumnSchema::new("is_nullable", DataType::Text, false),
10902 ColumnSchema::new("data_type", DataType::Text, false),
10903 ];
10904 let mut rows: Vec<Row> = Vec::new();
10905 for tname in cat.table_names() {
10906 let Some(t) = cat.get(&tname) else { continue };
10907 for (i, col) in t.schema().columns.iter().enumerate() {
10908 #[allow(clippy::cast_possible_wrap)]
10909 let ordinal = (i + 1) as i32;
10910 rows.push(Row::new(alloc::vec![
10911 Value::Text("spg".into()),
10912 Value::Text("public".into()),
10913 Value::Text(tname.clone()),
10914 Value::Text(col.name.clone()),
10915 Value::Int(ordinal),
10916 Value::Text(if col.nullable {
10917 "YES".into()
10918 } else {
10919 "NO".into()
10920 }),
10921 Value::Text(pg_data_type_text(col.ty)),
10922 ]));
10923 }
10924 }
10925 (schema, rows)
10926}
10927
10928fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10930 let schema = alloc::vec![
10931 ColumnSchema::new("table_catalog", DataType::Text, false),
10932 ColumnSchema::new("table_schema", DataType::Text, false),
10933 ColumnSchema::new("table_name", DataType::Text, false),
10934 ColumnSchema::new("table_type", DataType::Text, false),
10935 ];
10936 let mut rows: Vec<Row> = Vec::new();
10937 for tname in cat.table_names() {
10938 rows.push(Row::new(alloc::vec![
10939 Value::Text("spg".into()),
10940 Value::Text("public".into()),
10941 Value::Text(tname.clone()),
10942 Value::Text("BASE TABLE".into()),
10943 ]));
10944 }
10945 (schema, rows)
10946}
10947
10948fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10952 let schema = alloc::vec![
10953 ColumnSchema::new("relname", DataType::Text, false),
10954 ColumnSchema::new("relkind", DataType::Text, false),
10955 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10956 ];
10957 let mut rows: Vec<Row> = Vec::new();
10958 for tname in cat.table_names() {
10959 rows.push(Row::new(alloc::vec![
10960 Value::Text(tname.clone()),
10961 Value::Text("r".into()),
10962 Value::BigInt(2200), ]));
10964 }
10965 (schema, rows)
10966}
10967
10968fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10972 let schema = alloc::vec![
10973 ColumnSchema::new("attrelid", DataType::Text, false),
10974 ColumnSchema::new("attname", DataType::Text, false),
10975 ColumnSchema::new("attnum", DataType::Int, false),
10976 ColumnSchema::new("atttypid", DataType::Text, false),
10977 ColumnSchema::new("attnotnull", DataType::Bool, false),
10978 ];
10979 let mut rows: Vec<Row> = Vec::new();
10980 for tname in cat.table_names() {
10981 let Some(t) = cat.get(&tname) else { continue };
10982 for (i, col) in t.schema().columns.iter().enumerate() {
10983 #[allow(clippy::cast_possible_wrap)]
10984 let ordinal = (i + 1) as i32;
10985 rows.push(Row::new(alloc::vec![
10986 Value::Text(tname.clone()),
10987 Value::Text(col.name.clone()),
10988 Value::Int(ordinal),
10989 Value::Text(pg_data_type_text(col.ty)),
10990 Value::Bool(!col.nullable),
10991 ]));
10992 }
10993 }
10994 (schema, rows)
10995}
10996
10997fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11014 let schema = alloc::vec![
11015 ColumnSchema::new("oid", DataType::BigInt, false),
11016 ColumnSchema::new("typname", DataType::Text, false),
11017 ColumnSchema::new("typlen", DataType::SmallInt, false),
11018 ColumnSchema::new("typtype", DataType::Text, false),
11019 ColumnSchema::new("typcategory", DataType::Text, false),
11020 ColumnSchema::new("typelem", DataType::BigInt, false),
11021 ColumnSchema::new("typarray", DataType::BigInt, false),
11022 ColumnSchema::new("typnamespace", DataType::BigInt, false),
11023 ];
11024 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
11027 (16, "bool", 1, "b", "B", 0, 1000),
11029 (17, "bytea", -1, "b", "U", 0, 1001),
11030 (18, "char", 1, "b", "S", 0, 1002),
11031 (19, "name", 64, "b", "S", 0, 1003),
11032 (20, "int8", 8, "b", "N", 0, 1016),
11033 (21, "int2", 2, "b", "N", 0, 1005),
11034 (23, "int4", 4, "b", "N", 0, 1007),
11035 (24, "regproc", 4, "b", "N", 0, 1008),
11036 (25, "text", -1, "b", "S", 0, 1009),
11037 (26, "oid", 4, "b", "N", 0, 1028),
11038 (114, "json", -1, "b", "U", 0, 199),
11039 (142, "xml", -1, "b", "U", 0, 143),
11040 (700, "float4", 4, "b", "N", 0, 1021),
11041 (701, "float8", 8, "b", "N", 0, 1022),
11042 (650, "cidr", -1, "b", "I", 0, 651),
11043 (869, "inet", -1, "b", "I", 0, 1041),
11044 (829, "macaddr", 6, "b", "U", 0, 1040),
11045 (1042, "bpchar", -1, "b", "S", 0, 1014),
11046 (1043, "varchar", -1, "b", "S", 0, 1015),
11047 (1082, "date", 4, "b", "D", 0, 1182),
11048 (1083, "time", 8, "b", "D", 0, 1183),
11049 (1114, "timestamp", 8, "b", "D", 0, 1115),
11050 (1184, "timestamptz", 8, "b", "D", 0, 1185),
11051 (1186, "interval", 16, "b", "T", 0, 1187),
11052 (1266, "timetz", 12, "b", "D", 0, 1270),
11053 (1700, "numeric", -1, "b", "N", 0, 1231),
11054 (790, "money", 8, "b", "N", 0, 791),
11055 (2950, "uuid", 16, "b", "U", 0, 2951),
11056 (3802, "jsonb", -1, "b", "U", 0, 3807),
11057 (3614, "tsvector", -1, "b", "U", 0, 3643),
11058 (3615, "tsquery", -1, "b", "U", 0, 3645),
11059 (3908, "tstzrange", -1, "r", "R", 0, 3909),
11061 (3910, "tsrange", -1, "r", "R", 0, 3911),
11062 (3904, "int4range", -1, "r", "R", 0, 3905),
11063 (3926, "int8range", -1, "r", "R", 0, 3927),
11064 (3906, "numrange", -1, "r", "R", 0, 3907),
11065 (3912, "daterange", -1, "r", "R", 0, 3913),
11066 ];
11067 let arrays: &[(i64, &str, i64)] = &[
11070 (1000, "_bool", 16),
11071 (1001, "_bytea", 17),
11072 (1002, "_char", 18),
11073 (1003, "_name", 19),
11074 (1016, "_int8", 20),
11075 (1005, "_int2", 21),
11076 (1007, "_int4", 23),
11077 (1008, "_regproc", 24),
11078 (1009, "_text", 25),
11079 (1028, "_oid", 26),
11080 (199, "_json", 114),
11081 (143, "_xml", 142),
11082 (1021, "_float4", 700),
11083 (1022, "_float8", 701),
11084 (651, "_cidr", 650),
11085 (1041, "_inet", 869),
11086 (1040, "_macaddr", 829),
11087 (1014, "_bpchar", 1042),
11088 (1015, "_varchar", 1043),
11089 (1182, "_date", 1082),
11090 (1183, "_time", 1083),
11091 (1115, "_timestamp", 1114),
11092 (1185, "_timestamptz", 1184),
11093 (1187, "_interval", 1186),
11094 (1270, "_timetz", 1266),
11095 (1231, "_numeric", 1700),
11096 (791, "_money", 790),
11097 (2951, "_uuid", 2950),
11098 (3807, "_jsonb", 3802),
11099 (3643, "_tsvector", 3614),
11100 (3645, "_tsquery", 3615),
11101 ];
11102 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
11103 for &(oid, name, len, ty, cat, elem, arr) in scalars {
11104 rows.push(Row::new(alloc::vec![
11105 Value::BigInt(oid),
11106 Value::Text(name.into()),
11107 Value::SmallInt(len),
11108 Value::Text(ty.into()),
11109 Value::Text(cat.into()),
11110 Value::BigInt(elem),
11111 Value::BigInt(arr),
11112 Value::BigInt(2200),
11113 ]));
11114 }
11115 for &(oid, name, elem) in arrays {
11116 rows.push(Row::new(alloc::vec![
11117 Value::BigInt(oid),
11118 Value::Text(name.into()),
11119 Value::SmallInt(-1),
11120 Value::Text("b".into()),
11121 Value::Text("A".into()),
11122 Value::BigInt(elem),
11123 Value::BigInt(0),
11124 Value::BigInt(2200),
11125 ]));
11126 }
11127 (schema, rows)
11128}
11129
11130fn synth_pg_trigger(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11149 let schema = alloc::vec![
11150 ColumnSchema::new("tgname", DataType::Text, false),
11151 ColumnSchema::new("relname", DataType::Text, false),
11152 ColumnSchema::new("tgenabled", DataType::Text, false),
11153 ColumnSchema::new("timing", DataType::Text, false),
11154 ColumnSchema::new("events", DataType::Text, false),
11155 ColumnSchema::new("function", DataType::Text, false),
11156 ];
11157 let rows: Vec<Row> = cat
11158 .triggers()
11159 .iter()
11160 .map(|t| {
11161 Row::new(alloc::vec![
11162 Value::Text(t.name.clone()),
11163 Value::Text(t.table.clone()),
11164 Value::Text(if t.enabled { "O".into() } else { "D".into() }),
11165 Value::Text(t.timing.clone()),
11166 Value::Text(t.events.join(" OR ")),
11167 Value::Text(t.function.clone()),
11168 ])
11169 })
11170 .collect();
11171 (schema, rows)
11172}
11173
11174fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11175 let schema = alloc::vec![
11176 ColumnSchema::new("oid", DataType::BigInt, false),
11177 ColumnSchema::new("proname", DataType::Text, false),
11178 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11179 ColumnSchema::new("prokind", DataType::Text, false),
11180 ColumnSchema::new("pronargs", DataType::Int, false),
11181 ColumnSchema::new("prorettype", DataType::BigInt, false),
11182 ];
11183 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11186 (1318, "length", "f", 1, 23),
11188 (871, "upper", "f", 1, 25),
11189 (870, "lower", "f", 1, 25),
11190 (936, "substring", "f", 3, 25),
11191 (937, "substring", "f", 2, 25),
11192 (3055, "btrim", "f", 1, 25),
11193 (885, "btrim", "f", 2, 25),
11194 (3056, "ltrim", "f", 1, 25),
11195 (875, "ltrim", "f", 2, 25),
11196 (3057, "rtrim", "f", 1, 25),
11197 (876, "rtrim", "f", 2, 25),
11198 (1397, "abs", "f", 1, 23),
11199 (1396, "abs", "f", 1, 20),
11200 (1606, "round", "f", 1, 1700),
11201 (1707, "round", "f", 2, 1700),
11202 (2308, "ceil", "f", 1, 701),
11203 (2309, "ceiling", "f", 1, 701),
11204 (2310, "floor", "f", 1, 701),
11205 (1376, "sqrt", "f", 1, 701),
11206 (1369, "ln", "f", 1, 701),
11207 (1373, "exp", "f", 1, 701),
11208 (1368, "power", "f", 2, 701),
11209 (2228, "random", "f", 0, 701),
11210 (1299, "now", "f", 0, 1184),
11212 (1274, "current_timestamp", "f", 0, 1184),
11213 (1140, "current_date", "f", 0, 1082),
11214 (2050, "current_time", "f", 0, 1083),
11215 (1158, "date_trunc", "f", 2, 1184),
11216 (1171, "date_part", "f", 2, 701),
11217 (1172, "age", "f", 1, 1186),
11218 (936, "to_char", "f", 2, 25),
11219 (861, "current_database", "f", 0, 19),
11221 (745, "current_user", "f", 0, 19),
11222 (745, "session_user", "f", 0, 19),
11223 (1402, "current_schema", "f", 0, 19),
11224 (3058, "concat", "f", -1, 25),
11226 (3059, "concat_ws", "f", -1, 25),
11227 (3539, "format", "f", -1, 25),
11228 (2877, "pg_typeof", "f", 1, 2206),
11230 (3198, "json_build_object", "f", -1, 114),
11232 (3199, "jsonb_build_object", "f", -1, 3802),
11233 (3271, "json_build_array", "f", -1, 114),
11234 (3272, "jsonb_build_array", "f", -1, 3802),
11235 (3253, "gen_random_uuid", "f", 0, 2950),
11237 (3252, "uuid_generate_v4", "f", 0, 2950),
11238 (2147, "count", "a", 0, 20),
11240 (2803, "count", "a", -1, 20),
11241 (2116, "max", "a", 1, 23),
11242 (2132, "min", "a", 1, 23),
11243 (2108, "sum", "a", 1, 20),
11244 (2100, "avg", "a", 1, 1700),
11245 (2517, "string_agg", "a", 2, 25),
11246 (2747, "array_agg", "a", 1, 1009),
11247 (2517, "bool_and", "a", 1, 16),
11248 (2518, "bool_or", "a", 1, 16),
11249 (2519, "every", "a", 1, 16),
11250 (3100, "row_number", "w", 0, 20),
11252 (3101, "rank", "w", 0, 20),
11253 (3102, "dense_rank", "w", 0, 20),
11254 (3103, "percent_rank", "w", 0, 701),
11255 (3104, "cume_dist", "w", 0, 701),
11256 (3105, "lag", "w", -1, 2283),
11257 (3106, "lead", "w", -1, 2283),
11258 (3107, "first_value", "w", 1, 2283),
11259 (3108, "last_value", "w", 1, 2283),
11260 (3109, "nth_value", "w", 2, 2283),
11261 ];
11262 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11263 for &(oid, name, kind, nargs, rettype) in funcs {
11264 rows.push(Row::new(alloc::vec![
11265 Value::BigInt(oid),
11266 Value::Text(name.into()),
11267 Value::BigInt(11),
11268 Value::Text(kind.into()),
11269 Value::Int(nargs),
11270 Value::BigInt(rettype),
11271 ]));
11272 }
11273 (schema, rows)
11274}
11275
11276fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11282 let schema = alloc::vec![
11283 ColumnSchema::new("user", DataType::Text, false),
11284 ColumnSchema::new("host", DataType::Text, false),
11285 ColumnSchema::new("select_priv", DataType::Text, false),
11286 ];
11287 let mut rows: Vec<Row> = Vec::new();
11288 rows.push(Row::new(alloc::vec![
11289 Value::Text("root".into()),
11290 Value::Text("localhost".into()),
11291 Value::Text("Y".into()),
11292 ]));
11293 for (name, _) in engine.users.iter() {
11294 if name != "root" {
11295 rows.push(Row::new(alloc::vec![
11296 Value::Text(name.to_string()),
11297 Value::Text("%".into()),
11298 Value::Text("Y".into()),
11299 ]));
11300 }
11301 }
11302 (schema, rows)
11303}
11304
11305fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11310 let schema = alloc::vec![
11311 ColumnSchema::new("host", DataType::Text, false),
11312 ColumnSchema::new("db", DataType::Text, false),
11313 ColumnSchema::new("user", DataType::Text, false),
11314 ColumnSchema::new("select_priv", DataType::Text, false),
11315 ];
11316 let rows = alloc::vec![Row::new(alloc::vec![
11317 Value::Text("localhost".into()),
11318 Value::Text("postgres".into()),
11319 Value::Text("root".into()),
11320 Value::Text("Y".into()),
11321 ])];
11322 (schema, rows)
11323}
11324
11325fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11338 let schema = alloc::vec![
11339 ColumnSchema::new("constraint_name", DataType::Text, false),
11340 ColumnSchema::new("table_name", DataType::Text, false),
11341 ColumnSchema::new("column_name", DataType::Text, false),
11342 ColumnSchema::new("ordinal_position", DataType::Int, false),
11343 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11344 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11345 ];
11346 let mut rows: Vec<Row> = Vec::new();
11347 for tname in cat.table_names() {
11348 let Some(t) = cat.get(&tname) else { continue };
11349 let cols = &t.schema().columns;
11350 let col_name_at = |pos: usize| -> String {
11351 cols.get(pos)
11352 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11353 };
11354 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11356 let conname = fk
11357 .name
11358 .clone()
11359 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11360 for (i, (&local, &parent)) in fk
11361 .local_columns
11362 .iter()
11363 .zip(fk.parent_columns.iter())
11364 .enumerate()
11365 {
11366 let parent_name = cat
11367 .get(&fk.parent_table)
11368 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11369 .unwrap_or_else(|| alloc::format!("col{parent}"));
11370 #[allow(clippy::cast_possible_wrap)]
11371 let ordinal = (i + 1) as i32;
11372 rows.push(Row::new(alloc::vec![
11373 Value::Text(conname.clone()),
11374 Value::Text(tname.clone()),
11375 Value::Text(col_name_at(local)),
11376 Value::Int(ordinal),
11377 Value::Text(fk.parent_table.clone()),
11378 Value::Text(parent_name),
11379 ]));
11380 }
11381 }
11382 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11384 let conname = if uc.is_primary_key {
11385 alloc::format!("{}_pkey", tname)
11386 } else {
11387 alloc::format!("{}_uniq{ci}", tname)
11388 };
11389 for (i, &local) in uc.columns.iter().enumerate() {
11390 #[allow(clippy::cast_possible_wrap)]
11391 let ordinal = (i + 1) as i32;
11392 rows.push(Row::new(alloc::vec![
11393 Value::Text(conname.clone()),
11394 Value::Text(tname.clone()),
11395 Value::Text(col_name_at(local)),
11396 Value::Int(ordinal),
11397 Value::Text(String::new()),
11398 Value::Text(String::new()),
11399 ]));
11400 }
11401 }
11402 }
11403 (schema, rows)
11404}
11405
11406fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11409 let schema = alloc::vec![
11410 ColumnSchema::new("constraint_name", DataType::Text, false),
11411 ColumnSchema::new("table_name", DataType::Text, false),
11412 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11413 ColumnSchema::new("update_rule", DataType::Text, false),
11414 ColumnSchema::new("delete_rule", DataType::Text, false),
11415 ];
11416 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11417 match a {
11418 spg_storage::FkAction::Cascade => "CASCADE",
11419 spg_storage::FkAction::SetNull => "SET NULL",
11420 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11421 spg_storage::FkAction::Restrict => "RESTRICT",
11422 spg_storage::FkAction::NoAction => "NO ACTION",
11423 }
11424 }
11425 let mut rows: Vec<Row> = Vec::new();
11426 for tname in cat.table_names() {
11427 let Some(t) = cat.get(&tname) else { continue };
11428 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11429 let conname = fk
11430 .name
11431 .clone()
11432 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11433 rows.push(Row::new(alloc::vec![
11434 Value::Text(conname),
11435 Value::Text(tname.clone()),
11436 Value::Text(fk.parent_table.clone()),
11437 Value::Text(rule_name(fk.on_update).into()),
11438 Value::Text(rule_name(fk.on_delete).into()),
11439 ]));
11440 }
11441 }
11442 (schema, rows)
11443}
11444
11445fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11449 let schema = alloc::vec![
11450 ColumnSchema::new("table_name", DataType::Text, false),
11451 ColumnSchema::new("index_name", DataType::Text, false),
11452 ColumnSchema::new("column_name", DataType::Text, false),
11453 ColumnSchema::new("seq_in_index", DataType::Int, false),
11454 ColumnSchema::new("non_unique", DataType::Int, false),
11455 ColumnSchema::new("index_type", DataType::Text, false),
11456 ];
11457 let mut rows: Vec<Row> = Vec::new();
11458 for tname in cat.table_names() {
11459 let Some(t) = cat.get(&tname) else { continue };
11460 for idx in t.indices() {
11461 let col = t
11462 .schema()
11463 .columns
11464 .get(idx.column_position)
11465 .map_or("?".into(), |c| c.name.clone());
11466 rows.push(Row::new(alloc::vec![
11467 Value::Text(tname.clone()),
11468 Value::Text(idx.name.clone()),
11469 Value::Text(col),
11470 Value::Int(1),
11471 Value::Int(i32::from(!idx.is_unique)),
11472 Value::Text("BTREE".into()),
11473 ]));
11474 }
11475 }
11476 (schema, rows)
11477}
11478
11479fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11483 let schema = alloc::vec![
11484 ColumnSchema::new("routine_name", DataType::Text, false),
11485 ColumnSchema::new("routine_type", DataType::Text, false),
11486 ColumnSchema::new("data_type", DataType::Text, false),
11487 ];
11488 (schema, Vec::new())
11489}
11490
11491fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11506 let schema = alloc::vec![
11507 ColumnSchema::new("conname", DataType::Text, false),
11508 ColumnSchema::new("contype", DataType::Text, false),
11509 ColumnSchema::new("conrelid", DataType::Text, false),
11510 ColumnSchema::new("confrelid", DataType::Text, false),
11511 ColumnSchema::new("conkey", DataType::Text, false),
11512 ColumnSchema::new("confkey", DataType::Text, false),
11513 ];
11514 let mut rows: Vec<Row> = Vec::new();
11515 for tname in cat.table_names() {
11516 let Some(t) = cat.get(&tname) else { continue };
11517 let cols = &t.schema().columns;
11518 let col_name_at = |pos: usize| -> String {
11519 cols.get(pos)
11520 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11521 };
11522 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11524 let kind = if uc.is_primary_key { "p" } else { "u" };
11525 let conname = if uc.is_primary_key {
11526 alloc::format!("{}_pkey", tname)
11527 } else {
11528 alloc::format!("{}_uniq{ci}", tname)
11529 };
11530 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11531 rows.push(Row::new(alloc::vec![
11532 Value::Text(conname),
11533 Value::Text(kind.into()),
11534 Value::Text(tname.clone()),
11535 Value::Text(String::new()),
11536 Value::Text(conkey.join(",")),
11537 Value::Text(String::new()),
11538 ]));
11539 }
11540 for idx in t.indices() {
11545 if !idx.is_unique {
11546 continue;
11547 }
11548 let is_primary = idx.name.ends_with("_pkey");
11549 let conname = idx.name.clone();
11550 let kind = if is_primary { "p" } else { "u" };
11551 let col_name = col_name_at(idx.column_position);
11552 let already = t
11555 .schema()
11556 .uniqueness_constraints
11557 .iter()
11558 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11559 if already {
11560 continue;
11561 }
11562 rows.push(Row::new(alloc::vec![
11563 Value::Text(conname),
11564 Value::Text(kind.into()),
11565 Value::Text(tname.clone()),
11566 Value::Text(String::new()),
11567 Value::Text(col_name),
11568 Value::Text(String::new()),
11569 ]));
11570 }
11571 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11573 let conname = fk
11574 .name
11575 .clone()
11576 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11577 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11578 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11581 fk.parent_columns
11582 .iter()
11583 .map(|&p| {
11584 parent
11585 .schema()
11586 .columns
11587 .get(p)
11588 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11589 })
11590 .collect()
11591 } else {
11592 fk.parent_columns
11593 .iter()
11594 .map(|p| alloc::format!("col{p}"))
11595 .collect()
11596 };
11597 rows.push(Row::new(alloc::vec![
11598 Value::Text(conname),
11599 Value::Text("f".into()),
11600 Value::Text(tname.clone()),
11601 Value::Text(fk.parent_table.clone()),
11602 Value::Text(conkey.join(",")),
11603 Value::Text(confkey.join(",")),
11604 ]));
11605 }
11606 }
11607 (schema, rows)
11608}
11609
11610fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11615 let schema = alloc::vec![
11616 ColumnSchema::new("oid", DataType::BigInt, false),
11617 ColumnSchema::new("datname", DataType::Text, false),
11618 ColumnSchema::new("datdba", DataType::BigInt, false),
11619 ColumnSchema::new("encoding", DataType::Int, false),
11620 ColumnSchema::new("datcollate", DataType::Text, false),
11621 ];
11622 let rows = alloc::vec![Row::new(alloc::vec![
11623 Value::BigInt(16384),
11624 Value::Text("postgres".into()),
11625 Value::BigInt(10),
11626 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11628 ])];
11629 (schema, rows)
11630}
11631
11632fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11637 let schema = alloc::vec![
11638 ColumnSchema::new("oid", DataType::BigInt, false),
11639 ColumnSchema::new("rolname", DataType::Text, false),
11640 ColumnSchema::new("rolsuper", DataType::Bool, false),
11641 ColumnSchema::new("rolinherit", DataType::Bool, false),
11642 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11643 ];
11644 let mut rows: Vec<Row> = Vec::new();
11645 let oid: i64 = 10;
11646 for (i, (name, _)) in engine.users.iter().enumerate() {
11647 rows.push(Row::new(alloc::vec![
11648 Value::BigInt(oid + (i as i64) + 1),
11649 Value::Text(name.to_string()),
11650 Value::Bool(false),
11651 Value::Bool(true),
11652 Value::Bool(true),
11653 ]));
11654 }
11655 if !rows
11658 .iter()
11659 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11660 {
11661 rows.insert(
11662 0,
11663 Row::new(alloc::vec![
11664 Value::BigInt(10),
11665 Value::Text("postgres".into()),
11666 Value::Bool(true),
11667 Value::Bool(true),
11668 Value::Bool(true),
11669 ]),
11670 );
11671 }
11672 (schema, rows)
11673}
11674
11675fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11684 let schema = alloc::vec![
11685 ColumnSchema::new("oid", DataType::BigInt, false),
11686 ColumnSchema::new("extname", DataType::Text, false),
11687 ColumnSchema::new("extversion", DataType::Text, false),
11688 ColumnSchema::new("extnamespace", DataType::Text, false),
11689 ];
11690 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11691 let rows = exts
11692 .iter()
11693 .enumerate()
11694 .map(|(i, (name, ver))| {
11695 Row::new(alloc::vec![
11696 Value::BigInt(16384 + i as i64),
11697 Value::Text((*name).into()),
11698 Value::Text((*ver).into()),
11699 Value::Text("pg_catalog".into()),
11700 ])
11701 })
11702 .collect();
11703 (schema, rows)
11704}
11705
11706fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11707 let schema = alloc::vec![
11708 ColumnSchema::new("schemaname", DataType::Text, false),
11709 ColumnSchema::new("viewname", DataType::Text, false),
11710 ColumnSchema::new("definition", DataType::Text, false),
11711 ];
11712 let mut rows: Vec<Row> = Vec::new();
11713 for (name, def) in cat.views() {
11714 rows.push(Row::new(alloc::vec![
11715 Value::Text("public".into()),
11716 Value::Text(name.clone()),
11717 Value::Text(def.body.clone()),
11718 ]));
11719 }
11720 (schema, rows)
11721}
11722
11723fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11729 let schema = alloc::vec![
11730 ColumnSchema::new("name", DataType::Text, false),
11731 ColumnSchema::new("setting", DataType::Text, false),
11732 ColumnSchema::new("category", DataType::Text, false),
11733 ];
11734 let mut rows: Vec<Row> = Vec::new();
11735 let defaults: &[(&str, &str, &str)] = &[
11737 ("server_version", "16.0 (spg)", "Preset Options"),
11738 ("server_encoding", "UTF8", "Client Connection Defaults"),
11739 ("client_encoding", "UTF8", "Client Connection Defaults"),
11740 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11741 ("TimeZone", "UTC", "Client Connection Defaults"),
11742 ("standard_conforming_strings", "on", "Compatibility"),
11743 ("integer_datetimes", "on", "Compatibility"),
11744 ("max_connections", "100", "Connections and Authentication"),
11745 ];
11746 for &(name, val, cat) in defaults {
11747 rows.push(Row::new(alloc::vec![
11748 Value::Text(name.into()),
11749 Value::Text(val.into()),
11750 Value::Text(cat.into()),
11751 ]));
11752 }
11753 for (k, v) in &engine.session_params {
11755 if !defaults
11756 .iter()
11757 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11758 {
11759 rows.push(Row::new(alloc::vec![
11760 Value::Text(k.clone()),
11761 Value::Text(v.clone()),
11762 Value::Text("Session".into()),
11763 ]));
11764 }
11765 }
11766 (schema, rows)
11767}
11768
11769fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11780 let schema = alloc::vec![
11781 ColumnSchema::new("schemaname", DataType::Text, false),
11782 ColumnSchema::new("tablename", DataType::Text, false),
11783 ColumnSchema::new("indexname", DataType::Text, false),
11784 ColumnSchema::new("indexdef", DataType::Text, false),
11785 ];
11786 let mut rows: Vec<Row> = Vec::new();
11787 for tname in cat.table_names() {
11788 let Some(t) = cat.get(&tname) else { continue };
11789 for idx in t.indices() {
11790 let col_name = t
11791 .schema()
11792 .columns
11793 .get(idx.column_position)
11794 .map_or("?".into(), |c| c.name.clone());
11795 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11796 let indexdef = alloc::format!(
11797 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11798 idx.name,
11799 tname,
11800 col_name
11801 );
11802 rows.push(Row::new(alloc::vec![
11803 Value::Text("public".into()),
11804 Value::Text(tname.clone()),
11805 Value::Text(idx.name.clone()),
11806 Value::Text(indexdef),
11807 ]));
11808 }
11809 }
11810 (schema, rows)
11811}
11812
11813fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11825 let schema = alloc::vec![
11826 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11827 ColumnSchema::new("indrelid", DataType::BigInt, false),
11828 ColumnSchema::new("indnatts", DataType::Int, false),
11829 ColumnSchema::new("indisunique", DataType::Bool, false),
11830 ColumnSchema::new("indisprimary", DataType::Bool, false),
11831 ];
11832 let mut rows: Vec<Row> = Vec::new();
11833 let mut idx_oid: i64 = 100_000;
11834 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11835 let Some(t) = cat.get(tname) else { continue };
11836 for idx in t.indices() {
11837 idx_oid += 1;
11838 #[allow(clippy::cast_possible_wrap)]
11839 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11840 let is_primary = idx.name.ends_with("_pkey");
11843 rows.push(Row::new(alloc::vec![
11844 Value::BigInt(idx_oid),
11845 Value::BigInt((table_idx + 1) as i64),
11846 Value::Int(nattrs),
11847 Value::Bool(idx.is_unique),
11848 Value::Bool(is_primary),
11849 ]));
11850 }
11851 }
11852 (schema, rows)
11853}
11854
11855fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11860 let schema = alloc::vec![
11861 ColumnSchema::new("oid", DataType::BigInt, false),
11862 ColumnSchema::new("nspname", DataType::Text, false),
11863 ColumnSchema::new("nspowner", DataType::BigInt, false),
11864 ];
11865 let rows = alloc::vec![
11866 Row::new(alloc::vec![
11867 Value::BigInt(11),
11868 Value::Text("pg_catalog".into()),
11869 Value::BigInt(10),
11870 ]),
11871 Row::new(alloc::vec![
11872 Value::BigInt(2200),
11873 Value::Text("public".into()),
11874 Value::BigInt(10),
11875 ]),
11876 Row::new(alloc::vec![
11877 Value::BigInt(13000),
11878 Value::Text("information_schema".into()),
11879 Value::BigInt(10),
11880 ]),
11881 ];
11882 (schema, rows)
11883}
11884
11885fn materialise_meta_view(
11888 catalog: &mut Catalog,
11889 name: &str,
11890 columns: Vec<ColumnSchema>,
11891 rows: Vec<Row>,
11892) -> Result<(), EngineError> {
11893 let schema = TableSchema::new(name.to_string(), columns);
11894 catalog.create_table(schema).map_err(EngineError::Storage)?;
11895 let table = catalog
11896 .get_mut(name)
11897 .expect("just-created meta view must exist");
11898 for row in rows {
11899 table.insert(row).map_err(EngineError::Storage)?;
11900 }
11901 Ok(())
11902}
11903
11904fn collect_view_refs(
11917 tref: &spg_sql::ast::TableRef,
11918 cat: &spg_storage::Catalog,
11919 into: &mut Vec<String>,
11920) {
11921 if cat.views().contains_key(&tref.name)
11922 && cat.get(&tref.name).is_none()
11923 && !into.iter().any(|n| n == &tref.name)
11924 {
11925 into.push(tref.name.clone());
11926 }
11927}
11928
11929fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11930 fn is_meta(name: &str) -> bool {
11931 name.starts_with("__spg_info_")
11932 || name.starts_with("__spg_pg_")
11933 || name.starts_with("__spg_mysql_")
11934 }
11935 if let Some(from) = &stmt.from {
11936 if is_meta(&from.primary.name) {
11937 return true;
11938 }
11939 for j in &from.joins {
11940 if is_meta(&j.table.name) {
11941 return true;
11942 }
11943 }
11944 }
11945 for cte in &stmt.ctes {
11946 if select_references_meta_view(&cte.body) {
11947 return true;
11948 }
11949 }
11950 false
11951}
11952
11953fn collect_meta_view_names(
11958 stmt: &SelectStatement,
11959 into: &mut alloc::collections::BTreeSet<String>,
11960) {
11961 fn is_meta(name: &str) -> bool {
11962 name.starts_with("__spg_info_")
11963 || name.starts_with("__spg_pg_")
11964 || name.starts_with("__spg_mysql_")
11965 }
11966 if let Some(from) = &stmt.from {
11967 if is_meta(&from.primary.name) {
11968 into.insert(from.primary.name.clone());
11969 }
11970 for j in &from.joins {
11971 if is_meta(&j.table.name) {
11972 into.insert(j.table.name.clone());
11973 }
11974 }
11975 }
11976 for cte in &stmt.ctes {
11977 collect_meta_view_names(&cte.body, into);
11978 }
11979}
11980
11981fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11982 let mut out = columns.to_vec();
11983 for (col_idx, col) in out.iter_mut().enumerate() {
11984 if col.ty != DataType::Text {
11985 continue;
11986 }
11987 let mut inferred: Option<DataType> = None;
11988 let mut all_null = true;
11989 for row in rows {
11990 let Some(v) = row.values.get(col_idx) else {
11991 continue;
11992 };
11993 let ty = match v {
11994 Value::Null => continue,
11995 Value::SmallInt(_) => DataType::SmallInt,
11996 Value::Int(_) => DataType::Int,
11997 Value::BigInt(_) => DataType::BigInt,
11998 Value::Float(_) => DataType::Float,
11999 Value::Bool(_) => DataType::Bool,
12000 Value::Vector(_) => DataType::Vector {
12001 dim: 0,
12002 encoding: VecEncoding::F32,
12003 },
12004 _ => DataType::Text,
12005 };
12006 all_null = false;
12007 inferred = Some(match inferred {
12008 None => ty,
12009 Some(prev) if prev == ty => prev,
12010 Some(_) => DataType::Text,
12011 });
12012 }
12013 if let Some(t) = inferred {
12014 col.ty = t;
12015 col.nullable = true;
12016 } else if all_null {
12017 col.nullable = true;
12018 }
12019 }
12020 out
12021}
12022
12023#[allow(clippy::too_many_lines, clippy::format_push_string)]
12028fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
12045 use alloc::collections::BTreeSet;
12046 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
12047 let mut out: Vec<String> = Vec::new();
12048 let cat = engine.active_catalog();
12049 let Some(from) = &stmt.from else {
12053 return out;
12054 };
12055 let mut tables: Vec<String> = Vec::new();
12056 tables.push(from.primary.name.clone());
12057 for j in &from.joins {
12058 tables.push(j.table.name.clone());
12059 }
12060 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
12063 if let Some(w) = &stmt.where_ {
12064 collect_column_refs(w, &mut col_refs);
12065 }
12066 for j in &from.joins {
12067 if let Some(on) = &j.on {
12068 collect_column_refs(on, &mut col_refs);
12069 }
12070 }
12071 for cn in &col_refs {
12072 let owner: Option<String> = if let Some(q) = &cn.qualifier {
12075 tables.iter().find(|t| t == &q).cloned()
12076 } else {
12077 tables.iter().find_map(|t| {
12078 cat.get(t).and_then(|tbl| {
12079 if tbl.schema().column_position(&cn.name).is_some() {
12080 Some(t.clone())
12081 } else {
12082 None
12083 }
12084 })
12085 })
12086 };
12087 let Some(owner) = owner else {
12088 continue;
12089 };
12090 let Some(tbl) = cat.get(&owner) else {
12091 continue;
12092 };
12093 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
12094 continue;
12095 };
12096 let already_indexed = tbl.indices().iter().any(|i| {
12099 matches!(i.kind, spg_storage::IndexKind::BTree(_))
12100 && i.column_position == col_pos
12101 && i.expression.is_none()
12102 && i.partial_predicate.is_none()
12103 });
12104 if already_indexed {
12105 continue;
12106 }
12107 if seen.insert((owner.clone(), cn.name.clone())) {
12108 out.push(alloc::format!(
12109 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
12110 owner,
12111 cn.name,
12112 owner,
12113 cn.name
12114 ));
12115 }
12116 }
12117 out
12118}
12119
12120fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
12123 match expr {
12124 Expr::Column(cn) => out.push(cn.clone()),
12125 Expr::FunctionCall { args, .. } => {
12126 for a in args {
12127 collect_column_refs(a, out);
12128 }
12129 }
12130 Expr::Binary { lhs, rhs, .. } => {
12131 collect_column_refs(lhs, out);
12132 collect_column_refs(rhs, out);
12133 }
12134 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
12135 _ => {}
12136 }
12137}
12138
12139fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
12140 let catalog = engine.active_catalog();
12141 let cold_ids = catalog.cold_segment_ids_global();
12142 let any_cold = !cold_ids.is_empty();
12143 let cold_ids_repr = if any_cold {
12144 let mut s = alloc::string::String::from("[");
12145 for (i, id) in cold_ids.iter().enumerate() {
12146 if i > 0 {
12147 s.push(',');
12148 }
12149 s.push_str(&alloc::format!("{id}"));
12150 }
12151 s.push(']');
12152 s
12153 } else {
12154 alloc::string::String::new()
12155 };
12156 for (idx, line) in lines.iter_mut().enumerate() {
12157 let trimmed = line.trim_start();
12158 let is_top_level = idx == 0;
12159 if is_top_level {
12160 line.push_str(&alloc::format!(" (rows={total_rows})"));
12161 continue;
12162 }
12163 if let Some(rest) = trimmed.strip_prefix("From: ") {
12164 let (name, scan_kind) = match rest.split_once(" [") {
12165 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12166 None => (rest.trim(), ""),
12167 };
12168 let bare = name.split_whitespace().next().unwrap_or(name);
12169 let hot = catalog.get(bare).map(|t| t.rows().len());
12170 let annot = match (hot, scan_kind) {
12175 (Some(h), "full scan") => {
12176 let mut s = alloc::format!(" (hot_rows={h}");
12177 if any_cold {
12178 s.push_str(&alloc::format!(
12179 ", cold_tier=present, cold_segments={cold_ids_repr}"
12180 ));
12181 }
12182 s.push(')');
12183 s
12184 }
12185 (Some(h), "index seek") => {
12186 let mut s = alloc::format!(" (hot_rows≤{h}");
12187 if any_cold {
12188 s.push_str(&alloc::format!(
12189 ", cold_tier=present, cold_segments={cold_ids_repr}"
12190 ));
12191 }
12192 s.push(')');
12193 s
12194 }
12195 _ => " (rows=—)".to_string(),
12196 };
12197 line.push_str(&annot);
12198 continue;
12199 }
12200 line.push_str(" (rows=—)");
12202 }
12203}
12204
12205fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
12206 let pad = " ".repeat(depth);
12207 let top = if !stmt.ctes.is_empty() {
12209 if stmt.ctes.iter().any(|c| c.recursive) {
12210 "CTEScan (WITH RECURSIVE)"
12211 } else {
12212 "CTEScan (WITH)"
12213 }
12214 } else if !stmt.unions.is_empty() {
12215 "UnionScan"
12216 } else if select_has_window(stmt) {
12217 "WindowAgg"
12218 } else if aggregate::uses_aggregate(stmt) {
12219 "Aggregate"
12220 } else if stmt.distinct {
12221 "Distinct"
12222 } else if stmt.from.is_some() {
12223 "TableScan"
12224 } else {
12225 "Result"
12226 };
12227 out.push(alloc::format!("{pad}{top}"));
12228 let child = " ".repeat(depth + 1);
12229 for cte in &stmt.ctes {
12231 let head = if cte.recursive {
12232 alloc::format!("{child}CTE (recursive): {}", cte.name)
12233 } else {
12234 alloc::format!("{child}CTE: {}", cte.name)
12235 };
12236 out.push(head);
12237 explain_select(&cte.body, engine, depth + 2, out);
12238 }
12239 if let Some(from) = &stmt.from {
12241 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12242 if let Some(alias) = &from.primary.alias {
12243 tag.push_str(&alloc::format!(" AS {alias}"));
12244 }
12245 if let Some(w) = &stmt.where_
12248 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12249 {
12250 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12251 let cols = &table.schema().columns;
12252 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12253 tag.push_str(" [index seek]");
12254 } else {
12255 tag.push_str(" [full scan]");
12256 }
12257 } else {
12258 tag.push_str(" [full scan]");
12259 }
12260 out.push(tag);
12261 for j in &from.joins {
12262 let kind = match j.kind {
12263 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12264 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12265 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12266 };
12267 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12268 if let Some(alias) = &j.table.alias {
12269 s.push_str(&alloc::format!(" AS {alias}"));
12270 }
12271 if j.on.is_some() {
12272 s.push_str(" (ON …)");
12273 }
12274 out.push(s);
12275 }
12276 }
12277 if let Some(w) = &stmt.where_ {
12279 let mut s = alloc::format!("{child}Filter: {w}");
12280 if expr_has_subquery(w) {
12281 s.push_str(" [subquery]");
12282 }
12283 out.push(s);
12284 }
12285 if let Some(gs) = &stmt.group_by {
12286 let mut parts = Vec::new();
12287 for g in gs {
12288 parts.push(alloc::format!("{g}"));
12289 }
12290 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12291 }
12292 if let Some(h) = &stmt.having {
12293 out.push(alloc::format!("{child}Having: {h}"));
12294 }
12295 for o in &stmt.order_by {
12296 let dir = if o.desc { "DESC" } else { "ASC" };
12297 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12298 }
12299 if let Some(lim) = stmt.limit {
12300 out.push(alloc::format!("{child}Limit: {lim}"));
12301 }
12302 if let Some(off) = stmt.offset {
12303 out.push(alloc::format!("{child}Offset: {off}"));
12304 }
12305 if stmt
12307 .items
12308 .iter()
12309 .any(|it| matches!(it, SelectItem::Wildcard))
12310 {
12311 out.push(alloc::format!("{child}Project: *"));
12312 } else {
12313 out.push(alloc::format!(
12314 "{child}Project: {} item(s)",
12315 stmt.items.len()
12316 ));
12317 }
12318 for (kind, peer) in &stmt.unions {
12320 let label = match kind {
12321 UnionKind::All => "UNION ALL",
12322 UnionKind::Distinct => "UNION",
12323 };
12324 out.push(alloc::format!("{child}{label}"));
12325 explain_select(peer, engine, depth + 2, out);
12326 }
12327}
12328
12329fn is_correlation_error(e: &EngineError) -> bool {
12334 matches!(
12335 e,
12336 EngineError::Eval(
12337 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12338 )
12339 )
12340}
12341
12342struct JoinedPeer<'a> {
12353 eager_rows: Option<Vec<Row>>,
12354 cols: Vec<ColumnSchema>,
12355 alias: String,
12356 kind: JoinKind,
12357 on: Option<&'a Expr>,
12358 lateral: Option<&'a SelectStatement>,
12359}
12360
12361fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12368 match expr {
12369 Expr::Column(c) => c.name.clone(),
12371 Expr::FunctionCall { name, .. } => name.clone(),
12374 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12376 _ => alloc::format!("column{}", idx + 1),
12378 }
12379}
12380
12381fn substitute_outer_columns_multi(
12388 stmt: &mut SelectStatement,
12389 outer_row: &Row,
12390 outer_schema: &[ColumnSchema],
12391) {
12392 substitute_outer_in_select(stmt, outer_row, outer_schema);
12393}
12394
12395fn substitute_outer_in_select(
12396 stmt: &mut SelectStatement,
12397 outer_row: &Row,
12398 outer_schema: &[ColumnSchema],
12399) {
12400 for item in &mut stmt.items {
12401 if let SelectItem::Expr { expr, .. } = item {
12402 substitute_outer_in_expr(expr, outer_row, outer_schema);
12403 }
12404 }
12405 if let Some(w) = &mut stmt.where_ {
12406 substitute_outer_in_expr(w, outer_row, outer_schema);
12407 }
12408 if let Some(gs) = &mut stmt.group_by {
12409 for g in gs {
12410 substitute_outer_in_expr(g, outer_row, outer_schema);
12411 }
12412 }
12413 if let Some(h) = &mut stmt.having {
12414 substitute_outer_in_expr(h, outer_row, outer_schema);
12415 }
12416 for o in &mut stmt.order_by {
12417 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12418 }
12419 for (_, peer) in &mut stmt.unions {
12420 substitute_outer_in_select(peer, outer_row, outer_schema);
12421 }
12422}
12423
12424fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12425 if let Expr::Column(c) = e
12426 && let Some(qual) = &c.qualifier
12427 {
12428 let composite = alloc::format!("{qual}.{}", c.name);
12429 if let Some(idx) = outer_schema
12430 .iter()
12431 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12432 {
12433 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12434 if let Ok(lit) = value_to_literal_expr(v) {
12435 *e = lit;
12436 return;
12437 }
12438 }
12439 }
12440 match e {
12441 Expr::Binary { lhs, rhs, .. } => {
12442 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12443 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12444 }
12445 Expr::Unary { expr: inner, .. } => {
12446 substitute_outer_in_expr(inner, outer_row, outer_schema);
12447 }
12448 Expr::FunctionCall { args, .. } => {
12449 for a in args {
12450 substitute_outer_in_expr(a, outer_row, outer_schema);
12451 }
12452 }
12453 Expr::Cast { expr: inner, .. } => {
12454 substitute_outer_in_expr(inner, outer_row, outer_schema);
12455 }
12456 Expr::Case {
12457 operand,
12458 branches,
12459 else_branch,
12460 } => {
12461 if let Some(op) = operand {
12462 substitute_outer_in_expr(op, outer_row, outer_schema);
12463 }
12464 for (cond, val) in branches {
12465 substitute_outer_in_expr(cond, outer_row, outer_schema);
12466 substitute_outer_in_expr(val, outer_row, outer_schema);
12467 }
12468 if let Some(e) = else_branch {
12469 substitute_outer_in_expr(e, outer_row, outer_schema);
12470 }
12471 }
12472 _ => {}
12473 }
12474}
12475
12476fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12477 let outer_alias = ctx.table_alias.unwrap_or("");
12484 substitute_in_select(stmt, row, ctx, outer_alias);
12485}
12486
12487fn substitute_in_select(
12488 stmt: &mut SelectStatement,
12489 row: &Row,
12490 ctx: &EvalContext<'_>,
12491 outer_alias: &str,
12492) {
12493 for item in &mut stmt.items {
12494 if let SelectItem::Expr { expr, .. } = item {
12495 substitute_in_expr(expr, row, ctx, outer_alias);
12496 }
12497 }
12498 if let Some(w) = &mut stmt.where_ {
12499 substitute_in_expr(w, row, ctx, outer_alias);
12500 }
12501 if let Some(gs) = &mut stmt.group_by {
12502 for g in gs {
12503 substitute_in_expr(g, row, ctx, outer_alias);
12504 }
12505 }
12506 if let Some(h) = &mut stmt.having {
12507 substitute_in_expr(h, row, ctx, outer_alias);
12508 }
12509 for o in &mut stmt.order_by {
12510 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12511 }
12512 for (_, peer) in &mut stmt.unions {
12513 substitute_in_select(peer, row, ctx, outer_alias);
12514 }
12515}
12516
12517fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12518 if let Expr::Column(c) = e
12519 && let Some(qual) = &c.qualifier
12520 {
12521 let idx = if !outer_alias.is_empty() && qual.eq_ignore_ascii_case(outer_alias) {
12525 ctx.columns
12526 .iter()
12527 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12528 } else {
12529 None
12530 }
12531 .or_else(|| {
12532 let composite = alloc::format!("{qual}.{name}", name = c.name);
12533 ctx.columns
12534 .iter()
12535 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12536 });
12537 if let Some(idx) = idx {
12538 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12539 if let Ok(lit) = value_to_literal_expr(v) {
12540 *e = lit;
12541 return;
12542 }
12543 }
12544 }
12545 match e {
12546 Expr::AggregateOrdered { call, order_by } => {
12547 substitute_in_expr(call, row, ctx, outer_alias);
12548 for o in order_by.iter_mut() {
12549 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12550 }
12551 }
12552 Expr::Binary { lhs, rhs, .. } => {
12553 substitute_in_expr(lhs, row, ctx, outer_alias);
12554 substitute_in_expr(rhs, row, ctx, outer_alias);
12555 }
12556 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12557 substitute_in_expr(expr, row, ctx, outer_alias);
12558 }
12559 Expr::Like { expr, pattern, .. } => {
12560 substitute_in_expr(expr, row, ctx, outer_alias);
12561 substitute_in_expr(pattern, row, ctx, outer_alias);
12562 }
12563 Expr::FunctionCall { args, .. } => {
12564 for a in args {
12565 substitute_in_expr(a, row, ctx, outer_alias);
12566 }
12567 }
12568 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12569 Expr::WindowFunction {
12570 args,
12571 partition_by,
12572 order_by,
12573 ..
12574 } => {
12575 for a in args {
12576 substitute_in_expr(a, row, ctx, outer_alias);
12577 }
12578 for p in partition_by {
12579 substitute_in_expr(p, row, ctx, outer_alias);
12580 }
12581 for (o, _) in order_by {
12582 substitute_in_expr(o, row, ctx, outer_alias);
12583 }
12584 }
12585 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12586 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12587 substitute_in_select(subquery, row, ctx, outer_alias);
12588 }
12589 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12590 Expr::Array(items) => {
12591 for elem in items {
12592 substitute_in_expr(elem, row, ctx, outer_alias);
12593 }
12594 }
12595 Expr::ArraySubscript { target, index } => {
12596 substitute_in_expr(target, row, ctx, outer_alias);
12597 substitute_in_expr(index, row, ctx, outer_alias);
12598 }
12599 Expr::AnyAll { expr, array, .. } => {
12600 substitute_in_expr(expr, row, ctx, outer_alias);
12601 substitute_in_expr(array, row, ctx, outer_alias);
12602 }
12603 Expr::Case {
12604 operand,
12605 branches,
12606 else_branch,
12607 } => {
12608 if let Some(o) = operand {
12609 substitute_in_expr(o, row, ctx, outer_alias);
12610 }
12611 for (w, t) in branches {
12612 substitute_in_expr(w, row, ctx, outer_alias);
12613 substitute_in_expr(t, row, ctx, outer_alias);
12614 }
12615 if let Some(e) = else_branch {
12616 substitute_in_expr(e, row, ctx, outer_alias);
12617 }
12618 }
12619 }
12620}
12621
12622fn encode_row_key(row: &Row) -> Vec<u8> {
12626 let mut out = Vec::new();
12627 for v in &row.values {
12628 let s = alloc::format!("{v:?}|");
12629 out.extend_from_slice(s.as_bytes());
12630 }
12631 out
12632}
12633
12634fn select_has_window(stmt: &SelectStatement) -> bool {
12635 for item in &stmt.items {
12636 if let SelectItem::Expr { expr, .. } = item
12637 && expr_has_window(expr)
12638 {
12639 return true;
12640 }
12641 }
12642 false
12643}
12644
12645fn expr_has_window(e: &Expr) -> bool {
12646 match e {
12647 Expr::WindowFunction { .. } => true,
12648 Expr::AggregateOrdered { call, order_by } => {
12649 expr_has_window(call) || order_by.iter().any(|o| expr_has_window(&o.expr))
12650 }
12651 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12652 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12653 expr_has_window(expr)
12654 }
12655 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12656 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12657 Expr::Extract { source, .. } => expr_has_window(source),
12658 Expr::ScalarSubquery(_)
12659 | Expr::Exists { .. }
12660 | Expr::InSubquery { .. }
12661 | Expr::Literal(_)
12662 | Expr::Placeholder(_)
12663 | Expr::Column(_) => false,
12664 Expr::Array(items) => items.iter().any(expr_has_window),
12665 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12666 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12667 Expr::Case {
12668 operand,
12669 branches,
12670 else_branch,
12671 } => {
12672 operand.as_deref().is_some_and(expr_has_window)
12673 || branches
12674 .iter()
12675 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12676 || else_branch.as_deref().is_some_and(expr_has_window)
12677 }
12678 }
12679}
12680
12681fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12682 if let Expr::WindowFunction { .. } = e {
12683 if !out.iter().any(|x| x == e) {
12688 out.push(e.clone());
12689 }
12690 return;
12691 }
12692 match e {
12693 Expr::WindowFunction { .. } => unreachable!(),
12695 Expr::Binary { lhs, rhs, .. } => {
12696 collect_window_nodes(lhs, out);
12697 collect_window_nodes(rhs, out);
12698 }
12699 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12700 collect_window_nodes(expr, out);
12701 }
12702 Expr::FunctionCall { args, .. } => {
12703 for a in args {
12704 collect_window_nodes(a, out);
12705 }
12706 }
12707 Expr::Like { expr, pattern, .. } => {
12708 collect_window_nodes(expr, out);
12709 collect_window_nodes(pattern, out);
12710 }
12711 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12712 _ => {}
12713 }
12714}
12715
12716fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12717 if let Expr::WindowFunction { .. } = e
12718 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12719 {
12720 *e = Expr::Column(spg_sql::ast::ColumnName {
12721 qualifier: None,
12722 name: alloc::format!("__win_{idx}"),
12723 });
12724 return;
12725 }
12726 match e {
12727 Expr::Binary { lhs, rhs, .. } => {
12728 rewrite_window_to_columns(lhs, window_nodes);
12729 rewrite_window_to_columns(rhs, window_nodes);
12730 }
12731 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12732 rewrite_window_to_columns(expr, window_nodes);
12733 }
12734 Expr::FunctionCall { args, .. } => {
12735 for a in args {
12736 rewrite_window_to_columns(a, window_nodes);
12737 }
12738 }
12739 Expr::Like { expr, pattern, .. } => {
12740 rewrite_window_to_columns(expr, window_nodes);
12741 rewrite_window_to_columns(pattern, window_nodes);
12742 }
12743 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12744 _ => {}
12745 }
12746}
12747
12748fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12752 for (x, y) in a.iter().zip(b.iter()) {
12753 let c = value_cmp(x, y);
12754 if c != core::cmp::Ordering::Equal {
12755 return c;
12756 }
12757 }
12758 a.len().cmp(&b.len())
12759}
12760
12761fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12762 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12763 let c = value_cmp(va, vb);
12764 let c = if *desc { c.reverse() } else { c };
12765 if c != core::cmp::Ordering::Equal {
12766 return c;
12767 }
12768 }
12769 a.len().cmp(&b.len())
12770}
12771
12772const fn value_is_integer(v: &Value) -> bool {
12778 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12779}
12780
12781const fn value_to_i64(v: &Value) -> i64 {
12785 match v {
12786 Value::SmallInt(n) => *n as i64,
12787 Value::Int(n) => *n as i64,
12788 Value::BigInt(n) => *n,
12789 _ => panic!("value_to_i64 called on non-integer Value"),
12790 }
12791}
12792
12793fn generate_series_integers(
12799 start: i64,
12800 stop: i64,
12801 step: i64,
12802 cancel: &CancelToken<'_>,
12803) -> Result<alloc::vec::Vec<Row>, EngineError> {
12804 if step == 0 {
12805 return Err(EngineError::Unsupported(
12806 "generate_series(): step argument cannot be zero".into(),
12807 ));
12808 }
12809 let mut out = alloc::vec::Vec::new();
12810 let mut cur = start;
12811 const MAX_ROWS: usize = 10_000_000;
12815 loop {
12816 cancel.check()?;
12817 if step > 0 && cur > stop {
12818 break;
12819 }
12820 if step < 0 && cur < stop {
12821 break;
12822 }
12823 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12824 if out.len() > MAX_ROWS {
12825 return Err(EngineError::Unsupported(alloc::format!(
12826 "generate_series(): exceeded {MAX_ROWS} rows; \
12827 narrow start/stop or use a larger step"
12828 )));
12829 }
12830 cur = match cur.checked_add(step) {
12831 Some(n) => n,
12832 None => break,
12833 };
12834 }
12835 Ok(out)
12836}
12837
12838fn generate_series_timestamps(
12843 start: i64,
12844 stop: i64,
12845 step: Value,
12846 cancel: &CancelToken<'_>,
12847) -> Result<alloc::vec::Vec<Row>, EngineError> {
12848 let (months, micros) = match &step {
12849 Value::Interval { months, micros } => (*months, *micros),
12850 _ => unreachable!("caller guards step.is_interval"),
12851 };
12852 if months == 0 && micros == 0 {
12853 return Err(EngineError::Unsupported(
12854 "generate_series(): INTERVAL step cannot be zero".into(),
12855 ));
12856 }
12857 let ascending = months > 0 || micros > 0;
12858 let mut out = alloc::vec::Vec::new();
12859 let mut cur = Value::Timestamp(start);
12860 const MAX_ROWS: usize = 10_000_000;
12861 loop {
12862 cancel.check()?;
12863 let cur_t = match cur {
12864 Value::Timestamp(t) => t,
12865 _ => unreachable!("loop invariant: cur is Timestamp"),
12866 };
12867 if ascending && cur_t > stop {
12868 break;
12869 }
12870 if !ascending && cur_t < stop {
12871 break;
12872 }
12873 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12874 if out.len() > MAX_ROWS {
12875 return Err(EngineError::Unsupported(alloc::format!(
12876 "generate_series(): exceeded {MAX_ROWS} rows; \
12877 narrow start/stop or use a larger step"
12878 )));
12879 }
12880 let next = eval::apply_binary_interval(
12881 spg_sql::ast::BinOp::Add,
12882 &cur,
12883 &Value::Interval { months, micros },
12884 )
12885 .map_err(EngineError::Eval)?;
12886 cur = match next {
12887 Some(v) => v,
12888 None => break,
12889 };
12890 }
12891 Ok(out)
12892}
12893
12894#[allow(clippy::match_same_arms)] pub(crate) fn order_by_value_cmp(
12900 desc: bool,
12901 nulls_first: Option<bool>,
12902 a: &Value,
12903 b: &Value,
12904) -> core::cmp::Ordering {
12905 use core::cmp::Ordering;
12906 let nf = nulls_first.unwrap_or(desc);
12907 match (matches!(a, Value::Null), matches!(b, Value::Null)) {
12908 (true, true) => Ordering::Equal,
12909 (true, false) => {
12910 if nf {
12911 Ordering::Less
12912 } else {
12913 Ordering::Greater
12914 }
12915 }
12916 (false, true) => {
12917 if nf {
12918 Ordering::Greater
12919 } else {
12920 Ordering::Less
12921 }
12922 }
12923 (false, false) => {
12924 let c = value_cmp(a, b);
12925 if desc { c.reverse() } else { c }
12926 }
12927 }
12928}
12929
12930fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12931 use core::cmp::Ordering;
12932 match (a, b) {
12933 (Value::Null, Value::Null) => Ordering::Equal,
12934 (Value::Null, _) => Ordering::Less,
12935 (_, Value::Null) => Ordering::Greater,
12936 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12937 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12938 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12939 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12940 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12941 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12942 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12943 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12944 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12947 }
12948}
12949
12950#[allow(
12956 clippy::too_many_arguments,
12957 clippy::cast_possible_truncation,
12958 clippy::cast_possible_wrap,
12959 clippy::cast_precision_loss,
12960 clippy::cast_sign_loss,
12961 clippy::doc_markdown,
12962 clippy::too_many_lines,
12963 clippy::type_complexity,
12964 clippy::match_same_arms
12965)]
12966fn compute_window_partition(
12967 name: &str,
12968 args: &[Expr],
12969 ordered: bool,
12970 frame: Option<&WindowFrame>,
12971 null_treatment: spg_sql::ast::NullTreatment,
12972 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12973 filtered_rows: &[&Row],
12974 ctx: &EvalContext<'_>,
12975 out_vals: &mut [Value],
12976) -> Result<(), EngineError> {
12977 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12978 let lower = name.to_ascii_lowercase();
12979 match lower.as_str() {
12980 "row_number" => {
12981 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12982 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12983 }
12984 Ok(())
12985 }
12986 "rank" => {
12987 let mut prev_key: Option<&[(Value, bool)]> = None;
12988 let mut current_rank: i64 = 1;
12989 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12990 if let Some(p) = prev_key
12991 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12992 {
12993 current_rank = (i + 1) as i64;
12994 }
12995 if prev_key.is_none() {
12996 current_rank = 1;
12997 }
12998 out_vals[*idx] = Value::BigInt(current_rank);
12999 prev_key = Some(okey.as_slice());
13000 }
13001 Ok(())
13002 }
13003 "dense_rank" => {
13004 let mut prev_key: Option<&[(Value, bool)]> = None;
13005 let mut current_rank: i64 = 0;
13006 for (_, okey, idx) in slice {
13007 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
13008 current_rank += 1;
13009 }
13010 out_vals[*idx] = Value::BigInt(current_rank);
13011 prev_key = Some(okey.as_slice());
13012 }
13013 Ok(())
13014 }
13015 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
13016 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
13019 slice.iter().map(|_| Value::Null).collect()
13020 } else {
13021 slice
13022 .iter()
13023 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13024 .collect::<Result<_, _>>()
13025 .map_err(EngineError::Eval)?
13026 };
13027 let eff = effective_frame(frame, ordered)?;
13031 #[allow(clippy::needless_range_loop)]
13032 for i in 0..slice.len() {
13033 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13034 let mut sum: f64 = 0.0;
13035 let mut count: i64 = 0;
13036 let mut min_v: Option<f64> = None;
13037 let mut max_v: Option<f64> = None;
13038 let mut row_count: i64 = 0;
13039 if lo <= hi {
13040 for j in lo..=hi {
13041 let v = &arg_values[j];
13042 match lower.as_str() {
13043 "count_star" => row_count += 1,
13044 "count" => {
13045 if !v.is_null() {
13046 count += 1;
13047 }
13048 }
13049 _ => {
13050 if let Some(x) = value_to_f64(v) {
13051 sum += x;
13052 count += 1;
13053 min_v = Some(min_v.map_or(x, |m| m.min(x)));
13054 max_v = Some(max_v.map_or(x, |m| m.max(x)));
13055 }
13056 }
13057 }
13058 }
13059 }
13060 let value = match lower.as_str() {
13061 "count_star" => Value::BigInt(row_count),
13062 "count" => Value::BigInt(count),
13063 "sum" => Value::Float(sum),
13064 "avg" => {
13065 if count == 0 {
13066 Value::Null
13067 } else {
13068 Value::Float(sum / count as f64)
13069 }
13070 }
13071 "min" => min_v.map_or(Value::Null, Value::Float),
13072 "max" => max_v.map_or(Value::Null, Value::Float),
13073 _ => unreachable!(),
13074 };
13075 let (_, _, idx) = &slice[i];
13076 out_vals[*idx] = value;
13077 }
13078 Ok(())
13079 }
13080 "lag" | "lead" => {
13081 if args.is_empty() {
13084 return Err(EngineError::Unsupported(alloc::format!(
13085 "{lower}() requires at least one argument"
13086 )));
13087 }
13088 let offset: i64 = if args.len() >= 2 {
13089 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13090 .map_err(EngineError::Eval)?;
13091 match v {
13092 Value::SmallInt(n) => i64::from(n),
13093 Value::Int(n) => i64::from(n),
13094 Value::BigInt(n) => n,
13095 _ => {
13096 return Err(EngineError::Unsupported(alloc::format!(
13097 "{lower}() offset must be integer"
13098 )));
13099 }
13100 }
13101 } else {
13102 1
13103 };
13104 let default: Value = if args.len() >= 3 {
13105 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
13106 .map_err(EngineError::Eval)?
13107 } else {
13108 Value::Null
13109 };
13110 let values: Vec<Value> = slice
13111 .iter()
13112 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13113 .collect::<Result<_, _>>()
13114 .map_err(EngineError::Eval)?;
13115 let n = slice.len();
13116 for (i, (_, _, idx)) in slice.iter().enumerate() {
13117 let signed_offset = if lower == "lag" { -offset } else { offset };
13118 let v = if ignore_nulls {
13119 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
13123 let needed: i64 = signed_offset.abs();
13124 if needed == 0 {
13125 values[i].clone()
13126 } else {
13127 let mut j: i64 = i as i64;
13128 let mut hits: i64 = 0;
13129 let mut found: Option<Value> = None;
13130 loop {
13131 j += step;
13132 if j < 0 || j >= n as i64 {
13133 break;
13134 }
13135 #[allow(clippy::cast_sign_loss)]
13136 let v = &values[j as usize];
13137 if !v.is_null() {
13138 hits += 1;
13139 if hits == needed {
13140 found = Some(v.clone());
13141 break;
13142 }
13143 }
13144 }
13145 found.unwrap_or_else(|| default.clone())
13146 }
13147 } else {
13148 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
13149 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
13150 default.clone()
13151 } else {
13152 #[allow(clippy::cast_sign_loss)]
13153 {
13154 values[target_signed as usize].clone()
13155 }
13156 }
13157 };
13158 out_vals[*idx] = v;
13159 }
13160 Ok(())
13161 }
13162 "first_value" | "last_value" | "nth_value" => {
13163 if args.is_empty() {
13164 return Err(EngineError::Unsupported(alloc::format!(
13165 "{lower}() requires at least one argument"
13166 )));
13167 }
13168 let values: Vec<Value> = slice
13169 .iter()
13170 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
13171 .collect::<Result<_, _>>()
13172 .map_err(EngineError::Eval)?;
13173 let nth: usize = if lower == "nth_value" {
13174 if args.len() < 2 {
13175 return Err(EngineError::Unsupported(
13176 "nth_value() requires (expr, n)".into(),
13177 ));
13178 }
13179 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
13180 .map_err(EngineError::Eval)?;
13181 let raw = match v {
13182 Value::SmallInt(n) => i64::from(n),
13183 Value::Int(n) => i64::from(n),
13184 Value::BigInt(n) => n,
13185 _ => {
13186 return Err(EngineError::Unsupported(
13187 "nth_value() n must be integer".into(),
13188 ));
13189 }
13190 };
13191 if raw < 1 {
13192 return Err(EngineError::Unsupported(
13193 "nth_value() n must be >= 1".into(),
13194 ));
13195 }
13196 #[allow(clippy::cast_sign_loss)]
13197 {
13198 raw as usize
13199 }
13200 } else {
13201 0
13202 };
13203 let eff = effective_frame(frame, ordered)?;
13204 for i in 0..slice.len() {
13205 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13206 let (_, _, idx) = &slice[i];
13207 let v = if lo > hi {
13208 Value::Null
13209 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
13210 if lower == "first_value" {
13213 (lo..=hi)
13214 .find_map(|j| {
13215 let v = &values[j];
13216 (!v.is_null()).then(|| v.clone())
13217 })
13218 .unwrap_or(Value::Null)
13219 } else {
13220 (lo..=hi)
13221 .rev()
13222 .find_map(|j| {
13223 let v = &values[j];
13224 (!v.is_null()).then(|| v.clone())
13225 })
13226 .unwrap_or(Value::Null)
13227 }
13228 } else {
13229 match lower.as_str() {
13230 "first_value" => values[lo].clone(),
13231 "last_value" => values[hi].clone(),
13232 "nth_value" => {
13233 let pos = lo + nth - 1;
13234 if pos > hi {
13235 Value::Null
13236 } else {
13237 values[pos].clone()
13238 }
13239 }
13240 _ => unreachable!(),
13241 }
13242 };
13243 out_vals[*idx] = v;
13244 }
13245 Ok(())
13246 }
13247 "ntile" => {
13248 if args.is_empty() {
13249 return Err(EngineError::Unsupported(
13250 "ntile(n) requires an integer argument".into(),
13251 ));
13252 }
13253 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
13254 .map_err(EngineError::Eval)?;
13255 let bucket_count: i64 = match v {
13256 Value::SmallInt(n) => i64::from(n),
13257 Value::Int(n) => i64::from(n),
13258 Value::BigInt(n) => n,
13259 _ => {
13260 return Err(EngineError::Unsupported(
13261 "ntile() argument must be integer".into(),
13262 ));
13263 }
13264 };
13265 if bucket_count < 1 {
13266 return Err(EngineError::Unsupported(
13267 "ntile() argument must be >= 1".into(),
13268 ));
13269 }
13270 #[allow(clippy::cast_sign_loss)]
13271 let buckets = bucket_count as usize;
13272 let n = slice.len();
13273 let base = n / buckets;
13276 let extras = n % buckets;
13277 let mut bucket: usize = 1;
13278 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
13279 let mut buckets_with_extra_remaining = extras;
13280 for (_, _, idx) in slice {
13281 if remaining_in_bucket == 0 {
13282 bucket += 1;
13283 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
13284 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
13285 base + 1
13286 } else {
13287 base
13288 };
13289 if remaining_in_bucket == 0 {
13292 remaining_in_bucket = 1;
13293 }
13294 }
13295 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13296 remaining_in_bucket -= 1;
13297 }
13298 Ok(())
13299 }
13300 "percent_rank" => {
13301 let n = slice.len();
13304 let mut prev_key: Option<&[(Value, bool)]> = None;
13305 let mut current_rank: i64 = 1;
13306 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13307 if let Some(p) = prev_key
13308 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13309 {
13310 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13311 }
13312 if prev_key.is_none() {
13313 current_rank = 1;
13314 }
13315 #[allow(clippy::cast_precision_loss)]
13316 let pr = if n <= 1 {
13317 0.0
13318 } else {
13319 (current_rank - 1) as f64 / (n - 1) as f64
13320 };
13321 out_vals[*idx] = Value::Float(pr);
13322 prev_key = Some(okey.as_slice());
13323 }
13324 Ok(())
13325 }
13326 "cume_dist" => {
13327 let n = slice.len();
13329 for i in 0..slice.len() {
13331 let peer_end = peer_group_end(slice, i);
13332 #[allow(clippy::cast_precision_loss)]
13333 let cd = (peer_end + 1) as f64 / n as f64;
13334 let (_, _, idx) = &slice[i];
13335 out_vals[*idx] = Value::Float(cd);
13336 }
13337 Ok(())
13338 }
13339 other => Err(EngineError::Unsupported(alloc::format!(
13340 "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)"
13341 ))),
13342 }
13343}
13344
13345fn effective_frame(
13352 frame: Option<&WindowFrame>,
13353 ordered: bool,
13354) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13355 match frame {
13356 None => {
13357 if ordered {
13358 Ok((
13359 FrameKind::Range,
13360 FrameBound::UnboundedPreceding,
13361 FrameBound::CurrentRow,
13362 ))
13363 } else {
13364 Ok((
13365 FrameKind::Rows,
13366 FrameBound::UnboundedPreceding,
13367 FrameBound::UnboundedFollowing,
13368 ))
13369 }
13370 }
13371 Some(fr) => {
13372 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13373 if matches!(fr.start, FrameBound::UnboundedFollowing)
13375 || matches!(end, FrameBound::UnboundedPreceding)
13376 {
13377 return Err(EngineError::Unsupported(alloc::format!(
13378 "invalid frame: start={:?} end={:?}",
13379 fr.start,
13380 end
13381 )));
13382 }
13383 if fr.kind == FrameKind::Range
13388 && (matches!(
13389 fr.start,
13390 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13391 ) || matches!(
13392 end,
13393 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13394 ))
13395 {
13396 return Err(EngineError::Unsupported(
13397 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13398 ));
13399 }
13400 Ok((fr.kind, fr.start.clone(), end))
13401 }
13402 }
13403}
13404
13405#[allow(clippy::type_complexity)]
13409fn frame_bounds_for_row(
13410 eff: &(FrameKind, FrameBound, FrameBound),
13411 i: usize,
13412 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
13413) -> (usize, usize) {
13414 let (kind, start, end) = eff;
13415 let n = slice.len();
13416 let last = n.saturating_sub(1);
13417 let (mut lo, mut hi) = match kind {
13418 FrameKind::Rows => {
13419 let lo = match start {
13420 FrameBound::UnboundedPreceding => 0,
13421 FrameBound::OffsetPreceding(k) => {
13422 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13423 i.saturating_sub(k)
13424 }
13425 FrameBound::CurrentRow => i,
13426 FrameBound::OffsetFollowing(k) => {
13427 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13428 i.saturating_add(k).min(last)
13429 }
13430 FrameBound::UnboundedFollowing => last,
13431 };
13432 let hi = match end {
13433 FrameBound::UnboundedPreceding => 0,
13434 FrameBound::OffsetPreceding(k) => {
13435 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13436 i.saturating_sub(k)
13437 }
13438 FrameBound::CurrentRow => i,
13439 FrameBound::OffsetFollowing(k) => {
13440 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13441 i.saturating_add(k).min(last)
13442 }
13443 FrameBound::UnboundedFollowing => last,
13444 };
13445 (lo, hi)
13446 }
13447 FrameKind::Range => {
13448 let lo = match start {
13454 FrameBound::UnboundedPreceding => 0,
13455 FrameBound::CurrentRow => peer_group_start(slice, i),
13456 FrameBound::UnboundedFollowing => last,
13457 _ => unreachable!("offset bounds rejected for RANGE"),
13458 };
13459 let hi = match end {
13460 FrameBound::UnboundedPreceding => 0,
13461 FrameBound::CurrentRow => peer_group_end(slice, i),
13462 FrameBound::UnboundedFollowing => last,
13463 _ => unreachable!("offset bounds rejected for RANGE"),
13464 };
13465 (lo, hi)
13466 }
13467 };
13468 if hi >= n {
13469 hi = last;
13470 }
13471 if lo >= n {
13472 lo = last;
13473 }
13474 (lo, hi)
13475}
13476
13477#[allow(clippy::type_complexity)]
13481fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13482 let key = &slice[i].1;
13483 let mut j = i;
13484 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13485 j -= 1;
13486 }
13487 j
13488}
13489
13490#[allow(clippy::type_complexity)]
13493fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13494 let key = &slice[i].1;
13495 let mut j = i;
13496 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13497 j += 1;
13498 }
13499 j
13500}
13501
13502fn value_to_f64(v: &Value) -> Option<f64> {
13503 match v {
13504 Value::SmallInt(n) => Some(f64::from(*n)),
13505 Value::Int(n) => Some(f64::from(*n)),
13506 #[allow(clippy::cast_precision_loss)]
13507 Value::BigInt(n) => Some(*n as f64),
13508 Value::Float(x) => Some(*x),
13509 _ => None,
13510 }
13511}
13512
13513fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13517 let mut any = false;
13518 for item in &stmt.items {
13519 if let SelectItem::Expr { expr, .. } = item {
13520 any = any || expr_has_subquery(expr);
13521 }
13522 }
13523 if let Some(w) = &stmt.where_ {
13524 any = any || expr_has_subquery(w);
13525 }
13526 if let Some(h) = &stmt.having {
13527 any = any || expr_has_subquery(h);
13528 }
13529 for o in &stmt.order_by {
13530 any = any || expr_has_subquery(&o.expr);
13531 }
13532 for (_, peer) in &stmt.unions {
13533 any = any || expr_tree_has_subquery(peer);
13534 }
13535 any
13536}
13537
13538fn expr_has_subquery(e: &Expr) -> bool {
13539 match e {
13540 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13541 Expr::AggregateOrdered { call, order_by } => {
13542 expr_has_subquery(call) || order_by.iter().any(|o| expr_has_subquery(&o.expr))
13543 }
13544 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13545 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13546 expr_has_subquery(expr)
13547 }
13548 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13549 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13550 Expr::Extract { source, .. } => expr_has_subquery(source),
13551 Expr::WindowFunction {
13552 args,
13553 partition_by,
13554 order_by,
13555 ..
13556 } => {
13557 args.iter().any(expr_has_subquery)
13558 || partition_by.iter().any(expr_has_subquery)
13559 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13560 }
13561 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13562 Expr::Array(items) => items.iter().any(expr_has_subquery),
13563 Expr::ArraySubscript { target, index } => {
13564 expr_has_subquery(target) || expr_has_subquery(index)
13565 }
13566 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13567 Expr::Case {
13568 operand,
13569 branches,
13570 else_branch,
13571 } => {
13572 operand.as_deref().is_some_and(expr_has_subquery)
13573 || branches
13574 .iter()
13575 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13576 || else_branch.as_deref().is_some_and(expr_has_subquery)
13577 }
13578 }
13579}
13580
13581fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13588 let lit = match v {
13589 Value::Null => Literal::Null,
13590 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13591 Value::Int(n) => Literal::Integer(i64::from(n)),
13592 Value::BigInt(n) => Literal::Integer(n),
13593 Value::Float(x) => Literal::Float(x),
13594 Value::Text(s) | Value::Json(s) => Literal::String(s),
13595 Value::Bool(b) => Literal::Bool(b),
13596 other => {
13597 return Err(EngineError::Unsupported(alloc::format!(
13598 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13599 other.data_type()
13600 )));
13601 }
13602 };
13603 Ok(Expr::Literal(lit))
13604}
13605
13606fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13612 let lit = match v {
13613 Value::Null => Literal::Null,
13614 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13615 Value::Int(n) => Literal::Integer(i64::from(n)),
13616 Value::BigInt(n) => Literal::Integer(n),
13617 Value::Float(x) => Literal::Float(x),
13618 Value::Text(s) | Value::Json(s) => Literal::String(s),
13619 Value::Bool(b) => Literal::Bool(b),
13620 Value::Vector(xs) => Literal::Vector(xs),
13621 Value::Date(days) => {
13625 let micros = (i64::from(days)) * 86_400_000_000;
13626 Literal::String(format_timestamp_micros_as_date(micros))
13627 }
13628 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13629 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13630 other => {
13631 return Err(EngineError::Unsupported(alloc::format!(
13632 "INSERT … SELECT cannot materialise value of type {:?}; \
13633 add an explicit CAST in the inner SELECT",
13634 other.data_type()
13635 )));
13636 }
13637 };
13638 Ok(Expr::Literal(lit))
13639}
13640
13641fn format_timestamp_micros(us: i64) -> String {
13642 let days = us.div_euclid(86_400_000_000);
13644 let intra_day = us.rem_euclid(86_400_000_000);
13645 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13646 let secs = intra_day / 1_000_000;
13647 let us_rem = intra_day % 1_000_000;
13648 let h = (secs / 3600) % 24;
13649 let m = (secs / 60) % 60;
13650 let s = secs % 60;
13651 if us_rem == 0 {
13652 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13653 } else {
13654 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13655 }
13656}
13657
13658fn format_timestamp_micros_as_date(us: i64) -> String {
13659 let days = us.div_euclid(86_400_000_000);
13662 let jdn = days + 2_440_588;
13664 let (y, mo, d) = jdn_to_ymd(jdn);
13665 alloc::format!("{y:04}-{mo:02}-{d:02}")
13666}
13667
13668fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13669 let l = jdn + 68569;
13671 let n = (4 * l) / 146_097;
13672 let l = l - (146_097 * n + 3) / 4;
13673 let i = (4000 * (l + 1)) / 1_461_001;
13674 let l = l - (1461 * i) / 4 + 31;
13675 let j = (80 * l) / 2447;
13676 let day = (l - (2447 * j) / 80) as u32;
13677 let l = j / 11;
13678 let month = (j + 2 - 12 * l) as u32;
13679 let year = 100 * (n - 49) + i + l;
13680 (year, month, day)
13681}
13682
13683fn format_numeric(scaled: i128, scale: u8) -> String {
13684 if scale == 0 {
13685 return alloc::format!("{scaled}");
13686 }
13687 let abs = scaled.unsigned_abs();
13688 let divisor = 10u128.pow(u32::from(scale));
13689 let whole = abs / divisor;
13690 let frac = abs % divisor;
13691 let sign = if scaled < 0 { "-" } else { "" };
13692 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13693}
13694
13695fn rewrite_column_in_source(
13719 src: &str,
13720 old: &str,
13721 new: &str,
13722) -> Result<alloc::string::String, EngineError> {
13723 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13724 EngineError::Unsupported(alloc::format!(
13725 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13726 failed to parse for rewrite ({e})"
13727 ))
13728 })?;
13729 rewrite_column_in_expr(&mut expr, old, new);
13730 Ok(alloc::format!("{expr}"))
13731}
13732
13733fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13741 match e {
13742 Expr::AggregateOrdered { call, order_by } => {
13743 rewrite_column_in_expr(call, old, new);
13744 for o in order_by.iter_mut() {
13745 rewrite_column_in_expr(&mut o.expr, old, new);
13746 }
13747 }
13748 Expr::Column(c) => {
13749 if c.name.eq_ignore_ascii_case(old) {
13750 c.name = new.to_string();
13751 }
13752 }
13753 Expr::Binary { lhs, rhs, .. } => {
13754 rewrite_column_in_expr(lhs, old, new);
13755 rewrite_column_in_expr(rhs, old, new);
13756 }
13757 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13758 rewrite_column_in_expr(expr, old, new);
13759 }
13760 Expr::FunctionCall { args, .. } => {
13761 for a in args {
13762 rewrite_column_in_expr(a, old, new);
13763 }
13764 }
13765 Expr::Like { expr, pattern, .. } => {
13766 rewrite_column_in_expr(expr, old, new);
13767 rewrite_column_in_expr(pattern, old, new);
13768 }
13769 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13770 Expr::WindowFunction {
13771 args,
13772 partition_by,
13773 order_by,
13774 ..
13775 } => {
13776 for a in args {
13777 rewrite_column_in_expr(a, old, new);
13778 }
13779 for p in partition_by {
13780 rewrite_column_in_expr(p, old, new);
13781 }
13782 for (o, _) in order_by {
13783 rewrite_column_in_expr(o, old, new);
13784 }
13785 }
13786 Expr::Array(items) => {
13787 for elem in items {
13788 rewrite_column_in_expr(elem, old, new);
13789 }
13790 }
13791 Expr::ArraySubscript { target, index } => {
13792 rewrite_column_in_expr(target, old, new);
13793 rewrite_column_in_expr(index, old, new);
13794 }
13795 Expr::AnyAll { expr, array, .. } => {
13796 rewrite_column_in_expr(expr, old, new);
13797 rewrite_column_in_expr(array, old, new);
13798 }
13799 Expr::Case {
13800 operand,
13801 branches,
13802 else_branch,
13803 } => {
13804 if let Some(o) = operand {
13805 rewrite_column_in_expr(o, old, new);
13806 }
13807 for (w, t) in branches {
13808 rewrite_column_in_expr(w, old, new);
13809 rewrite_column_in_expr(t, old, new);
13810 }
13811 if let Some(e) = else_branch {
13812 rewrite_column_in_expr(e, old, new);
13813 }
13814 }
13815 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13819 Expr::Literal(_) | Expr::Placeholder(_) => {}
13820 }
13821}
13822
13823pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13831 match stmt {
13832 Statement::Select(s) => substitute_select(s, params)?,
13833 Statement::Insert(ins) => {
13834 for row in &mut ins.rows {
13835 for e in row {
13836 substitute_expr(e, params)?;
13837 }
13838 }
13839 if let Some(clause) = &mut ins.on_conflict
13843 && let spg_sql::ast::OnConflictAction::Update {
13844 assignments,
13845 where_,
13846 } = &mut clause.action
13847 {
13848 for (_, e) in assignments.iter_mut() {
13849 substitute_expr(e, params)?;
13850 }
13851 if let Some(w) = where_ {
13852 substitute_expr(w, params)?;
13853 }
13854 }
13855 }
13856 Statement::Update(u) => {
13857 for (_, e) in &mut u.assignments {
13858 substitute_expr(e, params)?;
13859 }
13860 if let Some(w) = &mut u.where_ {
13861 substitute_expr(w, params)?;
13862 }
13863 }
13864 Statement::Delete(d) => {
13865 if let Some(w) = &mut d.where_ {
13866 substitute_expr(w, params)?;
13867 }
13868 }
13869 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13870 _ => {}
13873 }
13874 Ok(())
13875}
13876
13877fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13878 for item in &mut s.items {
13879 if let SelectItem::Expr { expr, .. } = item {
13880 substitute_expr(expr, params)?;
13881 }
13882 }
13883 if let Some(w) = &mut s.where_ {
13884 substitute_expr(w, params)?;
13885 }
13886 if let Some(gs) = &mut s.group_by {
13887 for g in gs {
13888 substitute_expr(g, params)?;
13889 }
13890 }
13891 if let Some(h) = &mut s.having {
13892 substitute_expr(h, params)?;
13893 }
13894 for o in &mut s.order_by {
13895 substitute_expr(&mut o.expr, params)?;
13896 }
13897 for (_, peer) in &mut s.unions {
13898 substitute_select(peer, params)?;
13899 }
13900 if let Some(le) = s.limit {
13905 s.limit = Some(resolve_limit_placeholder(le, params)?);
13906 }
13907 if let Some(le) = s.offset {
13908 s.offset = Some(resolve_limit_placeholder(le, params)?);
13909 }
13910 Ok(())
13911}
13912
13913fn resolve_limit_placeholder(
13914 le: spg_sql::ast::LimitExpr,
13915 params: &[Value],
13916) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13917 use spg_sql::ast::LimitExpr;
13918 match le {
13919 LimitExpr::Literal(_) => Ok(le),
13920 LimitExpr::Placeholder(n) => {
13921 let idx = usize::from(n).saturating_sub(1);
13922 let v = params.get(idx).ok_or_else(|| {
13923 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13924 n,
13925 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13926 })
13927 })?;
13928 let int = match v {
13929 Value::SmallInt(x) => Some(i64::from(*x)),
13930 Value::Int(x) => Some(i64::from(*x)),
13931 Value::BigInt(x) => Some(*x),
13932 _ => None,
13933 }
13934 .ok_or_else(|| {
13935 EngineError::Unsupported(alloc::format!(
13936 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13937 ))
13938 })?;
13939 if int < 0 {
13940 return Err(EngineError::Unsupported(alloc::format!(
13941 "LIMIT/OFFSET ${n} bound to negative value {int}"
13942 )));
13943 }
13944 let bounded = u32::try_from(int).map_err(|_| {
13945 EngineError::Unsupported(alloc::format!(
13946 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13947 ))
13948 })?;
13949 Ok(LimitExpr::Literal(bounded))
13950 }
13951 }
13952}
13953
13954fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13955 if let Expr::Placeholder(n) = e {
13956 let idx = usize::from(*n).saturating_sub(1);
13957 let v = params.get(idx).ok_or_else(|| {
13958 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13959 n: *n,
13960 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13961 })
13962 })?;
13963 *e = Expr::Literal(value_to_literal(v.clone()));
13964 return Ok(());
13965 }
13966 match e {
13967 Expr::AggregateOrdered { call, order_by } => {
13968 substitute_expr(call, params)?;
13969 for o in order_by.iter_mut() {
13970 substitute_expr(&mut o.expr, params)?;
13971 }
13972 }
13973 Expr::Binary { lhs, rhs, .. } => {
13974 substitute_expr(lhs, params)?;
13975 substitute_expr(rhs, params)?;
13976 }
13977 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13978 substitute_expr(expr, params)?;
13979 }
13980 Expr::FunctionCall { args, .. } => {
13981 for a in args {
13982 substitute_expr(a, params)?;
13983 }
13984 }
13985 Expr::Like { expr, pattern, .. } => {
13986 substitute_expr(expr, params)?;
13987 substitute_expr(pattern, params)?;
13988 }
13989 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13990 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13991 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13992 Expr::InSubquery { expr, subquery, .. } => {
13993 substitute_expr(expr, params)?;
13994 substitute_select(subquery, params)?;
13995 }
13996 Expr::WindowFunction {
13997 args,
13998 partition_by,
13999 order_by,
14000 ..
14001 } => {
14002 for a in args {
14003 substitute_expr(a, params)?;
14004 }
14005 for p in partition_by {
14006 substitute_expr(p, params)?;
14007 }
14008 for (e, _) in order_by {
14009 substitute_expr(e, params)?;
14010 }
14011 }
14012 Expr::Literal(_) | Expr::Column(_) => {}
14013 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
14015 Expr::Array(items) => {
14016 for elem in items {
14017 substitute_expr(elem, params)?;
14018 }
14019 }
14020 Expr::ArraySubscript { target, index } => {
14021 substitute_expr(target, params)?;
14022 substitute_expr(index, params)?;
14023 }
14024 Expr::AnyAll { expr, array, .. } => {
14025 substitute_expr(expr, params)?;
14026 substitute_expr(array, params)?;
14027 }
14028 Expr::Case {
14029 operand,
14030 branches,
14031 else_branch,
14032 } => {
14033 if let Some(o) = operand {
14034 substitute_expr(o, params)?;
14035 }
14036 for (w, t) in branches {
14037 substitute_expr(w, params)?;
14038 substitute_expr(t, params)?;
14039 }
14040 if let Some(e) = else_branch {
14041 substitute_expr(e, params)?;
14042 }
14043 }
14044 }
14045 Ok(())
14046}
14047
14048fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
14066 use core::cmp::Ordering;
14067 match (a, b) {
14068 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
14069 (Value::Int(a), Value::Int(b)) => a.cmp(b),
14070 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
14071 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
14072 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
14073 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14074 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
14075 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
14076 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
14077 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
14078 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
14079 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
14080 (Value::Date(a), Value::Date(b)) => a.cmp(b),
14081 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
14082 (Value::SmallInt(n), Value::Float(x)) => {
14084 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14085 }
14086 (Value::Float(x), Value::SmallInt(n)) => {
14087 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14088 }
14089 (Value::Int(n), Value::Float(x)) => {
14090 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
14091 }
14092 (Value::Float(x), Value::Int(n)) => {
14093 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
14094 }
14095 (Value::BigInt(n), Value::Float(x)) => {
14096 #[allow(clippy::cast_precision_loss)]
14097 let nf = *n as f64;
14098 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
14099 }
14100 (Value::Float(x), Value::BigInt(n)) => {
14101 #[allow(clippy::cast_precision_loss)]
14102 let nf = *n as f64;
14103 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
14104 }
14105 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
14108 }
14109}
14110
14111fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
14118 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
14119 out.push('[');
14120 for (i, b) in bounds.iter().enumerate() {
14121 if i > 0 {
14122 out.push_str(", ");
14123 }
14124 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
14125 if needs_quote {
14126 out.push('"');
14127 for ch in b.chars() {
14128 if ch == '"' || ch == '\\' {
14129 out.push('\\');
14130 }
14131 out.push(ch);
14132 }
14133 out.push('"');
14134 } else {
14135 out.push_str(b);
14136 }
14137 }
14138 out.push(']');
14139 out
14140}
14141
14142pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
14152 match v {
14153 Value::Null => "NULL".to_string(),
14154 Value::SmallInt(n) => alloc::format!("{n}"),
14155 Value::Int(n) => alloc::format!("{n}"),
14156 Value::BigInt(n) => alloc::format!("{n}"),
14157 Value::Float(x) => alloc::format!("{x:?}"),
14158 Value::Text(s) | Value::Json(s) => s.clone(),
14159 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
14160 Value::Date(d) => eval::format_date(*d),
14161 Value::Timestamp(t) => eval::format_timestamp(*t),
14162 Value::Time(us) => eval::format_time(*us),
14164 Value::Year(y) => alloc::format!("{y:04}"),
14166 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
14168 Value::Money(c) => eval::format_money(*c),
14170 v @ Value::Range { .. } => format_range_str(v),
14172 Value::Hstore(pairs) => format_hstore_str(pairs),
14174 Value::IntArray2D(rows) => format_int_2d_text(rows),
14176 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
14177 Value::TextArray2D(rows) => format_text_2d_text(rows),
14178 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
14179 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
14180 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
14181 alloc::format!("{v:?}")
14185 }
14186 _ => alloc::format!("{v:?}"),
14190 }
14191}
14192
14193const fn is_internal_table_name(_name: &str) -> bool {
14200 false
14201}
14202
14203fn value_to_literal(v: Value) -> Literal {
14204 match v {
14205 Value::Null => Literal::Null,
14206 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
14207 Value::Int(n) => Literal::Integer(i64::from(n)),
14208 Value::BigInt(n) => Literal::Integer(n),
14209 Value::Float(x) => Literal::Float(x),
14210 Value::Text(s) | Value::Json(s) => Literal::String(s),
14211 Value::Bool(b) => Literal::Bool(b),
14212 Value::Vector(v) => Literal::Vector(v),
14213 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
14214 Value::Date(d) => Literal::String(eval::format_date(d)),
14215 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14216 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14222 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14227 Value::TextArray(items) => Literal::TextArray(items),
14232 Value::IntArray(items) => Literal::IntArray(items),
14233 Value::BigIntArray(items) => Literal::BigIntArray(items),
14234 Value::Interval { months, micros } => Literal::Interval {
14235 months,
14236 micros,
14237 text: eval::format_interval(months, micros),
14238 },
14239 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14242 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14243 v => Literal::String(alloc::format!("{v:?}")),
14247 }
14248}
14249
14250fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14251 let Some(now) = now_micros else {
14252 return;
14253 };
14254 match stmt {
14255 Statement::Select(s) => rewrite_select_clock(s, now),
14256 Statement::Insert(ins) => {
14257 for row in &mut ins.rows {
14258 for e in row {
14259 rewrite_expr_clock(e, now);
14260 }
14261 }
14262 if let Some(clause) = &mut ins.on_conflict
14266 && let spg_sql::ast::OnConflictAction::Update {
14267 assignments,
14268 where_,
14269 } = &mut clause.action
14270 {
14271 for (_, e) in assignments.iter_mut() {
14272 rewrite_expr_clock(e, now);
14273 }
14274 if let Some(w) = where_ {
14275 rewrite_expr_clock(w, now);
14276 }
14277 }
14278 }
14279 Statement::Update(u) => {
14283 for (_, e) in &mut u.assignments {
14284 rewrite_expr_clock(e, now);
14285 }
14286 if let Some(w) = &mut u.where_ {
14287 rewrite_expr_clock(w, now);
14288 }
14289 }
14290 Statement::Delete(d) => {
14291 if let Some(w) = &mut d.where_ {
14292 rewrite_expr_clock(w, now);
14293 }
14294 }
14295 _ => {}
14296 }
14297}
14298
14299fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14300 for item in &mut s.items {
14301 if let SelectItem::Expr { expr, .. } = item {
14302 rewrite_expr_clock(expr, now);
14303 }
14304 }
14305 if let Some(w) = &mut s.where_ {
14306 rewrite_expr_clock(w, now);
14307 }
14308 if let Some(gs) = &mut s.group_by {
14309 for g in gs {
14310 rewrite_expr_clock(g, now);
14311 }
14312 }
14313 if let Some(h) = &mut s.having {
14314 rewrite_expr_clock(h, now);
14315 }
14316 for o in &mut s.order_by {
14317 rewrite_expr_clock(&mut o.expr, now);
14318 }
14319 for (_, peer) in &mut s.unions {
14320 rewrite_select_clock(peer, now);
14321 }
14322}
14323
14324fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14332 if let Some(replacement) = clock_replacement_for(e, now) {
14336 *e = replacement;
14337 return;
14338 }
14339 match e {
14340 Expr::AggregateOrdered { call, order_by } => {
14341 rewrite_expr_clock(call, now);
14342 for o in order_by.iter_mut() {
14343 rewrite_expr_clock(&mut o.expr, now);
14344 }
14345 }
14346 Expr::Binary { lhs, rhs, .. } => {
14347 rewrite_expr_clock(lhs, now);
14348 rewrite_expr_clock(rhs, now);
14349 }
14350 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14351 rewrite_expr_clock(expr, now);
14352 }
14353 Expr::FunctionCall { args, .. } => {
14354 for a in args {
14355 rewrite_expr_clock(a, now);
14356 }
14357 }
14358 Expr::Like { expr, pattern, .. } => {
14359 rewrite_expr_clock(expr, now);
14360 rewrite_expr_clock(pattern, now);
14361 }
14362 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14363 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14367 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14368 Expr::InSubquery { expr, subquery, .. } => {
14369 rewrite_expr_clock(expr, now);
14370 rewrite_select_clock(subquery, now);
14371 }
14372 Expr::WindowFunction {
14375 args,
14376 partition_by,
14377 order_by,
14378 ..
14379 } => {
14380 for a in args {
14381 rewrite_expr_clock(a, now);
14382 }
14383 for p in partition_by {
14384 rewrite_expr_clock(p, now);
14385 }
14386 for (e, _) in order_by {
14387 rewrite_expr_clock(e, now);
14388 }
14389 }
14390 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14391 Expr::Array(items) => {
14392 for elem in items {
14393 rewrite_expr_clock(elem, now);
14394 }
14395 }
14396 Expr::ArraySubscript { target, index } => {
14397 rewrite_expr_clock(target, now);
14398 rewrite_expr_clock(index, now);
14399 }
14400 Expr::AnyAll { expr, array, .. } => {
14401 rewrite_expr_clock(expr, now);
14402 rewrite_expr_clock(array, now);
14403 }
14404 Expr::Case {
14405 operand,
14406 branches,
14407 else_branch,
14408 } => {
14409 if let Some(o) = operand {
14410 rewrite_expr_clock(o, now);
14411 }
14412 for (w, t) in branches {
14413 rewrite_expr_clock(w, now);
14414 rewrite_expr_clock(t, now);
14415 }
14416 if let Some(e) = else_branch {
14417 rewrite_expr_clock(e, now);
14418 }
14419 }
14420 }
14421}
14422
14423fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14430 let (kind, name) = match e {
14431 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14432 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14433 _ => return None,
14434 };
14435 enum ClockShape {
14443 Timestamp,
14444 Date,
14445 UnixSeconds,
14446 }
14447 let shape = match name.len() {
14448 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14449 Some(ClockShape::Timestamp)
14450 }
14451 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14452 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14453 Some(ClockShape::UnixSeconds)
14454 }
14455 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14456 _ => None,
14457 };
14458 let shape = shape?;
14459 let payload = match shape {
14460 ClockShape::Timestamp => now,
14461 ClockShape::Date => now.div_euclid(86_400_000_000),
14462 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14463 };
14464 let target = match shape {
14465 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14466 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14467 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14468 };
14469 Some(Expr::Cast {
14470 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14471 target,
14472 })
14473}
14474
14475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14476enum ClockSite {
14477 Fn,
14478 BareIdent,
14479}
14480
14481fn expand_group_by_all(s: &mut SelectStatement) {
14492 if !s.group_by_all {
14493 for (_, peer) in &mut s.unions {
14494 expand_group_by_all(peer);
14495 }
14496 return;
14497 }
14498 let mut groups: Vec<Expr> = Vec::new();
14499 for item in &s.items {
14500 if let SelectItem::Expr { expr, .. } = item
14501 && !aggregate::contains_aggregate(expr)
14502 {
14503 groups.push(expr.clone());
14504 }
14505 }
14506 s.group_by = Some(groups);
14507 s.group_by_all = false;
14508 for (_, peer) in &mut s.unions {
14509 expand_group_by_all(peer);
14510 }
14511}
14512
14513fn resolve_order_by_position(s: &mut SelectStatement) {
14514 for order in &mut s.order_by {
14519 match &order.expr {
14520 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14521 if let Ok(idx_one_based) = usize::try_from(*n) {
14522 let idx = idx_one_based - 1;
14523 if idx < s.items.len()
14524 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14525 {
14526 order.expr = expr.clone();
14527 }
14528 }
14529 }
14530 Expr::Column(c) if c.qualifier.is_none() => {
14531 for item in &s.items {
14533 if let SelectItem::Expr {
14534 expr,
14535 alias: Some(a),
14536 } = item
14537 && a == &c.name
14538 {
14539 order.expr = expr.clone();
14540 break;
14541 }
14542 }
14543 }
14544 _ => {}
14545 }
14546 }
14547 for (_, peer) in &mut s.unions {
14548 resolve_order_by_position(peer);
14549 }
14550}
14551
14552fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14565 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14566 match keep {
14567 Some(k) if k < tagged.len() && k > 0 => {
14568 let pivot = k - 1;
14569 tagged.select_nth_unstable_by(pivot, cmp);
14570 tagged[..k].sort_by(cmp);
14571 tagged.truncate(k);
14572 }
14573 _ => {
14574 tagged.sort_by(cmp);
14575 }
14576 }
14577}
14578
14579fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14580 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14581}
14582
14583fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14587 use core::cmp::Ordering;
14588 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14589 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14590 let ord = if descs.get(i).copied().unwrap_or(false) {
14591 ord.reverse()
14592 } else {
14593 ord
14594 };
14595 if ord != Ordering::Equal {
14596 return ord;
14597 }
14598 }
14599 Ordering::Equal
14600}
14601
14602fn build_order_keys(
14605 order_by: &[OrderBy],
14606 row: &Row,
14607 ctx: &EvalContext,
14608) -> Result<Vec<f64>, EngineError> {
14609 let mut keys = Vec::with_capacity(order_by.len());
14610 for o in order_by {
14611 let v = eval::eval_expr(&o.expr, row, ctx)?;
14612 if matches!(v, Value::Null) {
14619 let nf = o.nulls_first.unwrap_or(o.desc);
14620 keys.push(if nf == o.desc {
14621 f64::INFINITY
14622 } else {
14623 f64::NEG_INFINITY
14624 });
14625 } else {
14626 keys.push(value_to_order_key(&v)?);
14627 }
14628 }
14629 Ok(keys)
14630}
14631
14632fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14636 if let Some(off) = offset {
14637 let off = off as usize;
14638 if off >= rows.len() {
14639 rows.clear();
14640 } else {
14641 rows.drain(..off);
14642 }
14643 }
14644 if let Some(n) = limit {
14645 rows.truncate(n as usize);
14646 }
14647}
14648
14649fn apply_offset_and_limit_tagged(
14660 tagged: &mut Vec<(Vec<f64>, Row)>,
14661 offset: Option<u32>,
14662 limit: Option<u32>,
14663 with_ties: bool,
14664) {
14665 if let Some(off) = offset {
14666 let off = off as usize;
14667 if off >= tagged.len() {
14668 tagged.clear();
14669 } else {
14670 tagged.drain(..off);
14671 }
14672 }
14673 if let Some(n) = limit {
14674 let n = n as usize;
14675 if with_ties && n > 0 && n < tagged.len() {
14676 let cutoff_key = tagged[n - 1].0.clone();
14677 let mut end = n;
14678 while end < tagged.len() && tagged[end].0 == cutoff_key {
14679 end += 1;
14680 }
14681 tagged.truncate(end);
14682 } else {
14683 tagged.truncate(n);
14684 }
14685 }
14686}
14687
14688fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14694 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14695 return Err(EngineError::Unsupported(alloc::string::String::from(
14696 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14697 )));
14698 }
14699 Ok(())
14700}
14701
14702fn resolve_foreign_key(
14716 local_table_name: &str,
14717 local_cols: &[ColumnSchema],
14718 fk: spg_sql::ast::ForeignKeyConstraint,
14719 catalog: &Catalog,
14720) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14721 let mut local_columns = Vec::with_capacity(fk.columns.len());
14723 for name in &fk.columns {
14724 let pos = local_cols
14725 .iter()
14726 .position(|c| c.name == *name)
14727 .ok_or_else(|| {
14728 EngineError::Unsupported(alloc::format!(
14729 "FOREIGN KEY references unknown local column {name:?}"
14730 ))
14731 })?;
14732 local_columns.push(pos);
14733 }
14734 let is_self_ref = fk.parent_table == local_table_name;
14738 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14739 (local_cols, local_table_name)
14740 } else {
14741 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14742 EngineError::Storage(StorageError::TableNotFound {
14743 name: fk.parent_table.clone(),
14744 })
14745 })?;
14746 (
14747 parent_table.schema().columns.as_slice(),
14748 fk.parent_table.as_str(),
14749 )
14750 };
14751 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14756 if fk.columns.len() != 1 {
14757 return Err(EngineError::Unsupported(
14758 "composite FOREIGN KEY without explicit parent column list is not supported \
14759 — list the parent columns explicitly"
14760 .into(),
14761 ));
14762 }
14763 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14765 .ok_or_else(|| {
14766 EngineError::Unsupported(alloc::format!(
14767 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14768 to default the FOREIGN KEY against"
14769 ))
14770 })?;
14771 alloc::vec![pos]
14772 } else {
14773 let mut out = Vec::with_capacity(fk.parent_columns.len());
14774 for name in &fk.parent_columns {
14775 let pos = parent_cols_for_lookup
14776 .iter()
14777 .position(|c| c.name == *name)
14778 .ok_or_else(|| {
14779 EngineError::Unsupported(alloc::format!(
14780 "FOREIGN KEY references unknown parent column \
14781 {name:?} on table {parent_table_str:?}"
14782 ))
14783 })?;
14784 out.push(pos);
14785 }
14786 out
14787 };
14788 if parent_columns.len() != local_columns.len() {
14789 return Err(EngineError::Unsupported(alloc::format!(
14790 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14791 local_columns.len(),
14792 parent_columns.len()
14793 )));
14794 }
14795 if !is_self_ref {
14805 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14806 let primary_parent_col = parent_columns[0];
14807 let has_btree = parent_table
14808 .schema()
14809 .columns
14810 .get(primary_parent_col)
14811 .is_some()
14812 && parent_table.indices().iter().any(|idx| {
14813 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14814 && idx.column_position == primary_parent_col
14815 && idx.partial_predicate.is_none()
14816 });
14817 if !has_btree {
14818 return Err(EngineError::Unsupported(alloc::format!(
14819 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14820 index — create one with `CREATE INDEX ... ON {} ({})` first",
14821 parent_table_str,
14822 parent_table_str,
14823 parent_table.schema().columns[primary_parent_col].name,
14824 )));
14825 }
14826 }
14827 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14828 let on_update = fk_action_sql_to_storage(fk.on_update);
14829 Ok(spg_storage::ForeignKeyConstraint {
14830 name: fk.name,
14831 local_columns,
14832 parent_table: fk.parent_table,
14833 parent_columns,
14834 on_delete,
14835 on_update,
14836 })
14837}
14838
14839fn pick_pk_index_column(
14845 catalog: &Catalog,
14846 parent_name: &str,
14847 is_self_ref: bool,
14848 local_cols: &[ColumnSchema],
14849) -> Option<usize> {
14850 if is_self_ref {
14851 let _ = local_cols;
14855 return Some(0);
14856 }
14857 let parent = catalog.get(parent_name)?;
14858 parent.indices().iter().find_map(|idx| {
14859 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14860 && idx.partial_predicate.is_none()
14861 && idx.included_columns.is_empty()
14862 && idx.expression.is_none()
14863 {
14864 Some(idx.column_position)
14865 } else {
14866 None
14867 }
14868 })
14869}
14870
14871fn resolve_on_conflict_columns(
14878 catalog: &Catalog,
14879 table_name: &str,
14880 target: &[String],
14881) -> Result<Vec<usize>, EngineError> {
14882 let table = catalog.get(table_name).ok_or_else(|| {
14883 EngineError::Storage(StorageError::TableNotFound {
14884 name: table_name.into(),
14885 })
14886 })?;
14887 if target.is_empty() {
14888 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14898 return Ok(uc.columns.clone());
14899 }
14900 let pos = table
14901 .indices()
14902 .iter()
14903 .find_map(|idx| {
14904 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14905 && idx.partial_predicate.is_none()
14906 && idx.included_columns.is_empty()
14907 && idx.expression.is_none()
14908 {
14909 Some(idx.column_position)
14910 } else {
14911 None
14912 }
14913 })
14914 .ok_or_else(|| {
14915 EngineError::Unsupported(alloc::format!(
14916 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14917 ))
14918 })?;
14919 return Ok(alloc::vec![pos]);
14920 }
14921 let mut out = Vec::with_capacity(target.len());
14922 for name in target {
14923 let pos = table
14924 .schema()
14925 .columns
14926 .iter()
14927 .position(|c| c.name == *name)
14928 .ok_or_else(|| {
14929 EngineError::Unsupported(alloc::format!(
14930 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14931 ))
14932 })?;
14933 out.push(pos);
14934 }
14935 Ok(out)
14936}
14937
14938fn on_conflict_key_exists(
14941 catalog: &Catalog,
14942 table_name: &str,
14943 column_pos: usize,
14944 key: &Value,
14945) -> bool {
14946 let Some(table) = catalog.get(table_name) else {
14947 return false;
14948 };
14949 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14950 return false;
14951 };
14952 table.indices().iter().any(|idx| {
14953 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14954 && idx.column_position == column_pos
14955 && idx.partial_predicate.is_none()
14956 && !idx.lookup_eq(&idx_key).is_empty()
14957 })
14958}
14959
14960fn lookup_row_position_by_keys(
14966 catalog: &Catalog,
14967 table_name: &str,
14968 column_positions: &[usize],
14969 key: &[&Value],
14970) -> Option<usize> {
14971 let table = catalog.get(table_name)?;
14972 table.rows().iter().position(|r| {
14973 column_positions
14974 .iter()
14975 .enumerate()
14976 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14977 })
14978}
14979
14980fn on_conflict_keys_exist(
14985 catalog: &Catalog,
14986 table_name: &str,
14987 column_positions: &[usize],
14988 key: &[&Value],
14989) -> bool {
14990 if column_positions.len() == 1 {
14991 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14992 }
14993 let Some(table) = catalog.get(table_name) else {
14994 return false;
14995 };
14996 table.rows().iter().any(|r| {
14997 column_positions
14998 .iter()
14999 .enumerate()
15000 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
15001 })
15002}
15003
15004fn apply_on_conflict_assignments(
15017 catalog: &Catalog,
15018 table_name: &str,
15019 target_pos: usize,
15020 incoming: &[Value],
15021 assignments: &[(String, Expr)],
15022 where_: Option<&Expr>,
15023) -> Result<Option<Vec<Value>>, EngineError> {
15024 let table = catalog.get(table_name).ok_or_else(|| {
15025 EngineError::Storage(StorageError::TableNotFound {
15026 name: table_name.into(),
15027 })
15028 })?;
15029 let schema_cols = table.schema().columns.clone();
15030 let existing = table
15031 .rows()
15032 .get(target_pos)
15033 .ok_or_else(|| {
15034 EngineError::Unsupported(alloc::format!(
15035 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
15036 ))
15037 })?
15038 .clone();
15039 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
15040 if let Some(w) = where_ {
15042 let pred = w.clone();
15043 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
15044 let v = eval::eval_expr(&pred, &existing, &ctx)?;
15045 if !matches!(v, Value::Bool(true)) {
15046 return Ok(None);
15047 }
15048 }
15049 let mut new_values = existing.values.clone();
15050 for (col_name, expr) in assignments {
15051 let target_idx = schema_cols
15052 .iter()
15053 .position(|c| c.name == *col_name)
15054 .ok_or_else(|| {
15055 EngineError::Eval(EvalError::ColumnNotFound {
15056 name: col_name.clone(),
15057 })
15058 })?;
15059 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
15060 let v = eval::eval_expr(&sub, &existing, &ctx)?;
15061 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
15062 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
15063 new_values[target_idx] = coerced;
15064 }
15065 Ok(Some(new_values))
15066}
15067
15068fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
15073 use spg_sql::ast::ColumnName;
15074 match expr {
15075 Expr::Column(ColumnName { qualifier, name })
15076 if qualifier
15077 .as_deref()
15078 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
15079 {
15080 let pos = schema_cols.iter().position(|c| c.name == name);
15081 match pos {
15082 Some(p) => {
15083 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
15084 value_to_literal_expr(v)
15085 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
15086 }
15087 None => Expr::Column(ColumnName { qualifier, name }),
15088 }
15089 }
15090 Expr::Binary { op, lhs, rhs } => Expr::Binary {
15091 op,
15092 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
15093 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
15094 },
15095 Expr::Unary { op, expr } => Expr::Unary {
15096 op,
15097 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
15098 },
15099 Expr::FunctionCall { name, args } => Expr::FunctionCall {
15100 name,
15101 args: args
15102 .into_iter()
15103 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
15104 .collect(),
15105 },
15106 other => other,
15107 }
15108}
15109
15110fn enforce_uniqueness_inserts(
15133 catalog: &Catalog,
15134 child_table: &str,
15135 constraints: &[spg_storage::UniquenessConstraint],
15136 rows: &[Vec<Value>],
15137) -> Result<(), EngineError> {
15138 if constraints.is_empty() {
15139 return Ok(());
15140 }
15141 let table = catalog.get(child_table).ok_or_else(|| {
15142 EngineError::Storage(StorageError::TableNotFound {
15143 name: child_table.into(),
15144 })
15145 })?;
15146 let schema = table.schema();
15147 for uc in constraints {
15148 for (batch_idx, row_values) in rows.iter().enumerate() {
15149 let key: Vec<Value> = uc
15158 .columns
15159 .iter()
15160 .map(|&i| collated_key_cell(&row_values[i], i, schema))
15161 .collect();
15162 let has_null = key.iter().any(|v| matches!(v, Value::Null));
15163 if has_null && !uc.nulls_not_distinct {
15168 continue;
15169 }
15170 let collides_in_table = table.rows().iter().any(|prow| {
15172 uc.columns.iter().enumerate().all(|(i, &p)| {
15173 prow.values
15174 .get(p)
15175 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15176 })
15177 });
15178 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
15180 uc.columns.iter().enumerate().all(|(i, &p)| {
15181 earlier
15182 .get(p)
15183 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
15184 })
15185 });
15186 if collides_in_table || collides_in_batch {
15187 let kind = if uc.is_primary_key {
15188 "PRIMARY KEY"
15189 } else {
15190 "UNIQUE"
15191 };
15192 let col_names: Vec<String> = uc
15193 .columns
15194 .iter()
15195 .map(|&i| table.schema().columns[i].name.clone())
15196 .collect();
15197 return Err(EngineError::Unsupported(alloc::format!(
15198 "{kind} violation on {child_table:?} columns {col_names:?}: \
15199 row #{batch_idx} duplicates an existing key"
15200 )));
15201 }
15202 }
15203 }
15204 Ok(())
15205}
15206
15207fn collated_key_cell(
15214 v: &spg_storage::Value,
15215 column_position: usize,
15216 schema: &spg_storage::TableSchema,
15217) -> spg_storage::Value {
15218 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
15219 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
15220 spg_storage::Value::Text(s.to_ascii_lowercase())
15221 }
15222 _ => v.clone(),
15223 }
15224}
15225
15226fn predicate_truthy(v: &spg_storage::Value) -> bool {
15234 use spg_storage::Value as V;
15235 match v {
15236 V::Bool(b) => *b,
15237 V::Int(n) => *n != 0,
15238 V::BigInt(n) => *n != 0,
15239 V::SmallInt(n) => *n != 0,
15240 _ => false,
15241 }
15242}
15243
15244fn check_existing_unique_violation(
15249 idx: &spg_storage::Index,
15250 schema: &spg_storage::TableSchema,
15251 rows: &[spg_storage::Row],
15252) -> Result<(), EngineError> {
15253 let predicate_expr = match idx.partial_predicate.as_deref() {
15254 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15255 EngineError::Unsupported(alloc::format!(
15256 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15257 ))
15258 })?),
15259 None => None,
15260 };
15261 let ctx = eval::EvalContext::new(&schema.columns, None);
15262 let key_positions = unique_key_positions(idx);
15263 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15264 for row in rows {
15265 if let Some(expr) = &predicate_expr {
15266 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15267 EngineError::Unsupported(alloc::format!(
15268 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15269 ))
15270 })?;
15271 if !predicate_truthy(&v) {
15272 continue;
15273 }
15274 }
15275 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15276 .iter()
15277 .map(|&p| {
15278 let v = row
15279 .values
15280 .get(p)
15281 .cloned()
15282 .unwrap_or(spg_storage::Value::Null);
15283 collated_key_cell(&v, p, schema)
15284 })
15285 .collect();
15286 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15287 continue;
15288 }
15289 if seen.iter().any(|other| *other == key) {
15290 return Err(EngineError::Unsupported(alloc::format!(
15291 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15292 idx.name
15293 )));
15294 }
15295 seen.push(key);
15296 }
15297 Ok(())
15298}
15299
15300fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15304 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15305 out.push(idx.column_position);
15306 out.extend_from_slice(&idx.extra_column_positions);
15307 out
15308}
15309
15310fn enforce_unique_index_inserts(
15318 catalog: &Catalog,
15319 table_name: &str,
15320 rows: &[alloc::vec::Vec<spg_storage::Value>],
15321) -> Result<(), EngineError> {
15322 let table = catalog.get(table_name).ok_or_else(|| {
15323 EngineError::Storage(StorageError::TableNotFound {
15324 name: table_name.into(),
15325 })
15326 })?;
15327 let schema = table.schema();
15328 let ctx = eval::EvalContext::new(&schema.columns, None);
15329 for idx in table.indices() {
15330 if !idx.is_unique {
15331 continue;
15332 }
15333 let predicate_expr = match idx.partial_predicate.as_deref() {
15335 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15336 EngineError::Unsupported(alloc::format!(
15337 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15338 idx.name
15339 ))
15340 })?),
15341 None => None,
15342 };
15343 let key_positions = unique_key_positions(idx);
15344 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15345 key_positions
15349 .iter()
15350 .map(|&p| {
15351 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15352 collated_key_cell(&v, p, schema)
15353 })
15354 .collect()
15355 };
15356 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15360 let Some(expr) = &predicate_expr else {
15361 return Ok(true);
15362 };
15363 let tmp_row = spg_storage::Row {
15364 values: values.to_vec(),
15365 };
15366 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15367 EngineError::Unsupported(alloc::format!(
15368 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15369 idx.name
15370 ))
15371 })?;
15372 Ok(predicate_truthy(&v))
15373 };
15374 for (batch_idx, row_values) in rows.iter().enumerate() {
15375 if !participates(row_values)? {
15376 continue;
15377 }
15378 let key = key_of(row_values);
15379 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15380 continue;
15381 }
15382 for prow in table.rows() {
15384 if !participates(&prow.values)? {
15385 continue;
15386 }
15387 if key_of(&prow.values) == key {
15388 return Err(EngineError::Unsupported(alloc::format!(
15389 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15390 row #{batch_idx} duplicates an existing key",
15391 idx.name
15392 )));
15393 }
15394 }
15395 for earlier in &rows[..batch_idx] {
15397 if !participates(earlier)? {
15398 continue;
15399 }
15400 if key_of(earlier) == key {
15401 return Err(EngineError::Unsupported(alloc::format!(
15402 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15403 row #{batch_idx} duplicates an earlier row in the same batch",
15404 idx.name
15405 )));
15406 }
15407 }
15408 }
15409 }
15410 Ok(())
15411}
15412
15413fn any_column_changed(
15421 filter_cols: &[String],
15422 schema_cols: &[ColumnSchema],
15423 old_row: &Row,
15424 new_row: &Row,
15425) -> bool {
15426 for col_name in filter_cols {
15427 let Some(pos) = schema_cols
15428 .iter()
15429 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15430 else {
15431 continue;
15432 };
15433 let old_v = old_row.values.get(pos);
15434 let new_v = new_row.values.get(pos);
15435 if old_v != new_v {
15436 return true;
15437 }
15438 }
15439 false
15440}
15441
15442fn enforce_check_constraints(
15447 catalog: &Catalog,
15448 table_name: &str,
15449 rows: &[alloc::vec::Vec<spg_storage::Value>],
15450) -> Result<(), EngineError> {
15451 let table = catalog.get(table_name).ok_or_else(|| {
15452 EngineError::Storage(StorageError::TableNotFound {
15453 name: table_name.into(),
15454 })
15455 })?;
15456 let schema = table.schema();
15457 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15461 alloc::vec::Vec::new();
15462 for (idx, col) in schema.columns.iter().enumerate() {
15463 let Some(dname) = &col.user_domain_type else {
15464 continue;
15465 };
15466 let Some(dom) = catalog.domain_types().get(dname) else {
15467 continue;
15468 };
15469 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15470 alloc::vec::Vec::with_capacity(dom.checks.len());
15471 for src in &dom.checks {
15472 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15473 EngineError::Unsupported(alloc::format!(
15474 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15475 col.name
15476 ))
15477 })?;
15478 parsed_for_col.push(expr);
15479 }
15480 if !parsed_for_col.is_empty() {
15481 domain_checks_per_col.push((idx, parsed_for_col));
15482 }
15483 }
15484 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15485 return Ok(());
15486 }
15487 let ctx = eval::EvalContext::new(&schema.columns, None);
15488 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15489 for (i, src) in schema.checks.iter().enumerate() {
15490 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15491 EngineError::Unsupported(alloc::format!(
15492 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15493 ))
15494 })?;
15495 parsed.push((i, expr));
15496 }
15497 for (batch_idx, row_values) in rows.iter().enumerate() {
15498 let tmp_row = spg_storage::Row {
15499 values: row_values.clone(),
15500 };
15501 for (i, expr) in &parsed {
15502 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15503 EngineError::Unsupported(alloc::format!(
15504 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15505 ))
15506 })?;
15507 if matches!(v, spg_storage::Value::Bool(false)) {
15509 return Err(EngineError::Unsupported(alloc::format!(
15510 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15511 schema.checks[*i]
15512 )));
15513 }
15514 }
15515 for (col_idx, checks) in &domain_checks_per_col {
15521 let cell = row_values
15522 .get(*col_idx)
15523 .cloned()
15524 .unwrap_or(spg_storage::Value::Null);
15525 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15526 "value",
15527 schema.columns[*col_idx].ty,
15528 schema.columns[*col_idx].nullable,
15529 )];
15530 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15531 let synth_row = spg_storage::Row {
15532 values: alloc::vec![cell],
15533 };
15534 for (ci, expr) in checks.iter().enumerate() {
15535 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15536 EngineError::Unsupported(alloc::format!(
15537 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15538 schema.columns[*col_idx].name
15539 ))
15540 })?;
15541 if matches!(v, spg_storage::Value::Bool(false)) {
15542 return Err(EngineError::Unsupported(alloc::format!(
15543 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15544 schema.columns[*col_idx].name
15545 )));
15546 }
15547 }
15548 }
15549 }
15550 Ok(())
15551}
15552
15553fn enforce_fk_inserts(
15554 catalog: &Catalog,
15555 child_table: &str,
15556 fks: &[spg_storage::ForeignKeyConstraint],
15557 rows: &[Vec<Value>],
15558) -> Result<(), EngineError> {
15559 for fk in fks {
15560 let parent_is_self = fk.parent_table == child_table;
15561 let parent = if parent_is_self {
15562 catalog.get(child_table).ok_or_else(|| {
15565 EngineError::Storage(StorageError::TableNotFound {
15566 name: child_table.into(),
15567 })
15568 })?
15569 } else {
15570 catalog.get(&fk.parent_table).ok_or_else(|| {
15571 EngineError::Storage(StorageError::TableNotFound {
15572 name: fk.parent_table.clone(),
15573 })
15574 })?
15575 };
15576 for (batch_idx, row_values) in rows.iter().enumerate() {
15577 if fk.local_columns.len() == 1 {
15581 let v = &row_values[fk.local_columns[0]];
15582 if matches!(v, Value::Null) {
15583 continue;
15584 }
15585 let parent_col = fk.parent_columns[0];
15586 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15587 EngineError::Unsupported(alloc::format!(
15588 "FOREIGN KEY column value of type {:?} is not index-eligible",
15589 v.data_type()
15590 ))
15591 })?;
15592 let present_committed = parent.indices().iter().any(|idx| {
15593 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15594 && idx.column_position == parent_col
15595 && idx.partial_predicate.is_none()
15596 && !idx.lookup_eq(&key).is_empty()
15597 });
15598 let present_in_batch = parent_is_self
15602 && rows[..batch_idx]
15603 .iter()
15604 .any(|earlier| earlier.get(parent_col) == Some(v));
15605 if !(present_committed || present_in_batch) {
15606 return Err(EngineError::Unsupported(alloc::format!(
15607 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15608 fk.parent_table,
15609 parent
15610 .schema()
15611 .columns
15612 .get(parent_col)
15613 .map_or("?", |c| c.name.as_str()),
15614 v,
15615 )));
15616 }
15617 } else {
15618 if fk
15622 .local_columns
15623 .iter()
15624 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15625 {
15626 continue;
15627 }
15628 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15629 let parent_match_committed = parent.rows().iter().any(|prow| {
15630 fk.parent_columns
15631 .iter()
15632 .enumerate()
15633 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15634 });
15635 let parent_match_in_batch = parent_is_self
15636 && rows[..batch_idx].iter().any(|earlier| {
15637 fk.parent_columns
15638 .iter()
15639 .enumerate()
15640 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15641 });
15642 if !(parent_match_committed || parent_match_in_batch) {
15643 return Err(EngineError::Unsupported(alloc::format!(
15644 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15645 fk.parent_table,
15646 )));
15647 }
15648 }
15649 }
15650 }
15651 Ok(())
15652}
15653
15654#[derive(Debug, Clone)]
15658struct FkChildStep {
15659 child_table: String,
15660 action: FkChildAction,
15661}
15662
15663#[derive(Debug, Clone)]
15664enum FkChildAction {
15665 Delete { positions: Vec<usize> },
15667 SetNull {
15671 positions: Vec<usize>,
15672 columns: Vec<usize>,
15673 },
15674 SetDefault {
15678 positions: Vec<usize>,
15679 columns: Vec<usize>,
15680 defaults: Vec<Value>,
15681 },
15682}
15683
15684fn plan_fk_parent_deletions(
15700 catalog: &Catalog,
15701 parent_table_name: &str,
15702 to_delete_positions: &[usize],
15703 to_delete_rows: &[Vec<Value>],
15704) -> Result<Vec<FkChildStep>, EngineError> {
15705 use alloc::collections::{BTreeMap, BTreeSet};
15706 if to_delete_rows.is_empty() {
15707 return Ok(Vec::new());
15708 }
15709 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15710 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15712 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15713 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15714 for &p in to_delete_positions {
15715 visited.insert((parent_table_name.to_string(), p));
15716 }
15717 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15718 .iter()
15719 .map(|r| (parent_table_name.to_string(), r.clone()))
15720 .collect();
15721 while let Some((cur_parent, parent_row)) = work.pop() {
15722 for child_name in catalog.table_names() {
15723 let child = catalog
15724 .get(&child_name)
15725 .expect("table_names → catalog.get round-trip is total");
15726 for fk in &child.schema().foreign_keys {
15727 if fk.parent_table != cur_parent {
15728 continue;
15729 }
15730 let parent_key: Vec<&Value> = fk
15731 .parent_columns
15732 .iter()
15733 .map(|&pi| &parent_row[pi])
15734 .collect();
15735 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15736 continue;
15737 }
15738 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15739 if child_name == cur_parent
15740 && visited.contains(&(child_name.clone(), child_row_idx))
15741 {
15742 continue;
15743 }
15744 let matches_key = fk
15745 .local_columns
15746 .iter()
15747 .enumerate()
15748 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15749 if !matches_key {
15750 continue;
15751 }
15752 match fk.on_delete {
15753 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15754 return Err(EngineError::Unsupported(alloc::format!(
15755 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15756 restricted by FK from {child_name:?}.{:?}",
15757 fk.local_columns,
15758 )));
15759 }
15760 spg_storage::FkAction::Cascade => {
15761 if visited.insert((child_name.clone(), child_row_idx)) {
15762 delete_plan
15763 .entry(child_name.clone())
15764 .or_default()
15765 .insert(child_row_idx);
15766 work.push((child_name.clone(), child_row.values.clone()));
15767 }
15768 }
15769 spg_storage::FkAction::SetNull => {
15770 for &li in &fk.local_columns {
15772 let col = child.schema().columns.get(li).ok_or_else(|| {
15773 EngineError::Unsupported(alloc::format!(
15774 "FK local column {li} missing in {child_name:?}"
15775 ))
15776 })?;
15777 if !col.nullable {
15778 return Err(EngineError::Unsupported(alloc::format!(
15779 "FOREIGN KEY ON DELETE SET NULL: column \
15780 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15781 col.name,
15782 )));
15783 }
15784 }
15785 let entry = setnull_plan.entry(child_name.clone()).or_default();
15786 for &li in &fk.local_columns {
15787 entry.insert((child_row_idx, li));
15788 }
15789 }
15790 spg_storage::FkAction::SetDefault => {
15791 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15793 for &li in &fk.local_columns {
15794 let col = child.schema().columns.get(li).ok_or_else(|| {
15795 EngineError::Unsupported(alloc::format!(
15796 "FK local column {li} missing in {child_name:?}"
15797 ))
15798 })?;
15799 let default = col.default.clone().ok_or_else(|| {
15800 EngineError::Unsupported(alloc::format!(
15801 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15802 {child_name:?}.{:?} has no DEFAULT declared",
15803 col.name,
15804 ))
15805 })?;
15806 entry.insert((child_row_idx, li), default);
15807 }
15808 }
15809 }
15810 }
15811 }
15812 }
15813 }
15814 let mut steps: Vec<FkChildStep> = Vec::new();
15822 for (child_table, entries) in setnull_plan {
15823 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15824 steps.push(FkChildStep {
15825 child_table,
15826 action: FkChildAction::SetNull { positions, columns },
15827 });
15828 }
15829 for (child_table, entries) in setdefault_plan {
15830 let mut positions = Vec::with_capacity(entries.len());
15831 let mut columns = Vec::with_capacity(entries.len());
15832 let mut defaults = Vec::with_capacity(entries.len());
15833 for ((p, c), v) in entries {
15834 positions.push(p);
15835 columns.push(c);
15836 defaults.push(v);
15837 }
15838 steps.push(FkChildStep {
15839 child_table,
15840 action: FkChildAction::SetDefault {
15841 positions,
15842 columns,
15843 defaults,
15844 },
15845 });
15846 }
15847 for (child_table, positions) in delete_plan {
15848 steps.push(FkChildStep {
15849 child_table,
15850 action: FkChildAction::Delete {
15851 positions: positions.into_iter().collect(),
15852 },
15853 });
15854 }
15855 Ok(steps)
15856}
15857
15858fn plan_fk_parent_updates(
15875 catalog: &Catalog,
15876 parent_table_name: &str,
15877 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15878) -> Result<Vec<FkChildStep>, EngineError> {
15879 use alloc::collections::BTreeMap;
15880 if plan_with_old.is_empty() {
15881 return Ok(Vec::new());
15882 }
15883 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15888 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15889 BTreeMap::new();
15890 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15891 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15893
15894 for child_name in catalog.table_names() {
15895 let child = catalog
15896 .get(&child_name)
15897 .expect("table_names → catalog.get total");
15898 for fk in &child.schema().foreign_keys {
15899 if fk.parent_table != parent_table_name {
15900 continue;
15901 }
15902 for (_pos, old_row, new_row) in plan_with_old {
15903 let key_changed = fk
15905 .parent_columns
15906 .iter()
15907 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15908 if !key_changed {
15909 continue;
15910 }
15911 let old_key: Vec<&Value> =
15913 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15914 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15915 continue;
15917 }
15918 let new_key: Vec<&Value> =
15919 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15920 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15921 if child_name == parent_table_name
15924 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15925 {
15926 continue;
15927 }
15928 let matches_key = fk
15929 .local_columns
15930 .iter()
15931 .enumerate()
15932 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15933 if !matches_key {
15934 continue;
15935 }
15936 match fk.on_update {
15937 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15938 return Err(EngineError::Unsupported(alloc::format!(
15939 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15940 restricted by FK from {child_name:?}.{:?}",
15941 fk.local_columns,
15942 )));
15943 }
15944 spg_storage::FkAction::Cascade => {
15945 let entry = cascade_plan.entry(child_name.clone()).or_default();
15947 for (i, &li) in fk.local_columns.iter().enumerate() {
15948 entry.insert((child_row_idx, li), new_key[i].clone());
15949 }
15950 }
15951 spg_storage::FkAction::SetNull => {
15952 for &li in &fk.local_columns {
15953 let col = child.schema().columns.get(li).ok_or_else(|| {
15954 EngineError::Unsupported(alloc::format!(
15955 "FK local column {li} missing in {child_name:?}"
15956 ))
15957 })?;
15958 if !col.nullable {
15959 return Err(EngineError::Unsupported(alloc::format!(
15960 "FOREIGN KEY ON UPDATE SET NULL: column \
15961 {child_name:?}.{:?} is NOT NULL",
15962 col.name,
15963 )));
15964 }
15965 }
15966 let entry = setnull_plan.entry(child_name.clone()).or_default();
15967 for &li in &fk.local_columns {
15968 entry.insert((child_row_idx, li));
15969 }
15970 }
15971 spg_storage::FkAction::SetDefault => {
15972 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15973 for &li in &fk.local_columns {
15974 let col = child.schema().columns.get(li).ok_or_else(|| {
15975 EngineError::Unsupported(alloc::format!(
15976 "FK local column {li} missing in {child_name:?}"
15977 ))
15978 })?;
15979 let default = col.default.clone().ok_or_else(|| {
15980 EngineError::Unsupported(alloc::format!(
15981 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15982 {child_name:?}.{:?} has no DEFAULT",
15983 col.name,
15984 ))
15985 })?;
15986 entry.insert((child_row_idx, li), default);
15987 }
15988 }
15989 }
15990 }
15991 }
15992 }
15993 }
15994 let mut steps: Vec<FkChildStep> = Vec::new();
15997 for (child_table, entries) in cascade_plan {
15998 let mut positions = Vec::with_capacity(entries.len());
15999 let mut columns = Vec::with_capacity(entries.len());
16000 let mut defaults = Vec::with_capacity(entries.len());
16001 for ((p, c), v) in entries {
16002 positions.push(p);
16003 columns.push(c);
16004 defaults.push(v);
16005 }
16006 steps.push(FkChildStep {
16011 child_table,
16012 action: FkChildAction::SetDefault {
16013 positions,
16014 columns,
16015 defaults,
16016 },
16017 });
16018 }
16019 for (child_table, entries) in setnull_plan {
16020 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
16021 steps.push(FkChildStep {
16022 child_table,
16023 action: FkChildAction::SetNull { positions, columns },
16024 });
16025 }
16026 for (child_table, entries) in setdefault_plan {
16027 let mut positions = Vec::with_capacity(entries.len());
16028 let mut columns = Vec::with_capacity(entries.len());
16029 let mut defaults = Vec::with_capacity(entries.len());
16030 for ((p, c), v) in entries {
16031 positions.push(p);
16032 columns.push(c);
16033 defaults.push(v);
16034 }
16035 steps.push(FkChildStep {
16036 child_table,
16037 action: FkChildAction::SetDefault {
16038 positions,
16039 columns,
16040 defaults,
16041 },
16042 });
16043 }
16044 let _ = delete_plan; Ok(steps)
16046}
16047
16048fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
16052 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
16053 EngineError::Storage(StorageError::TableNotFound {
16054 name: step.child_table.clone(),
16055 })
16056 })?;
16057 match &step.action {
16058 FkChildAction::Delete { positions } => {
16059 let _ = child.delete_rows(positions);
16060 }
16061 FkChildAction::SetNull { positions, columns } => {
16062 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
16063 }
16064 FkChildAction::SetDefault {
16065 positions,
16066 columns,
16067 defaults,
16068 } => {
16069 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
16070 }
16071 }
16072 Ok(())
16073}
16074
16075fn apply_per_cell_writes(
16081 child: &mut spg_storage::Table,
16082 positions: &[usize],
16083 columns: &[usize],
16084 mut value_for: impl FnMut(usize) -> Value,
16085) -> Result<(), EngineError> {
16086 use alloc::collections::BTreeMap;
16087 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
16088 for i in 0..positions.len() {
16089 by_row
16090 .entry(positions[i])
16091 .or_default()
16092 .push((columns[i], value_for(i)));
16093 }
16094 for (pos, mutations) in by_row {
16095 let mut new_values = child.rows()[pos].values.clone();
16096 for (col, v) in mutations {
16097 if let Some(slot) = new_values.get_mut(col) {
16098 *slot = v;
16099 }
16100 }
16101 child
16102 .update_row(pos, new_values)
16103 .map_err(EngineError::Storage)?;
16104 }
16105 Ok(())
16106}
16107
16108fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
16109 match a {
16110 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
16111 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
16112 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
16113 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
16114 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
16115 }
16116}
16117
16118fn resolve_column_default_free(
16124 col: &ColumnSchema,
16125 clock_fn: Option<ClockFn>,
16126) -> Result<Value, EngineError> {
16127 if let Some(rt) = &col.runtime_default {
16128 return eval_runtime_default_free(rt, col.ty, clock_fn);
16129 }
16130 Ok(col.default.clone().unwrap_or(Value::Null))
16131}
16132
16133fn eval_runtime_default_free(
16134 rt: &str,
16135 ty: DataType,
16136 clock_fn: Option<ClockFn>,
16137) -> Result<Value, EngineError> {
16138 let s = rt.trim().to_ascii_lowercase();
16139 let with_no_parens = s.trim_end_matches("()");
16145 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
16146 if with_no_parens.ends_with(')') {
16147 &with_no_parens[..open_idx]
16148 } else {
16149 with_no_parens
16150 }
16151 } else {
16152 with_no_parens
16153 };
16154 let now_us = match clock_fn {
16155 Some(f) => f(),
16156 None => 0,
16157 };
16158 let v = match canonical {
16159 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
16160 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
16161 "current_time" | "localtime" => Value::Timestamp(now_us),
16162 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
16168 other => {
16169 return Err(EngineError::Unsupported(alloc::format!(
16170 "runtime DEFAULT expression {other:?} not supported \
16171 (v7.17.0 whitelist: now() / current_timestamp / \
16172 current_date / current_time / localtimestamp / \
16173 localtime / gen_random_uuid() / \
16174 uuid_generate_v4())"
16175 )));
16176 }
16177 };
16178 coerce_value(v, ty, "DEFAULT", 0)
16179}
16180
16181fn is_runtime_default_expr(expr: &Expr) -> bool {
16187 match expr {
16188 Expr::FunctionCall { .. } => true,
16189 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
16190 _ => false,
16191 }
16192}
16193
16194fn canonicalize_set_value(
16207 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16208 col_idx: usize,
16209 col_name: &str,
16210 value: Value,
16211) -> Result<Value, EngineError> {
16212 let Some(variants) = lookup.get(&col_idx) else {
16213 return Ok(value);
16214 };
16215 match value {
16216 Value::Null => Ok(Value::Null),
16217 Value::Text(s) => {
16218 if s.is_empty() {
16219 return Ok(Value::Text(alloc::string::String::new()));
16220 }
16221 let mut present = alloc::vec![false; variants.len()];
16224 for raw in s.split(',') {
16225 let tok = raw.trim();
16226 if tok.is_empty() {
16227 continue;
16228 }
16229 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
16230 EngineError::Unsupported(alloc::format!(
16231 "column {col_name:?}: invalid SET token {tok:?}; \
16232 allowed: {variants:?}"
16233 ))
16234 })?;
16235 present[idx] = true;
16236 }
16237 let mut out = alloc::string::String::new();
16239 let mut first = true;
16240 for (i, keep) in present.iter().enumerate() {
16241 if !keep {
16242 continue;
16243 }
16244 if !first {
16245 out.push(',');
16246 }
16247 first = false;
16248 out.push_str(&variants[i]);
16249 }
16250 Ok(Value::Text(out))
16251 }
16252 other => Err(EngineError::Unsupported(alloc::format!(
16253 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16254 other.data_type()
16255 ))),
16256 }
16257}
16258
16259fn enforce_enum_label(
16260 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16261 col_idx: usize,
16262 col_name: &str,
16263 value: &Value,
16264) -> Result<(), EngineError> {
16265 if let Some(labels) = lookup.get(&col_idx) {
16266 match value {
16267 Value::Null => Ok(()),
16268 Value::Text(s) => {
16269 if labels.iter().any(|l| l == s) {
16270 Ok(())
16271 } else {
16272 Err(EngineError::Unsupported(alloc::format!(
16273 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16274 )))
16275 }
16276 }
16277 other => Err(EngineError::Unsupported(alloc::format!(
16278 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16279 other.data_type()
16280 ))),
16281 }
16282 } else {
16283 Ok(())
16284 }
16285}
16286
16287fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16288 let ty = column_type_to_data_type(c.ty);
16289 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16290 if let Some(name) = c.user_type_ref {
16297 schema.user_enum_type = Some(name);
16298 }
16299 if let Some(expr) = c.on_update_runtime {
16302 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16303 }
16304 schema.collation = match c.collation {
16308 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16309 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16310 };
16311 schema.is_unsigned = c.is_unsigned;
16314 schema.inline_enum_variants = c.inline_enum_variants;
16318 schema.inline_set_variants = c.inline_set_variants;
16322 if let Some(default_expr) = c.default {
16323 if is_runtime_default_expr(&default_expr) {
16329 let display = alloc::format!("{default_expr}");
16330 schema = schema.with_runtime_default(display);
16331 } else {
16332 let raw = literal_expr_to_value(default_expr)?;
16333 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16334 schema = schema.with_default(coerced);
16335 }
16336 }
16337 if c.auto_increment {
16338 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16340 return Err(EngineError::Unsupported(alloc::format!(
16341 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16342 )));
16343 }
16344 schema = schema.with_auto_increment();
16345 }
16346 Ok(schema)
16347}
16348
16349fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16354 let s = s.trim();
16355 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16356 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16358 if cleaned.len() % 2 != 0 {
16359 return Err("odd-length hex literal");
16360 }
16361 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16362 let cleaned_bytes = cleaned.as_bytes();
16363 for i in (0..cleaned_bytes.len()).step_by(2) {
16364 let hi = hex_nibble(cleaned_bytes[i])?;
16365 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16366 out.push((hi << 4) | lo);
16367 }
16368 return Ok(out);
16369 }
16370 let bytes = s.as_bytes();
16373 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16374 let mut i = 0;
16375 while i < bytes.len() {
16376 let b = bytes[i];
16377 if b == b'\\' && i + 1 < bytes.len() {
16378 let n = bytes[i + 1];
16379 if n == b'\\' {
16380 out.push(b'\\');
16381 i += 2;
16382 continue;
16383 }
16384 if n.is_ascii_digit()
16385 && i + 3 < bytes.len()
16386 && bytes[i + 2].is_ascii_digit()
16387 && bytes[i + 3].is_ascii_digit()
16388 {
16389 let oct = |x: u8| (x - b'0') as u32;
16390 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16391 if v <= 0xFF {
16392 out.push(v as u8);
16393 i += 4;
16394 continue;
16395 }
16396 }
16397 }
16398 out.push(b);
16399 i += 1;
16400 }
16401 Ok(out)
16402}
16403
16404fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16405 match b {
16406 b'0'..=b'9' => Ok(b - b'0'),
16407 b'a'..=b'f' => Ok(b - b'a' + 10),
16408 b'A'..=b'F' => Ok(b - b'A' + 10),
16409 _ => Err("invalid hex digit"),
16410 }
16411}
16412
16413fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16427 let mut has_text = false;
16428 let mut has_bigint = false;
16429 let mut has_int = false;
16430 for v in &items {
16431 match v {
16432 Value::Null => {}
16433 Value::Text(_) | Value::Json(_) => has_text = true,
16434 Value::BigInt(_) => has_bigint = true,
16435 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16436 _ => has_text = true,
16437 }
16438 }
16439 if has_text || (!has_bigint && !has_int) {
16440 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16441 .into_iter()
16442 .map(|v| match v {
16443 Value::Null => None,
16444 Value::Text(s) | Value::Json(s) => Some(s),
16445 other => Some(alloc::format!("{other:?}")),
16446 })
16447 .collect();
16448 return Value::TextArray(out);
16449 }
16450 if has_bigint {
16451 let out: alloc::vec::Vec<Option<i64>> = items
16452 .into_iter()
16453 .map(|v| match v {
16454 Value::Null => None,
16455 Value::Int(n) => Some(i64::from(n)),
16456 Value::SmallInt(n) => Some(i64::from(n)),
16457 Value::BigInt(n) => Some(n),
16458 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16459 })
16460 .collect();
16461 return Value::BigIntArray(out);
16462 }
16463 let out: alloc::vec::Vec<Option<i32>> = items
16464 .into_iter()
16465 .map(|v| match v {
16466 Value::Null => None,
16467 Value::Int(n) => Some(n),
16468 Value::SmallInt(n) => Some(i32::from(n)),
16469 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16470 })
16471 .collect();
16472 Value::IntArray(out)
16473}
16474
16475fn decode_text_array_literal(
16476 s: &str,
16477) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16478 let trimmed = s.trim();
16479 let inner = trimmed
16480 .strip_prefix('{')
16481 .and_then(|x| x.strip_suffix('}'))
16482 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16483 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16484 if inner.trim().is_empty() {
16485 return Ok(out);
16486 }
16487 let bytes = inner.as_bytes();
16488 let mut i = 0;
16489 while i <= bytes.len() {
16490 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16492 i += 1;
16493 }
16494 if i < bytes.len() && bytes[i] == b'"' {
16496 i += 1; let mut buf = alloc::string::String::new();
16498 while i < bytes.len() && bytes[i] != b'"' {
16499 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16500 buf.push(bytes[i + 1] as char);
16501 i += 2;
16502 } else {
16503 buf.push(bytes[i] as char);
16504 i += 1;
16505 }
16506 }
16507 if i >= bytes.len() {
16508 return Err("unterminated quoted element");
16509 }
16510 i += 1; out.push(Some(buf));
16512 } else {
16513 let start = i;
16515 while i < bytes.len() && bytes[i] != b',' {
16516 i += 1;
16517 }
16518 let raw = inner[start..i].trim();
16519 if raw.eq_ignore_ascii_case("NULL") {
16520 out.push(None);
16521 } else {
16522 out.push(Some(alloc::string::ToString::to_string(raw)));
16523 }
16524 }
16525 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16527 i += 1;
16528 }
16529 if i >= bytes.len() {
16530 break;
16531 }
16532 if bytes[i] != b',' {
16533 return Err("expected ',' between TEXT[] elements");
16534 }
16535 i += 1;
16536 }
16537 Ok(out)
16538}
16539
16540fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16545 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16546 out.push('{');
16547 for (i, item) in items.iter().enumerate() {
16548 if i > 0 {
16549 out.push(',');
16550 }
16551 match item {
16552 None => out.push_str("NULL"),
16553 Some(s) => {
16554 let needs_quote = s.is_empty()
16555 || s.eq_ignore_ascii_case("NULL")
16556 || s.chars()
16557 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16558 if needs_quote {
16559 out.push('"');
16560 for c in s.chars() {
16561 if c == '"' || c == '\\' {
16562 out.push('\\');
16563 }
16564 out.push(c);
16565 }
16566 out.push('"');
16567 } else {
16568 out.push_str(s);
16569 }
16570 }
16571 }
16572 }
16573 out.push('}');
16574 out
16575}
16576
16577fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16581 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16582 out.push_str("\\x");
16583 for byte in b {
16584 let hi = byte >> 4;
16585 let lo = byte & 0x0F;
16586 out.push(hex_digit(hi));
16587 out.push(hex_digit(lo));
16588 }
16589 out
16590}
16591
16592const fn hex_digit(n: u8) -> char {
16593 match n {
16594 0..=9 => (b'0' + n) as char,
16595 10..=15 => (b'a' + n - 10) as char,
16596 _ => '?',
16597 }
16598}
16599
16600fn parse_hstore_str(
16612 s: &str,
16613) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16614 let bytes = s.as_bytes();
16615 let mut i = 0;
16616 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16617 let skip_ws = |bytes: &[u8], i: &mut usize| {
16618 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16619 *i += 1;
16620 }
16621 };
16622 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16623 if *i >= bytes.len() {
16624 return None;
16625 }
16626 if bytes[*i] == b'"' {
16627 *i += 1;
16628 let mut out = alloc::string::String::new();
16629 while *i < bytes.len() {
16630 match bytes[*i] {
16631 b'"' => {
16632 *i += 1;
16633 return Some(out);
16634 }
16635 b'\\' if *i + 1 < bytes.len() => {
16636 out.push(bytes[*i + 1] as char);
16637 *i += 2;
16638 }
16639 c => {
16640 out.push(c as char);
16641 *i += 1;
16642 }
16643 }
16644 }
16645 None
16646 } else {
16647 let start = *i;
16648 while *i < bytes.len()
16649 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16650 {
16651 *i += 1;
16652 }
16653 if *i == start {
16654 return None;
16655 }
16656 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16657 }
16658 };
16659 skip_ws(bytes, &mut i);
16660 while i < bytes.len() {
16661 let key = parse_token(bytes, &mut i)?;
16662 skip_ws(bytes, &mut i);
16663 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16664 return None;
16665 }
16666 i += 2;
16667 skip_ws(bytes, &mut i);
16668 let val_token = if i + 4 <= bytes.len()
16670 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16671 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16672 {
16673 i += 4;
16674 None
16675 } else {
16676 Some(parse_token(bytes, &mut i)?)
16677 };
16678 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16680 out[pos] = (key, val_token);
16681 } else {
16682 out.push((key, val_token));
16683 }
16684 skip_ws(bytes, &mut i);
16685 if i >= bytes.len() {
16686 break;
16687 }
16688 if bytes[i] == b',' {
16689 i += 1;
16690 skip_ws(bytes, &mut i);
16691 continue;
16692 }
16693 return None;
16694 }
16695 Some(out)
16696}
16697
16698fn format_hstore_str(
16702 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16703) -> alloc::string::String {
16704 let mut out = alloc::string::String::new();
16705 for (i, (k, v)) in pairs.iter().enumerate() {
16706 if i > 0 {
16707 out.push_str(", ");
16708 }
16709 out.push('"');
16710 out.push_str(k);
16711 out.push_str("\"=>");
16712 match v {
16713 None => out.push_str("NULL"),
16714 Some(val) => {
16715 out.push('"');
16716 out.push_str(val);
16717 out.push('"');
16718 }
16719 }
16720 }
16721 out
16722}
16723
16724pub fn format_hstore_text(
16727 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16728) -> alloc::string::String {
16729 format_hstore_str(pairs)
16730}
16731
16732fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16737 let s = s.trim();
16738 let outer = s
16739 .strip_prefix('{')
16740 .and_then(|x| x.strip_suffix('}'))
16741 .ok_or("missing outer '{...}' braces")?;
16742 let trimmed = outer.trim();
16743 if trimmed.is_empty() {
16744 return Ok(Vec::new());
16745 }
16746 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16747 let mut i = 0;
16748 let bytes = trimmed.as_bytes();
16749 while i < bytes.len() {
16750 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16751 i += 1;
16752 }
16753 if i >= bytes.len() {
16754 break;
16755 }
16756 if bytes[i] != b'{' {
16757 return Err("expected '{' opening a row");
16758 }
16759 i += 1;
16760 let row_start = i;
16761 let mut depth = 1;
16762 while i < bytes.len() && depth > 0 {
16763 match bytes[i] {
16764 b'{' => depth += 1,
16765 b'}' => depth -= 1,
16766 _ => {}
16767 }
16768 if depth > 0 {
16769 i += 1;
16770 }
16771 }
16772 if depth != 0 {
16773 return Err("unbalanced '{...}' in row");
16774 }
16775 let row_text = &trimmed[row_start..i];
16776 i += 1;
16777 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16778 Vec::new()
16779 } else {
16780 row_text.split(',').map(|t| t.trim().to_string()).collect()
16781 };
16782 rows.push(cells);
16783 }
16784 if let Some(first) = rows.first() {
16785 let cols = first.len();
16786 for r in &rows {
16787 if r.len() != cols {
16788 return Err("ragged 2D array (rows have different column counts)");
16789 }
16790 }
16791 }
16792 Ok(rows)
16793}
16794
16795fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16796 let raw = split_2d_literal(s)?;
16797 raw.into_iter()
16798 .map(|row| {
16799 row.into_iter()
16800 .map(|cell| {
16801 if cell.eq_ignore_ascii_case("NULL") {
16802 Ok(None)
16803 } else {
16804 cell.parse::<i32>()
16805 .map(Some)
16806 .map_err(|_| "invalid int element")
16807 }
16808 })
16809 .collect()
16810 })
16811 .collect()
16812}
16813
16814fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16815 let raw = split_2d_literal(s)?;
16816 raw.into_iter()
16817 .map(|row| {
16818 row.into_iter()
16819 .map(|cell| {
16820 if cell.eq_ignore_ascii_case("NULL") {
16821 Ok(None)
16822 } else {
16823 cell.parse::<i64>()
16824 .map(Some)
16825 .map_err(|_| "invalid bigint element")
16826 }
16827 })
16828 .collect()
16829 })
16830 .collect()
16831}
16832
16833fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16834 let raw = split_2d_literal(s)?;
16835 Ok(raw
16836 .into_iter()
16837 .map(|row| {
16838 row.into_iter()
16839 .map(|cell| {
16840 if cell.eq_ignore_ascii_case("NULL") {
16841 None
16842 } else {
16843 Some(cell.trim_matches('"').to_string())
16844 }
16845 })
16846 .collect()
16847 })
16848 .collect())
16849}
16850
16851fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16852 let mut out = alloc::string::String::from("{");
16853 for (i, row) in rows.iter().enumerate() {
16854 if i > 0 {
16855 out.push(',');
16856 }
16857 out.push('{');
16858 for (j, cell) in row.iter().enumerate() {
16859 if j > 0 {
16860 out.push(',');
16861 }
16862 match cell {
16863 None => out.push_str("NULL"),
16864 Some(n) => out.push_str(&alloc::format!("{n}")),
16865 }
16866 }
16867 out.push('}');
16868 }
16869 out.push('}');
16870 out
16871}
16872
16873fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16874 let mut out = alloc::string::String::from("{");
16875 for (i, row) in rows.iter().enumerate() {
16876 if i > 0 {
16877 out.push(',');
16878 }
16879 out.push('{');
16880 for (j, cell) in row.iter().enumerate() {
16881 if j > 0 {
16882 out.push(',');
16883 }
16884 match cell {
16885 None => out.push_str("NULL"),
16886 Some(n) => out.push_str(&alloc::format!("{n}")),
16887 }
16888 }
16889 out.push('}');
16890 }
16891 out.push('}');
16892 out
16893}
16894
16895fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16896 let mut out = alloc::string::String::from("{");
16897 for (i, row) in rows.iter().enumerate() {
16898 if i > 0 {
16899 out.push(',');
16900 }
16901 out.push('{');
16902 for (j, cell) in row.iter().enumerate() {
16903 if j > 0 {
16904 out.push(',');
16905 }
16906 match cell {
16907 None => out.push_str("NULL"),
16908 Some(s) => out.push_str(s),
16909 }
16910 }
16911 out.push('}');
16912 }
16913 out.push('}');
16914 out
16915}
16916
16917pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16920 format_int_2d_text(rows)
16921}
16922pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16923 format_bigint_2d_text(rows)
16924}
16925pub fn format_text_2d_text_pub(
16926 rows: &[Vec<Option<alloc::string::String>>],
16927) -> alloc::string::String {
16928 format_text_2d_text(rows)
16929}
16930
16931fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16936 let s = s.trim();
16937 if s.eq_ignore_ascii_case("empty") {
16938 return Some(Value::Range {
16939 kind,
16940 lower: None,
16941 upper: None,
16942 lower_inc: false,
16943 upper_inc: false,
16944 empty: true,
16945 });
16946 }
16947 let bytes = s.as_bytes();
16948 if bytes.len() < 3 {
16949 return None;
16950 }
16951 let lower_inc = match bytes[0] {
16952 b'[' => true,
16953 b'(' => false,
16954 _ => return None,
16955 };
16956 let upper_inc = match bytes[bytes.len() - 1] {
16957 b']' => true,
16958 b')' => false,
16959 _ => return None,
16960 };
16961 let inner = &s[1..s.len() - 1];
16962 let (lo_text, up_text) = inner.split_once(',')?;
16963 let lower = if lo_text.is_empty() {
16964 None
16965 } else {
16966 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16967 };
16968 let upper = if up_text.is_empty() {
16969 None
16970 } else {
16971 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16972 };
16973 Some(Value::Range {
16974 kind,
16975 lower,
16976 upper,
16977 lower_inc,
16978 upper_inc,
16979 empty: false,
16980 })
16981}
16982
16983fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16986 let text = text.trim().trim_matches('"');
16987 use spg_storage::RangeKind as K;
16988 match kind {
16989 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16990 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16991 K::Num => {
16992 let dot = text.find('.');
16995 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16996 let digits: alloc::string::String = text
16997 .chars()
16998 .filter(|c| *c == '-' || c.is_ascii_digit())
16999 .collect();
17000 let scaled: i128 = digits.parse().ok()?;
17001 Some(Value::Numeric { scaled, scale })
17002 }
17003 K::Ts | K::TsTz => {
17004 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
17009 }
17010 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
17011 }
17012}
17013
17014pub fn format_range_text(v: &Value) -> alloc::string::String {
17018 format_range_str(v)
17019}
17020
17021fn format_range_str(v: &Value) -> alloc::string::String {
17022 let Value::Range {
17023 lower,
17024 upper,
17025 lower_inc,
17026 upper_inc,
17027 empty,
17028 ..
17029 } = v
17030 else {
17031 return alloc::string::String::new();
17032 };
17033 if *empty {
17034 return "empty".into();
17035 }
17036 let mut out = alloc::string::String::new();
17037 out.push(if *lower_inc { '[' } else { '(' });
17038 if let Some(l) = lower {
17039 out.push_str(&format_range_element(l));
17040 }
17041 out.push(',');
17042 if let Some(u) = upper {
17043 out.push_str(&format_range_element(u));
17044 }
17045 out.push(if *upper_inc { ']' } else { ')' });
17046 out
17047}
17048
17049fn format_range_element(v: &Value) -> alloc::string::String {
17050 match v {
17051 Value::Int(n) => alloc::format!("{n}"),
17052 Value::BigInt(n) => alloc::format!("{n}"),
17053 Value::Date(d) => crate::eval::format_date(*d),
17054 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
17055 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
17056 other => alloc::format!("{other:?}"),
17057 }
17058}
17059
17060fn parse_money_str(s: &str) -> Option<i64> {
17071 let s = s.trim();
17072 let (neg, rest) = match s.strip_prefix('-') {
17073 Some(r) => (true, r.trim_start()),
17074 None => (false, s),
17075 };
17076 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
17077 let (int_part, frac_part) = match rest.split_once('.') {
17078 Some((i, f)) => (i, Some(f)),
17079 None => (rest, None),
17080 };
17081 if int_part.is_empty() {
17082 return None;
17083 }
17084 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
17086 for b in int_part.bytes() {
17087 match b {
17088 b',' => {}
17089 b'0'..=b'9' => int_digits.push(b as char),
17090 _ => return None,
17091 }
17092 }
17093 if int_digits.is_empty() {
17094 return None;
17095 }
17096 let dollars: i64 = int_digits.parse().ok()?;
17097 let cents: i64 = match frac_part {
17098 None => 0,
17099 Some(f) => {
17100 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
17101 return None;
17102 }
17103 let padded = if f.len() == 1 {
17104 alloc::format!("{f}0")
17105 } else {
17106 f.to_string()
17107 };
17108 padded.parse().ok()?
17109 }
17110 };
17111 let total = dollars.checked_mul(100)?.checked_add(cents)?;
17112 Some(if neg { -total } else { total })
17113}
17114
17115fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
17126 let s = s.trim();
17127 let bytes = s.as_bytes();
17131 let sign_pos = bytes
17132 .iter()
17133 .enumerate()
17134 .rev()
17135 .find(|&(_, &b)| b == b'+' || b == b'-')
17136 .map(|(i, _)| i)?;
17137 if sign_pos == 0 {
17138 return None; }
17140 let time_part = &s[..sign_pos];
17141 let offset_part = &s[sign_pos..];
17142 let us = parse_time_str(time_part)?;
17143 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
17144 let offset_body = &offset_part[1..];
17145 let (hh_str, mm_str) = match offset_body.split_once(':') {
17146 Some((h, m)) => (h, m),
17147 None => (offset_body, "0"),
17148 };
17149 let hh: i32 = hh_str.parse().ok()?;
17150 let mm: i32 = mm_str.parse().ok()?;
17151 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
17152 return None;
17153 }
17154 let total = sign * (hh * 3600 + mm * 60);
17155 if total.abs() > 50_400 {
17156 return None;
17157 }
17158 Some((us, total))
17159}
17160
17161fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
17166 if n == 0 || (1901..=2155).contains(&n) {
17167 return Ok(Value::Year(n as u16));
17170 }
17171 Err(EngineError::Eval(EvalError::TypeMismatch {
17172 detail: alloc::format!(
17173 "year value out of range: {n} (column `{col_name}`; \
17174 MySQL accepts 0 or 1901..=2155)"
17175 ),
17176 }))
17177}
17178
17179fn parse_time_str(s: &str) -> Option<i64> {
17191 let s = s.trim();
17192 let (hms, frac) = match s.split_once('.') {
17193 Some((h, f)) => (h, Some(f)),
17194 None => (s, None),
17195 };
17196 let mut parts = hms.split(':');
17197 let hh: u32 = parts.next()?.parse().ok()?;
17198 let mm: u32 = parts.next()?.parse().ok()?;
17199 let ss: u32 = parts.next()?.parse().ok()?;
17200 if parts.next().is_some() {
17201 return None;
17202 }
17203 if hh > 23 || mm > 59 || ss > 59 {
17204 return None;
17205 }
17206 let frac_us: i64 = match frac {
17207 None => 0,
17208 Some(f) => {
17209 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
17210 return None;
17211 }
17212 let mut padded = alloc::string::String::with_capacity(6);
17214 padded.push_str(f);
17215 while padded.len() < 6 {
17216 padded.push('0');
17217 }
17218 padded.parse().ok()?
17219 }
17220 };
17221 Some(
17222 i64::from(hh) * 3_600_000_000
17223 + i64::from(mm) * 60_000_000
17224 + i64::from(ss) * 1_000_000
17225 + frac_us,
17226 )
17227}
17228
17229const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
17230 match t {
17231 ColumnTypeName::SmallInt => DataType::SmallInt,
17232 ColumnTypeName::Int => DataType::Int,
17233 ColumnTypeName::BigInt => DataType::BigInt,
17234 ColumnTypeName::Float => DataType::Float,
17235 ColumnTypeName::Text => DataType::Text,
17236 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17237 ColumnTypeName::Char(n) => DataType::Char(n),
17238 ColumnTypeName::Bool => DataType::Bool,
17239 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17240 dim,
17241 encoding: match encoding {
17242 SqlVecEncoding::F32 => VecEncoding::F32,
17243 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17244 SqlVecEncoding::F16 => VecEncoding::F16,
17245 },
17246 },
17247 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17248 ColumnTypeName::Date => DataType::Date,
17249 ColumnTypeName::Timestamp => DataType::Timestamp,
17250 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17251 ColumnTypeName::Json => DataType::Json,
17252 ColumnTypeName::Jsonb => DataType::Jsonb,
17253 ColumnTypeName::Bytes => DataType::Bytes,
17254 ColumnTypeName::TextArray => DataType::TextArray,
17255 ColumnTypeName::IntArray => DataType::IntArray,
17256 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17257 ColumnTypeName::TsVector => DataType::TsVector,
17258 ColumnTypeName::TsQuery => DataType::TsQuery,
17259 ColumnTypeName::Uuid => DataType::Uuid,
17260 ColumnTypeName::Time => DataType::Time,
17261 ColumnTypeName::Year => DataType::Year,
17262 ColumnTypeName::TimeTz => DataType::TimeTz,
17263 ColumnTypeName::Money => DataType::Money,
17264 ColumnTypeName::Range(k) => DataType::Range(match k {
17265 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17266 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17267 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17268 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17269 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17270 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17271 }),
17272 ColumnTypeName::Hstore => DataType::Hstore,
17273 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17274 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17275 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17276 }
17277}
17278
17279fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17283 match expr {
17284 Expr::Literal(l) => Ok(literal_to_value(l)),
17285 Expr::Cast { expr, target } => {
17286 let inner_value = literal_expr_to_value(*expr)?;
17287 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17288 }
17289 Expr::Unary {
17290 op: UnOp::Neg,
17291 expr,
17292 } => match *expr {
17293 Expr::Literal(Literal::Integer(n)) => {
17294 let neg = n.checked_neg().ok_or_else(|| {
17297 EngineError::Unsupported("integer literal overflow on negation".into())
17298 })?;
17299 Ok(int_value_for(neg))
17300 }
17301 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17302 other => Err(EngineError::Unsupported(alloc::format!(
17303 "unary minus over non-literal expression: {other:?}"
17304 ))),
17305 },
17306 Expr::Array(items) => {
17314 let mut materialised: alloc::vec::Vec<Value> =
17315 alloc::vec::Vec::with_capacity(items.len());
17316 for elem in items {
17317 materialised.push(literal_expr_to_value(elem)?);
17318 }
17319 Ok(array_literal_widen(materialised))
17320 }
17321 other => {
17334 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17335 let ctx = EvalContext::new(&empty_schema, None);
17336 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17337 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17338 }
17339 }
17340}
17341
17342fn literal_to_value(l: Literal) -> Value {
17343 match l {
17344 Literal::Integer(n) => int_value_for(n),
17345 Literal::Float(x) => Value::Float(x),
17346 Literal::String(s) => Value::Text(s),
17347 Literal::Bool(b) => Value::Bool(b),
17348 Literal::Null => Value::Null,
17349 Literal::Vector(v) => Value::Vector(v),
17350 Literal::TextArray(items) => Value::TextArray(items),
17351 Literal::IntArray(items) => Value::IntArray(items),
17352 Literal::BigIntArray(items) => Value::BigIntArray(items),
17353 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17354 }
17355}
17356
17357fn int_value_for(n: i64) -> Value {
17361 if let Ok(small) = i32::try_from(n) {
17362 Value::Int(small)
17363 } else {
17364 Value::BigInt(n)
17365 }
17366}
17367
17368#[allow(clippy::too_many_lines)]
17374fn check_unsigned_range(
17379 v: &Value,
17380 schema: &ColumnSchema,
17381 position: usize,
17382) -> Result<(), EngineError> {
17383 if !schema.is_unsigned {
17384 return Ok(());
17385 }
17386 let n = match v {
17387 Value::SmallInt(x) => i64::from(*x),
17388 Value::Int(x) => i64::from(*x),
17389 Value::BigInt(x) => *x,
17390 _ => return Ok(()), };
17392 if n < 0 {
17393 return Err(EngineError::Unsupported(alloc::format!(
17394 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17395 schema.name
17396 )));
17397 }
17398 Ok(())
17399}
17400
17401fn coerce_value(
17402 v: Value,
17403 expected: DataType,
17404 col_name: &str,
17405 position: usize,
17406) -> Result<Value, EngineError> {
17407 if v.is_null() {
17408 return Ok(Value::Null);
17409 }
17410 let actual = v.data_type().expect("non-null");
17411 if actual == expected {
17412 return Ok(v);
17413 }
17414 let coerced = match (v, expected) {
17415 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17416 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17417 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17418 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17419 i128::from(n),
17420 precision,
17421 scale,
17422 col_name,
17423 )?),
17424 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17425 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17426 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17427 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17428 i128::from(n),
17429 precision,
17430 scale,
17431 col_name,
17432 )?),
17433 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17434 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17435 #[allow(clippy::cast_precision_loss)]
17436 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17437 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17438 i128::from(n),
17439 precision,
17440 scale,
17441 col_name,
17442 )?),
17443 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17444 Some(numeric_from_float(x, precision, scale, col_name)?)
17445 }
17446 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17457 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17458 return Err(EngineError::Eval(EvalError::TypeMismatch {
17459 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17460 }));
17461 };
17462 Some(numeric_rescale(
17463 mantissa, src_scale, precision, scale, col_name,
17464 )?)
17465 }
17466 (Value::Text(s), DataType::Date) => {
17468 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17469 EngineError::Eval(EvalError::TypeMismatch {
17470 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17471 })
17472 })?;
17473 Some(Value::Date(d))
17474 }
17475 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17482 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17483 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17484 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17485 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17486 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17487 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17488 _ => None,
17489 },
17490 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17499 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17500 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17501 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17505 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17506 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17514 (Value::Text(s), DataType::Bytes) => {
17521 let bytes = decode_bytea_literal(&s).map_err(|e| {
17522 EngineError::Eval(EvalError::TypeMismatch {
17523 detail: alloc::format!(
17524 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17525 ),
17526 })
17527 })?;
17528 Some(Value::Bytes(bytes))
17529 }
17530 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17534 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17542 Some(b) => Some(Value::Uuid(b)),
17543 None => {
17544 return Err(EngineError::Eval(EvalError::TypeMismatch {
17545 detail: alloc::format!(
17546 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17547 ),
17548 }));
17549 }
17550 },
17551 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17556 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17562 Some(us) => Some(Value::Time(us)),
17563 None => {
17564 return Err(EngineError::Eval(EvalError::TypeMismatch {
17565 detail: alloc::format!(
17566 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17567 ),
17568 }));
17569 }
17570 },
17571 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17573 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17578 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17579 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17580 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17584 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17585 Err(_) => {
17586 return Err(EngineError::Eval(EvalError::TypeMismatch {
17587 detail: alloc::format!(
17588 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17589 ),
17590 }));
17591 }
17592 },
17593 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17595 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17599 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17600 None => {
17601 return Err(EngineError::Eval(EvalError::TypeMismatch {
17602 detail: alloc::format!(
17603 "invalid input syntax for type time with time zone: \
17604 {s:?} (column `{col_name}`)"
17605 ),
17606 }));
17607 }
17608 },
17609 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17611 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17612 }
17613 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17617 Some(c) => Some(Value::Money(c)),
17618 None => {
17619 return Err(EngineError::Eval(EvalError::TypeMismatch {
17620 detail: alloc::format!(
17621 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17622 ),
17623 }));
17624 }
17625 },
17626 (Value::SmallInt(n), DataType::Money) => {
17630 Some(Value::Money(i64::from(n).saturating_mul(100)))
17631 }
17632 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17633 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17634 (Value::Float(x), DataType::Money) => {
17635 let scaled = x * 100.0;
17638 let cents = if scaled >= 0.0 {
17639 (scaled + 0.5) as i64
17640 } else {
17641 (scaled - 0.5) as i64
17642 };
17643 Some(Value::Money(cents))
17644 }
17645 (Value::Numeric { scaled, scale }, DataType::Money) => {
17646 let cents = if scale == 2 {
17649 scaled
17650 } else if scale < 2 {
17651 let mult = 10_i128.pow(u32::from(2 - scale));
17652 scaled.saturating_mul(mult)
17653 } else {
17654 let div = 10_i128.pow(u32::from(scale - 2));
17655 let half = div / 2;
17656 let bias = if scaled >= 0 { half } else { -half };
17657 (scaled + bias) / div
17658 };
17659 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17660 }
17661 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17663 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17667 Some(v) => Some(v),
17668 None => {
17669 return Err(EngineError::Eval(EvalError::TypeMismatch {
17670 detail: alloc::format!(
17671 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17672 ),
17673 }));
17674 }
17675 },
17676 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17678 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17680 Some(pairs) => Some(Value::Hstore(pairs)),
17681 None => {
17682 return Err(EngineError::Eval(EvalError::TypeMismatch {
17683 detail: alloc::format!(
17684 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17685 ),
17686 }));
17687 }
17688 },
17689 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17691 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17694 Ok(m) => Some(Value::IntArray2D(m)),
17695 Err(e) => {
17696 return Err(EngineError::Eval(EvalError::TypeMismatch {
17697 detail: alloc::format!(
17698 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17699 ),
17700 }));
17701 }
17702 },
17703 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17704 Ok(m) => Some(Value::BigIntArray2D(m)),
17705 Err(e) => {
17706 return Err(EngineError::Eval(EvalError::TypeMismatch {
17707 detail: alloc::format!(
17708 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17709 ),
17710 }));
17711 }
17712 },
17713 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17714 Ok(m) => Some(Value::TextArray2D(m)),
17715 Err(e) => {
17716 return Err(EngineError::Eval(EvalError::TypeMismatch {
17717 detail: alloc::format!(
17718 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17719 ),
17720 }));
17721 }
17722 },
17723 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17725 (Value::BigIntArray2D(rows), DataType::Text) => {
17726 Some(Value::Text(format_bigint_2d_text(&rows)))
17727 }
17728 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17729 (Value::Text(s), DataType::TextArray) => {
17734 let arr = decode_text_array_literal(&s).map_err(|e| {
17735 EngineError::Eval(EvalError::TypeMismatch {
17736 detail: alloc::format!(
17737 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17738 ),
17739 })
17740 })?;
17741 Some(Value::TextArray(arr))
17742 }
17743 (Value::Text(s), DataType::IntArray) => {
17749 let arr = decode_text_array_literal(&s).map_err(|e| {
17750 EngineError::Eval(EvalError::TypeMismatch {
17751 detail: alloc::format!(
17752 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17753 ),
17754 })
17755 })?;
17756 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17757 for elem in arr {
17758 match elem {
17759 None => out.push(None),
17760 Some(t) => {
17761 let n: i32 = t.parse().map_err(|_| {
17762 EngineError::Eval(EvalError::TypeMismatch {
17763 detail: alloc::format!(
17764 "cannot parse {t:?} as INT element for `{col_name}`"
17765 ),
17766 })
17767 })?;
17768 out.push(Some(n));
17769 }
17770 }
17771 }
17772 Some(Value::IntArray(out))
17773 }
17774 (Value::Text(s), DataType::BigIntArray) => {
17775 let arr = decode_text_array_literal(&s).map_err(|e| {
17776 EngineError::Eval(EvalError::TypeMismatch {
17777 detail: alloc::format!(
17778 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17779 ),
17780 })
17781 })?;
17782 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17783 for elem in arr {
17784 match elem {
17785 None => out.push(None),
17786 Some(t) => {
17787 let n: i64 = t.parse().map_err(|_| {
17788 EngineError::Eval(EvalError::TypeMismatch {
17789 detail: alloc::format!(
17790 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17791 ),
17792 })
17793 })?;
17794 out.push(Some(n));
17795 }
17796 }
17797 }
17798 Some(Value::BigIntArray(out))
17799 }
17800 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17804 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17813 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17814 EngineError::Eval(EvalError::TypeMismatch {
17815 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17816 })
17817 })?;
17818 if parsed.len() != dim as usize {
17819 return Err(EngineError::Eval(EvalError::TypeMismatch {
17820 detail: alloc::format!(
17821 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17822 parsed.len()
17823 ),
17824 }));
17825 }
17826 Some(match encoding {
17827 VecEncoding::F32 => Value::Vector(parsed),
17828 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17829 VecEncoding::F16 => {
17830 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17831 }
17832 })
17833 }
17834 (Value::Text(s), DataType::TsVector) => {
17844 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17845 EngineError::Eval(EvalError::TypeMismatch {
17846 detail: alloc::format!(
17847 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17848 ),
17849 })
17850 })?;
17851 Some(Value::TsVector(lexs))
17852 }
17853 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17854 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17855 EngineError::Eval(EvalError::TypeMismatch {
17856 detail: alloc::format!(
17857 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17858 ),
17859 })
17860 })?;
17861 Some(Value::Timestamp(t))
17862 }
17863 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17866 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17867 }
17868 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17872 (Value::Timestamp(t), DataType::Date) => {
17873 let days = t.div_euclid(86_400_000_000);
17874 i32::try_from(days).ok().map(Value::Date)
17875 }
17876 (
17877 Value::Numeric {
17878 scaled,
17879 scale: src_scale,
17880 },
17881 DataType::Numeric { precision, scale },
17882 ) => Some(numeric_rescale(
17883 scaled, src_scale, precision, scale, col_name,
17884 )?),
17885 #[allow(clippy::cast_precision_loss)]
17886 (Value::Numeric { scaled, scale }, DataType::Float) => {
17887 let mut div = 1.0_f64;
17888 for _ in 0..scale {
17889 div *= 10.0;
17890 }
17891 Some(Value::Float((scaled as f64) / div))
17892 }
17893 (Value::Numeric { scaled, scale }, DataType::Int) => {
17894 let truncated = numeric_truncate_to_integer(scaled, scale);
17895 i32::try_from(truncated).ok().map(Value::Int)
17896 }
17897 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17898 let truncated = numeric_truncate_to_integer(scaled, scale);
17899 i64::try_from(truncated).ok().map(Value::BigInt)
17900 }
17901 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17902 let truncated = numeric_truncate_to_integer(scaled, scale);
17903 i16::try_from(truncated).ok().map(Value::SmallInt)
17904 }
17905 (Value::Text(s), DataType::Varchar(max)) => {
17907 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17908 Some(Value::Text(s))
17909 } else {
17910 return Err(EngineError::Unsupported(alloc::format!(
17911 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17912 {} chars",
17913 s.chars().count()
17914 )));
17915 }
17916 }
17917 (
17925 Value::Vector(v),
17926 DataType::Vector {
17927 dim,
17928 encoding: VecEncoding::Sq8,
17929 },
17930 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17931 (
17936 Value::Vector(v),
17937 DataType::Vector {
17938 dim,
17939 encoding: VecEncoding::F16,
17940 },
17941 ) if v.len() == dim as usize => Some(Value::HalfVector(
17942 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17943 )),
17944 (Value::Text(s), DataType::Char(size)) => {
17948 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17949 if len > size {
17950 return Err(EngineError::Unsupported(alloc::format!(
17951 "value for CHAR({size}) column `{col_name}` exceeds length: \
17952 {len} chars"
17953 )));
17954 }
17955 let need = (size - len) as usize;
17956 let mut padded = s;
17957 padded.reserve(need);
17958 for _ in 0..need {
17959 padded.push(' ');
17960 }
17961 Some(Value::Text(padded))
17962 }
17963 _ => None,
17964 };
17965 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17966 column: col_name.into(),
17967 expected,
17968 actual,
17969 position,
17970 }))
17971}
17972
17973fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17979 use core::fmt::Write;
17980 let mut out = alloc::string::String::from("(");
17981 for (i, a) in args.iter().enumerate() {
17982 if i > 0 {
17983 out.push_str(", ");
17984 }
17985 match a.mode {
17986 spg_sql::ast::FunctionArgMode::In => {}
17987 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17988 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17989 }
17990 if let Some(n) = &a.name {
17991 out.push_str(n);
17992 out.push(' ');
17993 }
17994 match &a.ty {
17995 spg_sql::ast::FunctionArgType::Typed(t) => {
17996 let _ = write!(out, "{t}");
17997 }
17998 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17999 }
18000 }
18001 out.push(')');
18002 out
18003}
18004
18005fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
18014 match expr {
18015 spg_sql::ast::Expr::FunctionCall { name, args } => {
18016 name.eq_ignore_ascii_case("unnest") && args.len() == 1
18017 }
18018 _ => false,
18019 }
18020}
18021
18022fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
18026 match expr {
18027 spg_sql::ast::Expr::FunctionCall { name, args }
18028 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
18029 {
18030 Some(&args[0])
18031 }
18032 _ => None,
18033 }
18034}
18035
18036fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
18041 match v {
18042 Value::Null => Ok(Vec::new()),
18043 Value::TextArray(items) => Ok(items
18044 .iter()
18045 .map(|opt| {
18046 opt.as_ref()
18047 .map(|s| Value::Text(s.clone()))
18048 .unwrap_or(Value::Null)
18049 })
18050 .collect()),
18051 Value::IntArray(items) => Ok(items
18052 .iter()
18053 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
18054 .collect()),
18055 Value::BigIntArray(items) => Ok(items
18056 .iter()
18057 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
18058 .collect()),
18059 other => Err(EngineError::Eval(EvalError::TypeMismatch {
18060 detail: alloc::format!(
18061 "unnest() expects an array argument, got {:?}",
18062 other.data_type()
18063 ),
18064 })),
18065 }
18066}
18067
18068#[cfg(test)]
18069mod tests {
18070 use super::*;
18071 use alloc::vec;
18072
18073 fn unwrap_command_ok(r: &QueryResult) -> usize {
18074 match r {
18075 QueryResult::CommandOk { affected, .. } => *affected,
18076 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
18077 }
18078 }
18079
18080 #[test]
18081 fn update_seek_positions_engages_on_indexed_eq() {
18082 let mut e = Engine::new();
18083 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
18084 .unwrap();
18085 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
18086 for i in 0..100 {
18087 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
18088 .unwrap();
18089 }
18090 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
18091 .expect("parse");
18092 let Statement::Update(u) = stmt else {
18093 panic!("expected Update, got {stmt:?}");
18094 };
18095 let w = u.where_.as_ref().expect("where");
18096 let table = e.catalog().get("b").unwrap();
18097 let schema_cols = table.schema().columns.clone();
18098 let Expr::Binary { lhs, op, rhs } = w else {
18100 panic!("WHERE not Binary: {w:?}");
18101 };
18102 assert_eq!(*op, BinOp::Eq, "op not Eq");
18103 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
18104 assert!(
18105 pair.is_some(),
18106 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
18107 );
18108 let (col_pos, value) = pair.unwrap();
18109 assert!(
18110 table.index_on(col_pos).is_some(),
18111 "no index on col {col_pos}"
18112 );
18113 assert!(
18114 IndexKey::from_value(&value).is_some(),
18115 "IndexKey::from_value None for {value:?}"
18116 );
18117 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
18118 assert_eq!(positions, Some(vec![42]), "seek did not engage");
18119 }
18120
18121 #[test]
18122 fn create_table_registers_schema() {
18123 let mut e = Engine::new();
18124 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
18125 .unwrap();
18126 assert_eq!(e.catalog().table_count(), 1);
18127 let t = e.catalog().get("foo").unwrap();
18128 assert_eq!(t.schema().columns.len(), 2);
18129 assert_eq!(t.schema().columns[0].ty, DataType::Int);
18130 assert!(!t.schema().columns[0].nullable);
18131 assert_eq!(t.schema().columns[1].ty, DataType::Text);
18132 }
18133
18134 #[test]
18135 fn create_table_vector_default_is_f32_encoded() {
18136 let mut e = Engine::new();
18137 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
18138 let t = e.catalog().get("t").unwrap();
18139 assert_eq!(
18140 t.schema().columns[0].ty,
18141 DataType::Vector {
18142 dim: 8,
18143 encoding: VecEncoding::F32,
18144 },
18145 );
18146 }
18147
18148 #[test]
18149 fn create_table_vector_using_sq8_succeeds() {
18150 let mut e = Engine::new();
18154 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
18155 let t = e.catalog().get("t").unwrap();
18156 assert_eq!(
18157 t.schema().columns[0].ty,
18158 DataType::Vector {
18159 dim: 8,
18160 encoding: VecEncoding::Sq8,
18161 },
18162 );
18163 }
18164
18165 #[test]
18166 fn insert_into_sq8_column_quantises_f32_payload() {
18167 let mut e = Engine::new();
18174 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18175 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18176 .unwrap();
18177 let t = e.catalog().get("t").unwrap();
18178 assert_eq!(t.rows().len(), 1);
18179 match &t.rows()[0].values[0] {
18180 Value::Sq8Vector(q) => {
18181 assert_eq!(q.bytes.len(), 4);
18182 assert!((q.min - 0.0).abs() < 1e-6);
18184 assert!((q.max - 1.0).abs() < 1e-6);
18185 }
18186 other => panic!("expected Sq8Vector cell, got {other:?}"),
18187 }
18188 }
18189
18190 #[test]
18191 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
18192 let mut e = Engine::new();
18199 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18200 .unwrap();
18201 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
18202 .unwrap();
18203 let t = e.catalog().get("t").unwrap();
18204 assert_eq!(t.rows().len(), 1);
18205 match &t.rows()[0].values[0] {
18206 Value::HalfVector(h) => {
18207 assert_eq!(h.dim(), 4);
18208 let back = h.to_f32_vec();
18209 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
18210 for (g, e) in back.iter().zip(expected.iter()) {
18211 assert!(
18212 (g - e).abs() < 1e-6,
18213 "{g} vs {e} should be exact on f16 grid"
18214 );
18215 }
18216 }
18217 other => panic!("expected HalfVector cell, got {other:?}"),
18218 }
18219 }
18220
18221 #[test]
18222 fn alter_index_rebuild_in_place_succeeds() {
18223 let mut e = Engine::new();
18228 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18229 .unwrap();
18230 for i in 0..8_i32 {
18231 #[allow(clippy::cast_precision_loss)]
18232 let base = (i as f32) * 0.1;
18233 e.execute(&alloc::format!(
18234 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
18235 b1 = base + 0.01,
18236 b2 = base + 0.02,
18237 ))
18238 .unwrap();
18239 }
18240 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18241 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18242 assert_eq!(
18244 e.catalog().get("t").unwrap().schema().columns[1].ty,
18245 DataType::Vector {
18246 dim: 3,
18247 encoding: VecEncoding::F32,
18248 },
18249 );
18250 }
18251
18252 #[test]
18253 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18254 let mut e = Engine::new();
18259 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18260 .unwrap();
18261 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18262 .unwrap();
18263 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18264 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18265 .unwrap();
18266 let t = e.catalog().get("t").unwrap();
18267 assert_eq!(
18268 t.schema().columns[1].ty,
18269 DataType::Vector {
18270 dim: 4,
18271 encoding: VecEncoding::Sq8,
18272 },
18273 );
18274 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18275 }
18276
18277 #[test]
18278 fn alter_index_rebuild_unknown_index_errors() {
18279 let mut e = Engine::new();
18280 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18281 assert!(
18282 matches!(
18283 &err,
18284 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18285 ),
18286 "got: {err}"
18287 );
18288 }
18289
18290 #[test]
18291 fn alter_index_rebuild_on_btree_index_errors() {
18292 let mut e = Engine::new();
18295 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18296 e.execute("INSERT INTO t VALUES (1)").unwrap();
18297 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18298 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18299 assert!(
18300 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18301 "got: {err}"
18302 );
18303 }
18304
18305 #[test]
18306 fn prepared_insert_substitutes_placeholders() {
18307 let mut e = Engine::new();
18313 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18314 .unwrap();
18315 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18316 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18317 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18318 .unwrap();
18319 }
18320 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18322 let QueryResult::Rows { rows, .. } = rows_result else {
18323 panic!("expected Rows")
18324 };
18325 assert_eq!(rows.len(), 3);
18326 }
18327
18328 #[test]
18329 fn prepared_select_with_placeholder_filters_rows() {
18330 let mut e = Engine::new();
18331 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18332 .unwrap();
18333 for i in 0..10_i32 {
18334 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18335 .unwrap();
18336 }
18337 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18338 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18339 else {
18340 panic!("expected Rows")
18341 };
18342 assert_eq!(rows.len(), 1);
18344 assert_eq!(rows[0].values[0], Value::Int(5));
18345 }
18346
18347 #[test]
18348 fn prepared_too_few_params_errors() {
18349 let mut e = Engine::new();
18350 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18351 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18352 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18353 assert!(
18354 matches!(
18355 &err,
18356 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18357 ),
18358 "got: {err}"
18359 );
18360 }
18361
18362 #[test]
18363 fn bytea_cast_round_trips_text_input() {
18364 let e = Engine::new();
18367 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18368 let QueryResult::Rows { rows, .. } = r else {
18369 panic!("expected Rows")
18370 };
18371 assert_eq!(rows.len(), 1);
18372 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18373 }
18374
18375 #[test]
18376 fn bytea_cast_pg_escape_hex_form() {
18377 let e = Engine::new();
18381 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18382 let QueryResult::Rows { rows, .. } = r else {
18383 panic!("expected Rows")
18384 };
18385 assert_eq!(
18386 rows[0].values[0],
18387 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18388 );
18389 }
18390
18391 #[test]
18392 fn bytea_cast_chains_through_octet_length() {
18393 let e = Engine::new();
18397 let r = e
18398 .execute_readonly("SELECT octet_length('hello'::bytea)")
18399 .unwrap();
18400 let QueryResult::Rows { rows, .. } = r else {
18401 panic!("expected Rows")
18402 };
18403 match &rows[0].values[0] {
18404 Value::Int(n) => assert_eq!(*n, 5),
18405 Value::BigInt(n) => assert_eq!(*n, 5),
18406 other => panic!("expected integer length, got {other:?}"),
18407 }
18408 }
18409
18410 #[test]
18411 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18412 let mut e = Engine::new();
18418 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18419 .unwrap();
18420 for i in 0..10_i32 {
18421 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18422 .unwrap();
18423 }
18424 let snapshot = e.clone_snapshot();
18425 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18426 let QueryResult::Rows { rows, .. } =
18427 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18428 .unwrap()
18429 else {
18430 panic!("expected Rows")
18431 };
18432 assert_eq!(rows.len(), 1);
18433 assert_eq!(rows[0].values[0], Value::Int(5));
18434 }
18435
18436 #[test]
18437 fn readonly_prepared_on_snapshot_rejects_writes() {
18438 let mut e = Engine::new();
18442 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18443 let snapshot = e.clone_snapshot();
18444 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18445 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18446 .unwrap_err();
18447 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18448 }
18449
18450 #[test]
18451 fn readonly_prepared_on_snapshot_frozen_view() {
18452 let mut e = Engine::new();
18458 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18459 e.execute("INSERT INTO t VALUES (1)").unwrap();
18460 let snapshot = e.clone_snapshot();
18461 e.execute("INSERT INTO t VALUES (2)").unwrap();
18462 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18463 let QueryResult::Rows { rows, .. } =
18464 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18465 .unwrap()
18466 else {
18467 panic!("expected Rows")
18468 };
18469 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18470 }
18471
18472 #[test]
18473 fn describe_prepared_on_snapshot_resolves_columns() {
18474 let mut e = Engine::new();
18479 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18480 .unwrap();
18481 let snapshot = e.clone_snapshot();
18482 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18483 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18484 assert_eq!(cols.len(), 2);
18485 assert_eq!(cols[0].name, "id");
18486 assert_eq!(cols[0].ty, DataType::Int);
18487 assert_eq!(cols[1].name, "name");
18488 assert_eq!(cols[1].ty, DataType::Text);
18489 }
18490
18491 #[test]
18492 fn insert_into_half_column_dim_mismatch_errors() {
18493 let mut e = Engine::new();
18494 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18495 .unwrap();
18496 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18497 assert!(matches!(
18498 &err,
18499 EngineError::Storage(StorageError::TypeMismatch { .. })
18500 ));
18501 }
18502
18503 #[test]
18504 fn insert_into_sq8_column_dim_mismatch_errors() {
18505 let mut e = Engine::new();
18510 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18511 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18512 assert!(
18513 matches!(
18514 &err,
18515 EngineError::Storage(StorageError::TypeMismatch { .. })
18516 ),
18517 "got: {err}",
18518 );
18519 }
18520
18521 #[test]
18522 fn create_table_duplicate_errors() {
18523 let mut e = Engine::new();
18524 e.execute("CREATE TABLE foo (a INT)").unwrap();
18525 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18526 assert!(matches!(
18527 err,
18528 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18529 ));
18530 }
18531
18532 #[test]
18533 fn insert_into_unknown_table_errors() {
18534 let mut e = Engine::new();
18535 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18536 assert!(matches!(
18537 err,
18538 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18539 ));
18540 }
18541
18542 #[test]
18543 fn insert_happy_path_reports_one_affected() {
18544 let mut e = Engine::new();
18545 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18546 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18547 assert_eq!(unwrap_command_ok(&r), 1);
18548 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18549 }
18550
18551 #[test]
18552 fn insert_arity_mismatch_propagates() {
18553 let mut e = Engine::new();
18554 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18555 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18556 assert!(matches!(
18557 err,
18558 EngineError::Storage(StorageError::ArityMismatch { .. })
18559 ));
18560 }
18561
18562 #[test]
18563 fn insert_negative_integer_via_unary_minus() {
18564 let mut e = Engine::new();
18565 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18566 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18567 let rows = e.catalog().get("foo").unwrap().rows();
18568 assert_eq!(rows[0].values[0], Value::Int(-7));
18569 }
18570
18571 #[test]
18572 fn insert_expression_evaluated_against_empty_context() {
18573 let mut e = Engine::new();
18578 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18579 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18580 let rows = e.catalog().get("foo").unwrap().rows();
18581 assert_eq!(rows[0].values[0], Value::Int(3));
18582 }
18583
18584 #[test]
18585 fn select_star_returns_all_rows_in_insertion_order() {
18586 let mut e = Engine::new();
18587 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18588 .unwrap();
18589 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18590 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18591 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18592
18593 let r = e.execute("SELECT * FROM foo").unwrap();
18594 let QueryResult::Rows { columns, rows } = r else {
18595 panic!("expected Rows")
18596 };
18597 assert_eq!(columns.len(), 2);
18598 assert_eq!(columns[0].name, "a");
18599 assert_eq!(rows.len(), 3);
18600 assert_eq!(
18601 rows[1].values,
18602 vec![Value::Int(2), Value::Text("two".into())]
18603 );
18604 }
18605
18606 #[test]
18607 fn select_star_on_empty_table_returns_zero_rows() {
18608 let mut e = Engine::new();
18609 e.execute("CREATE TABLE foo (a INT)").unwrap();
18610 let r = e.execute("SELECT * FROM foo").unwrap();
18611 match r {
18612 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18613 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18614 }
18615 }
18616
18617 fn make_three_row_users(e: &mut Engine) {
18620 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18621 .unwrap();
18622 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18623 .unwrap();
18624 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18625 .unwrap();
18626 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18627 .unwrap();
18628 }
18629
18630 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18631 match r {
18632 QueryResult::Rows { columns, rows } => (columns, rows),
18633 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18634 }
18635 }
18636
18637 #[test]
18638 fn where_filter_passes_only_true_rows() {
18639 let mut e = Engine::new();
18640 make_three_row_users(&mut e);
18641 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18642 let (_, rows) = unwrap_rows(r);
18643 assert_eq!(rows.len(), 2);
18644 assert_eq!(rows[0].values[0], Value::Int(2));
18645 assert_eq!(rows[1].values[0], Value::Int(3));
18646 }
18647
18648 #[test]
18649 fn where_with_null_result_filters_out_row() {
18650 let mut e = Engine::new();
18651 make_three_row_users(&mut e);
18652 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18654 let (_, rows) = unwrap_rows(r);
18655 assert_eq!(rows.len(), 1);
18656 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18657 }
18658
18659 #[test]
18660 fn projection_named_columns() {
18661 let mut e = Engine::new();
18662 make_three_row_users(&mut e);
18663 let r = e.execute("SELECT name, score FROM users").unwrap();
18664 let (cols, rows) = unwrap_rows(r);
18665 assert_eq!(cols.len(), 2);
18666 assert_eq!(cols[0].name, "name");
18667 assert_eq!(cols[1].name, "score");
18668 assert_eq!(rows.len(), 3);
18669 assert_eq!(
18670 rows[0].values,
18671 vec![Value::Text("alice".into()), Value::Int(90)]
18672 );
18673 }
18674
18675 #[test]
18676 fn projection_with_column_alias() {
18677 let mut e = Engine::new();
18678 make_three_row_users(&mut e);
18679 let r = e
18680 .execute("SELECT name AS who FROM users WHERE id = 1")
18681 .unwrap();
18682 let (cols, rows) = unwrap_rows(r);
18683 assert_eq!(cols[0].name, "who");
18684 assert_eq!(rows.len(), 1);
18685 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18686 }
18687
18688 #[test]
18689 fn qualified_column_with_table_alias_resolves() {
18690 let mut e = Engine::new();
18691 make_three_row_users(&mut e);
18692 let r = e
18693 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18694 .unwrap();
18695 let (cols, rows) = unwrap_rows(r);
18696 assert_eq!(cols.len(), 2);
18697 assert_eq!(rows.len(), 2);
18698 }
18699
18700 #[test]
18701 fn qualified_column_with_wrong_alias_errors() {
18702 let mut e = Engine::new();
18703 make_three_row_users(&mut e);
18704 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18705 assert!(matches!(
18706 err,
18707 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18708 ));
18709 }
18710
18711 #[test]
18712 fn select_unknown_column_errors_in_projection() {
18713 let mut e = Engine::new();
18714 make_three_row_users(&mut e);
18715 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18716 assert!(matches!(
18717 err,
18718 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18719 ));
18720 }
18721
18722 #[test]
18723 fn where_unknown_column_errors() {
18724 let mut e = Engine::new();
18725 make_three_row_users(&mut e);
18726 let err = e
18727 .execute("SELECT * FROM users WHERE ghost = 1")
18728 .unwrap_err();
18729 assert!(matches!(
18730 err,
18731 EngineError::Eval(EvalError::ColumnNotFound { .. })
18732 ));
18733 }
18734
18735 #[test]
18736 fn expression_projection_evaluates_and_renders() {
18737 let mut e = Engine::new();
18740 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18741 e.execute("INSERT INTO t VALUES (3)").unwrap();
18742 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18743 assert_eq!(rows.len(), 1);
18744 assert_eq!(rows[0].values[0], Value::Int(3));
18747 }
18748
18749 #[test]
18750 fn select_unknown_table_errors() {
18751 let mut e = Engine::new();
18752 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18753 assert!(matches!(
18754 err,
18755 EngineError::Storage(StorageError::TableNotFound { .. })
18756 ));
18757 }
18758
18759 #[test]
18760 fn invalid_sql_returns_parse_error() {
18761 let mut e = Engine::new();
18764 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18765 assert!(matches!(err, EngineError::Parse(_)));
18766 }
18767
18768 #[test]
18771 fn create_index_registers_on_table() {
18772 let mut e = Engine::new();
18773 make_three_row_users(&mut e);
18774 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18775 let t = e.catalog().get("users").unwrap();
18776 assert_eq!(t.indices().len(), 1);
18777 assert_eq!(t.indices()[0].name, "by_name");
18778 }
18779
18780 #[test]
18781 fn create_index_on_unknown_table_errors() {
18782 let mut e = Engine::new();
18783 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18784 assert!(matches!(
18785 err,
18786 EngineError::Storage(StorageError::TableNotFound { .. })
18787 ));
18788 }
18789
18790 #[test]
18791 fn create_index_on_unknown_column_errors() {
18792 let mut e = Engine::new();
18793 make_three_row_users(&mut e);
18794 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18795 assert!(matches!(
18796 err,
18797 EngineError::Storage(StorageError::ColumnNotFound { .. })
18798 ));
18799 }
18800
18801 #[test]
18802 fn select_eq_uses_index_returns_same_rows_as_scan() {
18803 let mut without = Engine::new();
18807 make_three_row_users(&mut without);
18808 let mut with = Engine::new();
18809 make_three_row_users(&mut with);
18810 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18811
18812 let q = "SELECT * FROM users WHERE id = 2";
18813 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18814 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18815 assert_eq!(no_idx_rows, idx_rows);
18816 assert_eq!(idx_rows.len(), 1);
18817 }
18818
18819 #[test]
18820 fn select_eq_with_no_matching_index_value_returns_empty() {
18821 let mut e = Engine::new();
18822 make_three_row_users(&mut e);
18823 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18824 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18825 assert_eq!(rows.len(), 0);
18826 }
18827
18828 #[test]
18831 fn begin_sets_in_transaction_flag() {
18832 let mut e = Engine::new();
18833 assert!(!e.in_transaction());
18834 e.execute("BEGIN").unwrap();
18835 assert!(e.in_transaction());
18836 }
18837
18838 #[test]
18839 fn double_begin_errors() {
18840 let mut e = Engine::new();
18841 e.execute("BEGIN").unwrap();
18842 let err = e.execute("BEGIN").unwrap_err();
18843 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18844 }
18845
18846 #[test]
18847 fn commit_without_begin_errors() {
18848 let mut e = Engine::new();
18849 let err = e.execute("COMMIT").unwrap_err();
18850 assert_eq!(err, EngineError::NoActiveTransaction);
18851 }
18852
18853 #[test]
18854 fn rollback_without_begin_errors() {
18855 let mut e = Engine::new();
18856 let err = e.execute("ROLLBACK").unwrap_err();
18857 assert_eq!(err, EngineError::NoActiveTransaction);
18858 }
18859
18860 #[test]
18861 fn commit_applies_shadow_to_committed_catalog() {
18862 let mut e = Engine::new();
18863 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18864 e.execute("BEGIN").unwrap();
18865 e.execute("INSERT INTO t VALUES (1)").unwrap();
18866 e.execute("INSERT INTO t VALUES (2)").unwrap();
18867 e.execute("COMMIT").unwrap();
18868 assert!(!e.in_transaction());
18869 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18870 }
18871
18872 #[test]
18873 fn rollback_discards_shadow() {
18874 let mut e = Engine::new();
18875 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18876 e.execute("BEGIN").unwrap();
18877 e.execute("INSERT INTO t VALUES (1)").unwrap();
18878 e.execute("INSERT INTO t VALUES (2)").unwrap();
18879 e.execute("ROLLBACK").unwrap();
18880 assert!(!e.in_transaction());
18881 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18882 }
18883
18884 #[test]
18885 fn select_during_tx_sees_uncommitted_writes_own_session() {
18886 let mut e = Engine::new();
18889 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18890 e.execute("BEGIN").unwrap();
18891 e.execute("INSERT INTO t VALUES (42)").unwrap();
18892 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18893 assert_eq!(rows.len(), 1);
18894 assert_eq!(rows[0].values[0], Value::Int(42));
18895 }
18896
18897 #[test]
18898 fn snapshot_with_no_users_is_bare_catalog_format() {
18899 let mut e = Engine::new();
18900 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18901 let bytes = e.snapshot();
18902 assert_eq!(
18903 &bytes[..8],
18904 b"SPGDB001",
18905 "must be the bare v3.x catalog magic"
18906 );
18907 let e2 = Engine::restore_envelope(&bytes).unwrap();
18908 assert!(e2.users().is_empty());
18909 assert_eq!(e2.catalog().table_count(), 1);
18910 }
18911
18912 #[test]
18913 fn snapshot_with_users_round_trips_both_via_envelope() {
18914 let mut e = Engine::new();
18915 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18916 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18917 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18918 .unwrap();
18919 let bytes = e.snapshot();
18920 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18921 let e2 = Engine::restore_envelope(&bytes).unwrap();
18922 assert_eq!(e2.users().len(), 2);
18923 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18924 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18925 assert_eq!(e2.verify_user("alice", "wrong"), None);
18926 assert_eq!(e2.catalog().table_count(), 1);
18927 }
18928
18929 #[test]
18930 fn ddl_inside_tx_also_rolled_back() {
18931 let mut e = Engine::new();
18932 e.execute("BEGIN").unwrap();
18933 e.execute("CREATE TABLE t (v INT)").unwrap();
18934 e.execute("SELECT * FROM t").unwrap();
18936 e.execute("ROLLBACK").unwrap();
18937 let err = e.execute("SELECT * FROM t").unwrap_err();
18939 assert!(matches!(
18940 err,
18941 EngineError::Storage(StorageError::TableNotFound { .. })
18942 ));
18943 }
18944
18945 #[test]
18948 fn create_publication_lands_in_catalog() {
18949 let mut e = Engine::new();
18950 assert!(e.publications().is_empty());
18951 e.execute("CREATE PUBLICATION pub_a").unwrap();
18952 assert_eq!(e.publications().len(), 1);
18953 assert!(e.publications().contains("pub_a"));
18954 }
18955
18956 #[test]
18957 fn create_publication_duplicate_errors() {
18958 let mut e = Engine::new();
18959 e.execute("CREATE PUBLICATION pub_a").unwrap();
18960 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18961 assert!(
18962 alloc::format!("{err:?}").contains("DuplicateName"),
18963 "got {err:?}"
18964 );
18965 }
18966
18967 #[test]
18968 fn drop_publication_silent_when_absent() {
18969 let mut e = Engine::new();
18970 let r = e.execute("DROP PUBLICATION nope").unwrap();
18973 match r {
18974 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18975 other => panic!("expected CommandOk, got {other:?}"),
18976 }
18977 }
18978
18979 #[test]
18980 fn drop_publication_present_reports_one_affected() {
18981 let mut e = Engine::new();
18982 e.execute("CREATE PUBLICATION pub_a").unwrap();
18983 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18984 match r {
18985 QueryResult::CommandOk {
18986 affected,
18987 modified_catalog,
18988 } => {
18989 assert_eq!(affected, 1);
18990 assert!(modified_catalog);
18991 }
18992 other => panic!("expected CommandOk, got {other:?}"),
18993 }
18994 assert!(e.publications().is_empty());
18995 }
18996
18997 #[test]
18998 fn publications_persist_across_snapshot_restore() {
18999 let mut e = Engine::new();
19004 e.execute("CREATE PUBLICATION pub_a").unwrap();
19005 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
19006 .unwrap();
19007 let snap = e.snapshot();
19008 let e2 = Engine::restore_envelope(&snap).unwrap();
19009 assert_eq!(e2.publications().len(), 2);
19010 assert!(e2.publications().contains("pub_a"));
19011 assert!(e2.publications().contains("pub_b"));
19012 }
19013
19014 #[test]
19015 fn create_publication_allowed_inside_transaction() {
19016 let mut e = Engine::new();
19020 e.execute("BEGIN").unwrap();
19021 e.execute("CREATE PUBLICATION pub_a").unwrap();
19022 e.execute("COMMIT").unwrap();
19023 assert!(e.publications().contains("pub_a"));
19024 }
19025
19026 #[test]
19029 fn create_publication_for_table_list_lands_with_scope() {
19030 let mut e = Engine::new();
19031 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19032 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
19033 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
19034 .unwrap();
19035 let scope = e.publications().get("pub_a").cloned();
19036 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
19037 panic!("expected ForTables scope, got {scope:?}")
19038 };
19039 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19040 }
19041
19042 #[test]
19043 fn create_publication_all_tables_except_lands_with_scope() {
19044 let mut e = Engine::new();
19045 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
19046 .unwrap();
19047 let scope = e.publications().get("pub_a").cloned();
19048 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
19049 panic!("expected AllTablesExcept scope, got {scope:?}")
19050 };
19051 assert_eq!(ts, alloc::vec!["t3".to_string()]);
19052 }
19053
19054 #[test]
19055 fn show_publications_empty_returns_zero_rows() {
19056 let e = Engine::new();
19057 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19058 let QueryResult::Rows { rows, columns } = r else {
19059 panic!()
19060 };
19061 assert!(rows.is_empty());
19062 assert_eq!(columns.len(), 3);
19063 assert_eq!(columns[0].name, "name");
19064 assert_eq!(columns[1].name, "scope");
19065 assert_eq!(columns[2].name, "table_count");
19066 }
19067
19068 #[test]
19069 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
19070 let mut e = Engine::new();
19071 e.execute("CREATE PUBLICATION z_pub").unwrap();
19072 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
19073 .unwrap();
19074 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
19075 .unwrap();
19076 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
19077 let QueryResult::Rows { rows, .. } = r else {
19078 panic!()
19079 };
19080 assert_eq!(rows.len(), 3);
19081 let names: Vec<&str> = rows
19083 .iter()
19084 .map(|r| {
19085 if let Value::Text(s) = &r.values[0] {
19086 s.as_str()
19087 } else {
19088 panic!()
19089 }
19090 })
19091 .collect();
19092 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
19093 match &rows[0].values[1] {
19095 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
19096 other => panic!("expected Text, got {other:?}"),
19097 }
19098 assert_eq!(rows[0].values[2], Value::Int(2));
19099 match &rows[1].values[1] {
19101 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
19102 other => panic!("expected Text, got {other:?}"),
19103 }
19104 assert_eq!(rows[1].values[2], Value::Int(1));
19105 match &rows[2].values[1] {
19107 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
19108 other => panic!("expected Text, got {other:?}"),
19109 }
19110 assert_eq!(rows[2].values[2], Value::Null);
19111 }
19112
19113 #[test]
19114 fn for_list_scopes_persist_across_snapshot() {
19115 let mut e = Engine::new();
19118 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
19119 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
19120 .unwrap();
19121 let snap = e.snapshot();
19122 let e2 = Engine::restore_envelope(&snap).unwrap();
19123 assert_eq!(e2.publications().len(), 2);
19124 let p1 = e2.publications().get("p1").cloned();
19125 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
19126 panic!("p1 scope lost: {p1:?}")
19127 };
19128 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
19129 let p2 = e2.publications().get("p2").cloned();
19130 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
19131 panic!("p2 scope lost: {p2:?}")
19132 };
19133 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
19134 }
19135
19136 #[test]
19139 fn create_subscription_lands_in_catalog_with_defaults() {
19140 let mut e = Engine::new();
19141 e.execute(
19142 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
19143 )
19144 .unwrap();
19145 let s = e.subscriptions().get("sub_a").cloned().expect("present");
19146 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
19147 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
19148 assert!(s.enabled);
19149 assert_eq!(s.last_received_pos, 0);
19150 }
19151
19152 #[test]
19153 fn create_subscription_duplicate_name_errors() {
19154 let mut e = Engine::new();
19155 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
19156 .unwrap();
19157 let err = e
19158 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
19159 .unwrap_err();
19160 assert!(
19161 alloc::format!("{err:?}").contains("DuplicateName"),
19162 "got {err:?}"
19163 );
19164 }
19165
19166 #[test]
19167 fn drop_subscription_silent_when_absent() {
19168 let mut e = Engine::new();
19169 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
19170 match r {
19171 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
19172 other => panic!("expected CommandOk, got {other:?}"),
19173 }
19174 }
19175
19176 #[test]
19177 fn subscription_advance_updates_last_pos_monotone() {
19178 let mut e = Engine::new();
19179 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19180 .unwrap();
19181 assert!(e.subscription_advance("s", 100));
19182 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19183 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
19185 assert!(e.subscription_advance("s", 200));
19186 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
19187 assert!(!e.subscription_advance("missing", 1));
19188 }
19189
19190 #[test]
19191 fn show_subscriptions_returns_rows_ordered_by_name() {
19192 let mut e = Engine::new();
19193 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
19194 .unwrap();
19195 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
19196 .unwrap();
19197 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
19198 let QueryResult::Rows { rows, columns } = r else {
19199 panic!()
19200 };
19201 assert_eq!(rows.len(), 2);
19202 assert_eq!(columns.len(), 5);
19203 assert_eq!(columns[0].name, "name");
19204 assert_eq!(columns[4].name, "last_received_pos");
19205 let names: Vec<&str> = rows
19207 .iter()
19208 .map(|r| {
19209 if let Value::Text(s) = &r.values[0] {
19210 s.as_str()
19211 } else {
19212 panic!()
19213 }
19214 })
19215 .collect();
19216 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
19217 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
19219 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
19220 assert_eq!(rows[0].values[3], Value::Bool(true));
19221 assert_eq!(rows[0].values[4], Value::BigInt(0));
19222 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
19224 }
19225
19226 #[test]
19227 fn subscriptions_persist_across_snapshot_envelope_v4() {
19228 let mut e = Engine::new();
19229 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
19230 .unwrap();
19231 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
19232 .unwrap();
19233 e.subscription_advance("s2", 42);
19234 let snap = e.snapshot();
19235 let e2 = Engine::restore_envelope(&snap).unwrap();
19236 assert_eq!(e2.subscriptions().len(), 2);
19237 let s1 = e2.subscriptions().get("s1").unwrap();
19238 assert_eq!(s1.conn_str, "h=A");
19239 assert_eq!(
19240 s1.publications,
19241 alloc::vec!["p1".to_string(), "p2".to_string()]
19242 );
19243 assert_eq!(s1.last_received_pos, 0);
19244 let s2 = e2.subscriptions().get("s2").unwrap();
19245 assert_eq!(s2.last_received_pos, 42);
19246 }
19247
19248 #[test]
19249 fn v3_envelope_loads_with_empty_subscriptions() {
19250 let mut e = Engine::new();
19254 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19255 let catalog = e.catalog.serialize();
19256 let users = crate::users::serialize_users(&e.users);
19257 let pubs = e.publications.serialize();
19258 let mut buf = Vec::new();
19259 buf.extend_from_slice(b"SPGENV01");
19260 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19262 buf.extend_from_slice(&catalog);
19263 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19264 buf.extend_from_slice(&users);
19265 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19266 buf.extend_from_slice(&pubs);
19267 let crc = spg_crypto::crc32::crc32(&buf);
19268 buf.extend_from_slice(&crc.to_le_bytes());
19269
19270 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19271 assert!(e2.subscriptions().is_empty());
19272 assert!(e2.publications().contains("pub_legacy"));
19273 }
19274
19275 #[test]
19276 fn create_subscription_allowed_inside_transaction() {
19277 let mut e = Engine::new();
19278 e.execute("BEGIN").unwrap();
19279 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19280 .unwrap();
19281 e.execute("COMMIT").unwrap();
19282 assert!(e.subscriptions().contains("s"));
19283 }
19284
19285 #[test]
19287 fn analyze_populates_histogram_bounds() {
19288 let mut e = Engine::new();
19289 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19290 .unwrap();
19291 for i in 0..50 {
19292 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19293 .unwrap();
19294 }
19295 e.execute("ANALYZE t").unwrap();
19296 let stats = e.statistics();
19297 let id_stats = stats.get("t", "id").unwrap();
19298 assert!(id_stats.histogram_bounds.len() >= 2);
19299 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19300 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19301 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19302 assert_eq!(id_stats.n_distinct, 50);
19303 }
19304
19305 #[test]
19306 fn reanalyze_overwrites_prior_stats() {
19307 let mut e = Engine::new();
19308 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19309 for i in 0..10 {
19310 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19311 .unwrap();
19312 }
19313 e.execute("ANALYZE t").unwrap();
19314 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19315 assert_eq!(n1, 10);
19316 for i in 10..30 {
19317 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19318 .unwrap();
19319 }
19320 e.execute("ANALYZE t").unwrap();
19321 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19322 assert_eq!(n2, 30);
19323 }
19324
19325 #[test]
19326 fn analyze_unknown_table_errors() {
19327 let mut e = Engine::new();
19328 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19329 assert!(matches!(
19330 err,
19331 EngineError::Storage(StorageError::TableNotFound { .. })
19332 ));
19333 }
19334
19335 #[test]
19336 fn bare_analyze_covers_all_user_tables() {
19337 let mut e = Engine::new();
19338 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19339 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19340 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19341 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19342 let r = e.execute("ANALYZE").unwrap();
19343 match r {
19344 QueryResult::CommandOk {
19345 affected,
19346 modified_catalog,
19347 } => {
19348 assert_eq!(affected, 2);
19349 assert!(modified_catalog);
19350 }
19351 other => panic!("expected CommandOk, got {other:?}"),
19352 }
19353 assert!(e.statistics().get("t1", "id").is_some());
19354 assert!(e.statistics().get("t2", "name").is_some());
19355 }
19356
19357 #[test]
19358 fn select_from_spg_statistic_returns_rows_per_column() {
19359 let mut e = Engine::new();
19360 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19361 .unwrap();
19362 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19363 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19364 e.execute("ANALYZE t").unwrap();
19365 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19366 let QueryResult::Rows { rows, columns } = r else {
19367 panic!()
19368 };
19369 assert_eq!(columns.len(), 6);
19371 assert_eq!(columns[0].name, "table_name");
19372 assert_eq!(columns[4].name, "histogram_bounds");
19373 assert_eq!(columns[5].name, "cold_row_count");
19374 assert_eq!(rows.len(), 2, "one row per column of t");
19375 match (&rows[0].values[0], &rows[0].values[1]) {
19377 (Value::Text(t), Value::Text(c)) => {
19378 assert_eq!(t, "t");
19379 assert_eq!(c, "id");
19381 }
19382 _ => panic!(),
19383 }
19384 }
19385
19386 #[test]
19387 fn analyze_skips_vector_columns() {
19388 let mut e = Engine::new();
19391 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19392 .unwrap();
19393 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19394 e.execute("ANALYZE t").unwrap();
19395 assert!(e.statistics().get("t", "id").is_some());
19396 assert!(e.statistics().get("t", "v").is_none());
19397 }
19398
19399 #[test]
19400 fn statistics_persist_across_envelope_v5_round_trip() {
19401 let mut e = Engine::new();
19402 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19403 for i in 0..20 {
19404 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19405 .unwrap();
19406 }
19407 e.execute("ANALYZE").unwrap();
19408 let snap = e.snapshot();
19409 let e2 = Engine::restore_envelope(&snap).unwrap();
19410 let s = e2.statistics().get("t", "id").unwrap();
19411 assert_eq!(s.n_distinct, 20);
19412 }
19413
19414 #[test]
19417 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19418 let mut e = Engine::new();
19422 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19423 for i in 0..9 {
19424 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19425 .unwrap();
19426 }
19427 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19428 e.execute("INSERT INTO t VALUES (9)").unwrap();
19429 let needs = e.tables_needing_analyze();
19430 assert_eq!(needs, alloc::vec!["t".to_string()]);
19431 }
19432
19433 #[test]
19434 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19435 let mut e = Engine::new();
19441 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19442 for i in 0..1000 {
19443 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19444 .unwrap();
19445 }
19446 e.execute("ANALYZE t").unwrap();
19447 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19448 for i in 1000..1050 {
19449 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19450 .unwrap();
19451 }
19452 assert!(
19453 e.tables_needing_analyze().is_empty(),
19454 "50 inserts < threshold of ~105"
19455 );
19456 for i in 1050..1200 {
19457 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19458 .unwrap();
19459 }
19460 assert_eq!(
19461 e.tables_needing_analyze(),
19462 alloc::vec!["t".to_string()],
19463 "200 inserts > 0.1 × 1200 threshold"
19464 );
19465 }
19466
19467 #[test]
19468 fn auto_analyze_threshold_resets_after_analyze() {
19469 let mut e = Engine::new();
19470 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19471 for i in 0..200 {
19472 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19473 .unwrap();
19474 }
19475 assert!(!e.tables_needing_analyze().is_empty());
19476 e.execute("ANALYZE").unwrap();
19477 assert!(
19478 e.tables_needing_analyze().is_empty(),
19479 "ANALYZE must reset the counter"
19480 );
19481 }
19482
19483 #[test]
19484 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19485 let mut e = Engine::new();
19486 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19487 .unwrap();
19488 for i in 0..50 {
19489 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19490 .unwrap();
19491 }
19492 e.execute("ANALYZE t").unwrap();
19493 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19496 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19497 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19498 }
19499
19500 #[test]
19501 fn v4_envelope_loads_with_empty_statistics() {
19502 let mut e = Engine::new();
19506 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19507 .unwrap();
19508 let catalog = e.catalog.serialize();
19509 let users = crate::users::serialize_users(&e.users);
19510 let pubs = e.publications.serialize();
19511 let subs = e.subscriptions.serialize();
19512 let mut buf = Vec::new();
19513 buf.extend_from_slice(b"SPGENV01");
19514 buf.push(4u8);
19515 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19516 buf.extend_from_slice(&catalog);
19517 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19518 buf.extend_from_slice(&users);
19519 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19520 buf.extend_from_slice(&pubs);
19521 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19522 buf.extend_from_slice(&subs);
19523 let crc = spg_crypto::crc32::crc32(&buf);
19524 buf.extend_from_slice(&crc.to_le_bytes());
19525 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19526 assert!(e2.statistics().is_empty());
19527 }
19528
19529 #[test]
19530 fn v1_v2_envelope_loads_with_empty_publications() {
19531 let mut e = Engine::new();
19538 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19541 .unwrap();
19542
19543 let catalog = e.catalog.serialize();
19545 let users = crate::users::serialize_users(&e.users);
19546 let mut buf = Vec::new();
19547 buf.extend_from_slice(b"SPGENV01");
19548 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19550 buf.extend_from_slice(&catalog);
19551 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19552 buf.extend_from_slice(&users);
19553 let crc = spg_crypto::crc32::crc32(&buf);
19554 buf.extend_from_slice(&crc.to_le_bytes());
19555
19556 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19557 assert!(e2.publications().is_empty());
19558 }
19559}