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 schema.uniqueness_constraints = uc_storage.clone();
6564 schema.checks = check_exprs;
6565 self.active_catalog_mut().create_table(schema)?;
6566 let table = self
6570 .active_catalog_mut()
6571 .get_mut(&table_name)
6572 .expect("just created");
6573 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6574 let idx_name = if inline_pk_columns.len() == 1 {
6575 alloc::format!("{table_name}_pkey")
6576 } else {
6577 alloc::format!("{table_name}_pkey_{i}")
6578 };
6579 if let Err(e) = table.add_index(idx_name, col_name) {
6580 return Err(EngineError::Storage(e));
6581 }
6582 }
6583 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6584 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6589 for (k, col) in columns.iter().enumerate() {
6590 let already = table.indices().iter().any(|idx| {
6591 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6592 && table.schema().columns[idx.column_position].name == *col
6593 });
6594 if already {
6595 continue;
6596 }
6597 let idx_name = match (name.as_ref(), columns.len(), k) {
6598 (Some(n), 1, _) => n.clone(),
6599 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6600 (None, _, _) => {
6601 alloc::format!("{table_name}_{col}_ftidx")
6602 }
6603 };
6604 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6605 return Err(EngineError::Storage(e));
6606 }
6607 }
6608 continue;
6609 }
6610 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6614 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6615 ("pkey", columns, None)
6616 }
6617 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6618 spg_sql::ast::TableConstraint::Index { name, columns } => {
6619 ("idx", columns, name.as_ref())
6620 }
6621 spg_sql::ast::TableConstraint::Check { .. } => continue,
6622 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6624 };
6625 let leading = &names[0];
6626 let already = table.indices().iter().any(|idx| {
6629 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6630 && table.schema().columns[idx.column_position].name == *leading
6631 });
6632 if already {
6633 continue;
6634 }
6635 let idx_name = if let Some(n) = explicit_name {
6636 n.clone()
6637 } else if names.len() == 1 {
6638 alloc::format!("{table_name}_{leading}_{suffix}")
6639 } else {
6640 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6641 };
6642 if let Err(e) = table.add_index(idx_name, leading) {
6643 return Err(EngineError::Storage(e));
6644 }
6645 }
6646 Ok(QueryResult::CommandOk {
6647 affected: 0,
6648 modified_catalog: !self.in_transaction(),
6649 })
6650 }
6651
6652 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6653 for tuple in &mut stmt.rows {
6661 for cell in tuple.iter_mut() {
6662 self.resolve_sequence_calls_in_expr(cell)?;
6663 }
6664 }
6665 if let Some(select) = stmt.select_source.clone() {
6670 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6671 let rows = match select_result {
6672 QueryResult::Rows { rows, .. } => rows,
6673 other => {
6674 return Err(EngineError::Unsupported(alloc::format!(
6675 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6676 )));
6677 }
6678 };
6679 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6680 for row in rows {
6681 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6682 for v in row.values {
6683 tuple.push(value_to_literal_expr_permissive(v)?);
6684 }
6685 materialised.push(tuple);
6686 }
6687 let recurse = InsertStatement {
6688 table: stmt.table,
6689 columns: stmt.columns,
6690 rows: materialised,
6691 select_source: None,
6692 on_conflict: stmt.on_conflict,
6693 returning: stmt.returning,
6694 };
6695 return self.exec_insert(recurse);
6696 }
6697 let clock = self.clock;
6701 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6707 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6708 let trigger_session_cfg: Option<alloc::string::String> = self
6709 .session_params
6710 .get("default_text_search_config")
6711 .cloned();
6712 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6718 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6719 EngineError::Storage(StorageError::TableNotFound {
6720 name: stmt.table.clone(),
6721 })
6722 })?;
6723 preview_table.schema().columns.clone()
6724 };
6725 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6726 pre_borrow_column_meta
6727 .iter()
6728 .enumerate()
6729 .filter_map(|(i, col)| {
6730 if let Some(inline) = &col.inline_enum_variants {
6735 return Some((i, inline.clone()));
6736 }
6737 col.user_enum_type.as_ref().and_then(|ename| {
6738 self.active_catalog()
6739 .enum_types()
6740 .get(ename)
6741 .map(|e| (i, e.labels.clone()))
6742 })
6743 })
6744 .collect();
6745 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6750 pre_borrow_column_meta
6751 .iter()
6752 .enumerate()
6753 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6754 .collect();
6755 let table = self
6756 .active_catalog_mut()
6757 .get_mut(&stmt.table)
6758 .ok_or_else(|| {
6759 EngineError::Storage(StorageError::TableNotFound {
6760 name: stmt.table.clone(),
6761 })
6762 })?;
6763 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6769 let schema_cols_len = column_meta.len();
6770 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6774 None => None, Some(cols) => {
6776 let mut map = alloc::vec![None; schema_cols_len];
6777 for (j, name) in cols.iter().enumerate() {
6778 let idx = column_meta
6779 .iter()
6780 .position(|c| c.name == *name)
6781 .ok_or_else(|| {
6782 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6783 })?;
6784 if map[idx].is_some() {
6785 return Err(EngineError::Storage(StorageError::ArityMismatch {
6786 expected: schema_cols_len,
6787 actual: cols.len(),
6788 }));
6789 }
6790 map[idx] = Some(j);
6791 }
6792 for (i, col) in column_meta.iter().enumerate() {
6796 if map[i].is_none()
6797 && !col.nullable
6798 && col.default.is_none()
6799 && col.runtime_default.is_none()
6800 && !col.auto_increment
6801 {
6802 return Err(EngineError::Storage(StorageError::NullInNotNull {
6803 column: col.name.clone(),
6804 }));
6805 }
6806 }
6807 Some(map)
6808 }
6809 };
6810 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6811 let fks = table.schema().foreign_keys.clone();
6817 let mut affected = 0usize;
6818 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6821 for tuple in stmt.rows {
6822 if tuple.len() != expected_tuple_len {
6823 return Err(EngineError::Storage(StorageError::ArityMismatch {
6824 expected: expected_tuple_len,
6825 actual: tuple.len(),
6826 }));
6827 }
6828 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6832 let raw_tuple: Vec<Value> = tuple
6834 .into_iter()
6835 .map(literal_expr_to_value)
6836 .collect::<Result<_, _>>()?;
6837 let mut out = Vec::with_capacity(schema_cols_len);
6838 for (i, col) in column_meta.iter().enumerate() {
6839 let mut raw = match map[i] {
6840 Some(j) => raw_tuple[j].clone(),
6841 None => resolve_column_default_free(col, clock)?,
6842 };
6843 if col.auto_increment && raw.is_null() {
6844 let next = table.next_auto_value(i).ok_or_else(|| {
6845 EngineError::Unsupported(alloc::format!(
6846 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6847 col.name
6848 ))
6849 })?;
6850 raw = Value::BigInt(next);
6851 }
6852 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6853 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6854 let coerced =
6855 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6856 check_unsigned_range(&coerced, col, i)?;
6857 out.push(coerced);
6858 }
6859 out
6860 } else {
6861 let mut out = Vec::with_capacity(schema_cols_len);
6863 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6864 let mut raw = literal_expr_to_value(expr)?;
6865 if col.auto_increment && raw.is_null() {
6866 let next = table.next_auto_value(i).ok_or_else(|| {
6867 EngineError::Unsupported(alloc::format!(
6868 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6869 col.name
6870 ))
6871 })?;
6872 raw = Value::BigInt(next);
6873 }
6874 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6875 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6876 let coerced =
6877 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6878 check_unsigned_range(&coerced, col, i)?;
6879 out.push(coerced);
6880 }
6881 out
6882 };
6883 all_values.push(values);
6884 }
6885 let uniqueness = table.schema().uniqueness_constraints.clone();
6890 let _ = table;
6891 if !fks.is_empty() {
6892 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6893 }
6894 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6896 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6910 let mut skipped_count = 0usize;
6911 if let Some(clause) = &stmt.on_conflict {
6912 let conflict_cols = resolve_on_conflict_columns(
6913 self.active_catalog(),
6914 &stmt.table,
6915 clause.target_columns.as_slice(),
6916 )?;
6917 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6918 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6919 for values in all_values {
6920 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6921 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6924 let collides_with_table = !has_null_key
6925 && on_conflict_keys_exist(
6926 self.active_catalog(),
6927 &stmt.table,
6928 &conflict_cols,
6929 &key_tuple,
6930 );
6931 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6932 let collides_with_batch =
6933 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6934 let collides = collides_with_table || collides_with_batch;
6935 match (&clause.action, collides) {
6936 (_, false) => {
6937 seen_keys.push(key_tuple_owned);
6938 kept.push(values);
6939 }
6940 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6941 skipped_count += 1;
6942 }
6943 (
6944 spg_sql::ast::OnConflictAction::Update {
6945 assignments,
6946 where_,
6947 },
6948 true,
6949 ) => {
6950 if !collides_with_table {
6951 skipped_count += 1;
6952 continue;
6953 }
6954 let target_pos = lookup_row_position_by_keys(
6955 self.active_catalog(),
6956 &stmt.table,
6957 &conflict_cols,
6958 &key_tuple,
6959 )
6960 .ok_or_else(|| {
6961 EngineError::Unsupported(
6962 "ON CONFLICT DO UPDATE: conflict detected but row \
6963 position could not be resolved (cold-tier row?)"
6964 .into(),
6965 )
6966 })?;
6967 let updated = apply_on_conflict_assignments(
6968 self.active_catalog(),
6969 &stmt.table,
6970 target_pos,
6971 &values,
6972 assignments,
6973 where_.as_ref(),
6974 )?;
6975 if let Some(new_row) = updated {
6976 pending_updates.push((target_pos, new_row));
6977 } else {
6978 skipped_count += 1;
6979 }
6980 }
6981 }
6982 }
6983 all_values = kept;
6984 }
6985 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
6991 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
6992 let table = self
6994 .active_catalog_mut()
6995 .get_mut(&stmt.table)
6996 .ok_or_else(|| {
6997 EngineError::Storage(StorageError::TableNotFound {
6998 name: stmt.table.clone(),
6999 })
7000 })?;
7001 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
7005 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
7009 'rowloop: for values in all_values {
7010 let mut row = Row::new(values);
7011 for fd in &before_insert_triggers {
7016 let (outcome, deferred) = triggers::fire_row_trigger(
7017 fd,
7018 Some(row.clone()),
7019 None,
7020 &stmt.table,
7021 &column_meta,
7022 &[],
7023 trigger_session_cfg.as_deref(),
7024 false,
7025 )
7026 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
7027 deferred_embedded.extend(deferred);
7028 match outcome {
7029 triggers::TriggerOutcome::Row(r) => row = r,
7030 triggers::TriggerOutcome::Skip => continue 'rowloop,
7031 }
7032 }
7033 if stmt.returning.is_some() {
7034 returning_rows.push(row.values.clone());
7035 }
7036 let inserted = row.clone();
7039 table.insert(row)?;
7040 affected += 1;
7041 for fd in &after_insert_triggers {
7045 let (_outcome, deferred) = triggers::fire_row_trigger(
7046 fd,
7047 Some(inserted.clone()),
7048 None,
7049 &stmt.table,
7050 &column_meta,
7051 &[],
7052 trigger_session_cfg.as_deref(),
7053 true,
7054 )
7055 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
7056 deferred_embedded.extend(deferred);
7057 }
7058 }
7059 for (pos, new_row) in pending_updates {
7063 if stmt.returning.is_some() {
7064 returning_rows.push(new_row.clone());
7065 }
7066 table.update_row(pos, new_row)?;
7067 affected += 1;
7068 }
7069 let _ = skipped_count;
7070 let _ = table;
7076 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
7077 if let Some(items) = &stmt.returning {
7081 return self.build_returning_rows(&stmt.table, items, returning_rows);
7082 }
7083 if !self.in_transaction() && affected > 0 {
7088 self.statistics
7089 .record_modifications(&stmt.table, affected as u64);
7090 }
7091 Ok(QueryResult::CommandOk {
7092 affected,
7093 modified_catalog: !self.in_transaction(),
7094 })
7095 }
7096
7097 fn exec_select_as_of_segment(
7110 &self,
7111 stmt: &SelectStatement,
7112 from: &spg_sql::ast::FromClause,
7113 segment_id: u32,
7114 ) -> Result<QueryResult, EngineError> {
7115 if !from.joins.is_empty()
7118 || stmt.group_by.is_some()
7119 || stmt.having.is_some()
7120 || !stmt.unions.is_empty()
7121 || !stmt.order_by.is_empty()
7122 || stmt.offset.is_some()
7123 || stmt.distinct
7124 || aggregate::uses_aggregate(stmt)
7125 {
7126 return Err(EngineError::Unsupported(
7127 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
7128 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
7129 .into(),
7130 ));
7131 }
7132 let table = self
7133 .active_catalog()
7134 .get(&from.primary.name)
7135 .ok_or_else(|| StorageError::TableNotFound {
7136 name: from.primary.name.clone(),
7137 })?;
7138 let schema = table.schema().clone();
7139 let schema_cols = &schema.columns;
7140 let alias = from
7141 .primary
7142 .alias
7143 .as_deref()
7144 .unwrap_or(from.primary.name.as_str());
7145 let ctx = EvalContext::new(schema_cols, Some(alias));
7146 let seg = self
7147 .active_catalog()
7148 .cold_segment(segment_id)
7149 .ok_or_else(|| {
7150 EngineError::Unsupported(alloc::format!(
7151 "AS OF SEGMENT: cold segment {segment_id} not registered"
7152 ))
7153 })?;
7154 let mut out_rows: Vec<Row> = Vec::new();
7155 let mut limit_remaining: Option<usize> =
7156 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
7157 for (_key, body) in seg.scan() {
7158 let (row, _consumed) =
7159 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
7160 if let Some(where_expr) = &stmt.where_ {
7161 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
7162 if !matches!(cond, Value::Bool(true)) {
7163 continue;
7164 }
7165 }
7166 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7168 out_rows.push(projected);
7169 if let Some(rem) = limit_remaining.as_mut() {
7170 if *rem == 0 {
7171 out_rows.pop();
7172 break;
7173 }
7174 *rem -= 1;
7175 }
7176 }
7177 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7179 Ok(QueryResult::Rows {
7180 columns,
7181 rows: out_rows,
7182 })
7183 }
7184
7185 fn eval_expr_simple(
7190 &self,
7191 expr: &Expr,
7192 row: &Row,
7193 ctx: &EvalContext,
7194 ) -> Result<Value, EngineError> {
7195 let cancel = CancelToken::none();
7196 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7197 }
7198
7199 fn build_returning_rows(
7206 &self,
7207 table_name: &str,
7208 items: &[SelectItem],
7209 mutated_rows: Vec<Vec<Value>>,
7210 ) -> Result<QueryResult, EngineError> {
7211 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7212 EngineError::Storage(StorageError::TableNotFound {
7213 name: table_name.into(),
7214 })
7215 })?;
7216 let schema_cols = table.schema().columns.clone();
7217 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7218 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7219 for values in mutated_rows {
7220 let row = Row::new(values);
7221 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7222 out_rows.push(projected);
7223 }
7224 Ok(QueryResult::Rows {
7225 columns,
7226 rows: out_rows,
7227 })
7228 }
7229
7230 fn project_row_simple(
7234 &self,
7235 row: &Row,
7236 items: &[SelectItem],
7237 schema_cols: &[ColumnSchema],
7238 alias: &str,
7239 ) -> Result<Row, EngineError> {
7240 let ctx = EvalContext::new(schema_cols, Some(alias));
7241 let cancel = CancelToken::none();
7242 let mut out_vals = Vec::new();
7243 for item in items {
7244 match item {
7245 SelectItem::Wildcard => {
7246 out_vals.extend(row.values.iter().cloned());
7247 }
7248 SelectItem::Expr { expr, .. } => {
7249 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7250 out_vals.push(v);
7251 }
7252 }
7253 }
7254 Ok(Row::new(out_vals))
7255 }
7256
7257 fn derive_output_columns(
7262 &self,
7263 items: &[SelectItem],
7264 schema_cols: &[ColumnSchema],
7265 _alias: &str,
7266 ) -> Vec<ColumnSchema> {
7267 let mut out = Vec::new();
7268 for item in items {
7269 match item {
7270 SelectItem::Wildcard => {
7271 out.extend(schema_cols.iter().cloned());
7272 }
7273 SelectItem::Expr { expr, alias } => {
7274 if let Expr::Column(col) = expr
7280 && let Some(sc) = schema_cols.iter().find(|c| c.name == col.name)
7281 {
7282 let name = alias.clone().unwrap_or_else(|| sc.name.clone());
7283 out.push(ColumnSchema::new(name, sc.ty, sc.nullable));
7284 continue;
7285 }
7286 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7287 out.push(ColumnSchema::new(name, DataType::Text, true));
7290 }
7291 }
7292 }
7293 out
7294 }
7295
7296 fn exec_select_cancel(
7297 &self,
7298 stmt: &SelectStatement,
7299 cancel: CancelToken<'_>,
7300 ) -> Result<QueryResult, EngineError> {
7301 cancel.check()?;
7302 if !self.active_catalog().views().is_empty() {
7309 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7310 return self.exec_select_cancel(&rewritten, cancel);
7311 }
7312 }
7313 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7322 return self.exec_select_with_meta_views(stmt, cancel);
7323 }
7324 if let Some(from) = &stmt.from
7333 && let Some(seg_id) = from.primary.as_of_segment
7334 {
7335 return self.exec_select_as_of_segment(stmt, from, seg_id);
7336 }
7337 if let Some(from) = &stmt.from
7341 && from.joins.is_empty()
7342 && stmt.where_.is_none()
7343 && stmt.group_by.is_none()
7344 && stmt.having.is_none()
7345 && stmt.unions.is_empty()
7346 && stmt.order_by.is_empty()
7347 && stmt.limit.is_none()
7348 && stmt.offset.is_none()
7349 && !stmt.distinct
7350 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7351 {
7352 let lower = from.primary.name.to_ascii_lowercase();
7353 match lower.as_str() {
7354 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7355 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7357 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7358 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7359 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7360 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7361 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7362 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7363 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7364 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7365 _ => {}
7366 }
7367 }
7368 if !stmt.ctes.is_empty() {
7376 return self.exec_with_ctes(stmt, cancel);
7377 }
7378 let mut stmt_owned;
7385 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7386 stmt_owned = stmt.clone();
7387 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7388 &stmt_owned
7389 } else {
7390 stmt
7391 };
7392 if stmt_ref.unions.is_empty() {
7393 return self.exec_bare_select_cancel(stmt_ref, cancel);
7394 }
7395 let mut head = stmt_ref.clone();
7400 head.unions = Vec::new();
7401 head.order_by = Vec::new();
7402 head.limit = None;
7403 let QueryResult::Rows { columns, mut rows } =
7404 self.exec_bare_select_cancel(&head, cancel)?
7405 else {
7406 unreachable!("bare SELECT cannot return CommandOk")
7407 };
7408 for (kind, peer) in &stmt_ref.unions {
7409 let QueryResult::Rows {
7410 columns: peer_cols,
7411 rows: peer_rows,
7412 } = self.exec_bare_select_cancel(peer, cancel)?
7413 else {
7414 unreachable!("bare SELECT cannot return CommandOk")
7415 };
7416 if peer_cols.len() != columns.len() {
7417 return Err(EngineError::Unsupported(alloc::format!(
7418 "UNION arity mismatch: head has {} columns, peer has {}",
7419 columns.len(),
7420 peer_cols.len()
7421 )));
7422 }
7423 rows.extend(peer_rows);
7424 if matches!(kind, UnionKind::Distinct) {
7425 rows = dedup_rows(rows);
7426 }
7427 }
7428 if !stmt.order_by.is_empty() {
7431 let synth_ctx = EvalContext::new(&columns, None);
7432 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7433 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7434 for r in rows {
7435 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7436 tagged.push((keys, r));
7437 }
7438 sort_by_keys(&mut tagged, &descs);
7439 rows = tagged.into_iter().map(|(_, r)| r).collect();
7440 }
7441 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7442 Ok(QueryResult::Rows { columns, rows })
7443 }
7444
7445 #[allow(clippy::too_many_lines)]
7446 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7454 &self,
7455 stmt: &SelectStatement,
7456 primary: &TableRef,
7457 cancel: CancelToken<'_>,
7458 ) -> Result<QueryResult, EngineError> {
7459 let expr = primary
7460 .unnest_expr
7461 .as_deref()
7462 .expect("caller guards unnest_expr.is_some()");
7463 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7466 let ctx = EvalContext::new(&empty_schema, None);
7467 let dummy_row = Row::new(alloc::vec::Vec::new());
7468 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7471 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7472 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7473 Value::TextArray(items) => {
7474 let rows = items
7475 .into_iter()
7476 .map(|item| {
7477 Row::new(alloc::vec![match item {
7478 Some(s) => Value::Text(s),
7479 None => Value::Null,
7480 }])
7481 })
7482 .collect();
7483 (DataType::Text, rows)
7484 }
7485 Value::IntArray(items) => {
7486 let rows = items
7487 .into_iter()
7488 .map(|item| {
7489 Row::new(alloc::vec![match item {
7490 Some(n) => Value::Int(n),
7491 None => Value::Null,
7492 }])
7493 })
7494 .collect();
7495 (DataType::Int, rows)
7496 }
7497 Value::BigIntArray(items) => {
7498 let rows = items
7499 .into_iter()
7500 .map(|item| {
7501 Row::new(alloc::vec![match item {
7502 Some(n) => Value::BigInt(n),
7503 None => Value::Null,
7504 }])
7505 })
7506 .collect();
7507 (DataType::BigInt, rows)
7508 }
7509 other => {
7510 return Err(EngineError::Unsupported(alloc::format!(
7511 "unnest() expects an array argument, got {:?}",
7512 other.data_type()
7513 )));
7514 }
7515 };
7516 let alias = primary
7517 .alias
7518 .clone()
7519 .unwrap_or_else(|| "unnest".to_string());
7520 let col_name = primary
7526 .unnest_column_aliases
7527 .first()
7528 .cloned()
7529 .unwrap_or_else(|| alias.clone());
7530 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7531 let schema_cols = alloc::vec![col_schema.clone()];
7532 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7533 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7535 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7536 for row in rows {
7537 cancel.check()?;
7538 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7539 if matches!(v, Value::Bool(true)) {
7540 out.push(row);
7541 }
7542 }
7543 out
7544 } else {
7545 rows
7546 };
7547 if aggregate::uses_aggregate(stmt) {
7553 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7554 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7555 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7556 return Ok(QueryResult::Rows {
7557 columns: agg.columns,
7558 rows: agg.rows,
7559 });
7560 }
7561 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7563 let mut projected_rows: alloc::vec::Vec<Row> =
7564 alloc::vec::Vec::with_capacity(filtered.len());
7565 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7574 if let Some(srf_idx) = srf_position {
7575 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7576 .expect("checked by is_top_level_unnest above");
7577 for row in &filtered {
7578 let arr_val =
7579 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7580 let elements = array_value_to_elements(&arr_val)?;
7581 for elem in elements {
7585 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7586 for (i, p) in projection.iter().enumerate() {
7587 if i == srf_idx {
7588 vals.push(elem.clone());
7589 } else {
7590 vals.push(
7591 eval::eval_expr(&p.expr, row, &scan_ctx)
7592 .map_err(EngineError::Eval)?,
7593 );
7594 }
7595 }
7596 projected_rows.push(Row::new(vals));
7597 }
7598 }
7599 } else {
7600 for row in &filtered {
7601 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7602 for p in &projection {
7603 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7604 }
7605 projected_rows.push(Row::new(vals));
7606 }
7607 }
7608 let columns: alloc::vec::Vec<ColumnSchema> = projection
7611 .iter()
7612 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7613 .collect();
7614 if !stmt.order_by.is_empty() {
7617 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7618 .iter()
7619 .enumerate()
7620 .map(|(i, r)| -> Result<_, EngineError> {
7621 let keys: Result<Vec<Value>, EngineError> = stmt
7622 .order_by
7623 .iter()
7624 .map(|ob| {
7625 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7626 })
7627 .collect();
7628 Ok((i, keys?))
7629 })
7630 .collect::<Result<_, _>>()?;
7631 indexed.sort_by(|a, b| {
7632 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7633 let mut cmp = value_cmp(ka, kb);
7634 if stmt.order_by[idx].desc {
7635 cmp = cmp.reverse();
7636 }
7637 if cmp != core::cmp::Ordering::Equal {
7638 return cmp;
7639 }
7640 }
7641 core::cmp::Ordering::Equal
7642 });
7643 projected_rows = indexed
7644 .into_iter()
7645 .map(|(i, _)| projected_rows[i].clone())
7646 .collect();
7647 }
7648 if let Some(offset) = stmt.offset_literal() {
7650 let off = (offset as usize).min(projected_rows.len());
7651 projected_rows.drain(..off);
7652 }
7653 if let Some(limit) = stmt.limit_literal() {
7654 projected_rows.truncate(limit as usize);
7655 }
7656 Ok(QueryResult::Rows {
7657 columns,
7658 rows: projected_rows,
7659 })
7660 }
7661
7662 fn exec_select_generate_series(
7673 &self,
7674 stmt: &SelectStatement,
7675 primary: &TableRef,
7676 cancel: CancelToken<'_>,
7677 ) -> Result<QueryResult, EngineError> {
7678 let args = primary
7679 .generate_series_args
7680 .as_ref()
7681 .expect("caller guards generate_series_args.is_some()");
7682 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7683 let ctx = EvalContext::new(&empty_schema, None);
7684 let dummy_row = Row::new(alloc::vec::Vec::new());
7685 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7686 for a in args {
7687 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7688 }
7689 let (elem_dtype, rows) = match arg_values.as_slice() {
7693 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7694 let interval_step = match step {
7695 Value::Interval { .. } => step.clone(),
7696 other => {
7697 return Err(EngineError::Unsupported(alloc::format!(
7698 "generate_series(timestamp, timestamp, …): \
7699 step must be INTERVAL, got {:?}",
7700 other.data_type()
7701 )));
7702 }
7703 };
7704 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7705 (DataType::Timestamp, rows)
7706 }
7707 [start, stop, step]
7708 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7709 {
7710 let s = value_to_i64(start);
7711 let e = value_to_i64(stop);
7712 let st = value_to_i64(step);
7713 let rows = generate_series_integers(s, e, st, &cancel)?;
7714 (DataType::BigInt, rows)
7715 }
7716 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7717 let s = value_to_i64(start);
7718 let e = value_to_i64(stop);
7719 let rows = generate_series_integers(s, e, 1, &cancel)?;
7720 (DataType::BigInt, rows)
7721 }
7722 _ => {
7723 return Err(EngineError::Unsupported(alloc::format!(
7724 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7725 argument shapes; got {:?}",
7726 arg_values
7727 .iter()
7728 .map(|v| v.data_type())
7729 .collect::<alloc::vec::Vec<_>>()
7730 )));
7731 }
7732 };
7733 let alias = primary
7734 .alias
7735 .clone()
7736 .unwrap_or_else(|| "generate_series".to_string());
7737 let col_name = alias.clone();
7738 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7739 let schema_cols = alloc::vec![col_schema.clone()];
7740 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7741 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7743 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7744 for row in rows {
7745 cancel.check()?;
7746 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7747 if matches!(v, Value::Bool(true)) {
7748 out.push(row);
7749 }
7750 }
7751 out
7752 } else {
7753 rows
7754 };
7755 if aggregate::uses_aggregate(stmt) {
7765 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7766 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7767 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7768 return Ok(QueryResult::Rows {
7769 columns: agg.columns,
7770 rows: agg.rows,
7771 });
7772 }
7773 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7775 let mut projected_rows: alloc::vec::Vec<Row> =
7776 alloc::vec::Vec::with_capacity(filtered.len());
7777 for row in &filtered {
7778 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7779 for p in &projection {
7780 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7781 }
7782 projected_rows.push(Row::new(vals));
7783 }
7784 let columns: alloc::vec::Vec<ColumnSchema> = projection
7785 .iter()
7786 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7787 .collect();
7788 if !stmt.order_by.is_empty() {
7790 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7791 .iter()
7792 .enumerate()
7793 .map(|(i, r)| -> Result<_, EngineError> {
7794 let keys: Result<Vec<Value>, EngineError> = stmt
7795 .order_by
7796 .iter()
7797 .map(|ob| {
7798 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7799 })
7800 .collect();
7801 Ok((i, keys?))
7802 })
7803 .collect::<Result<_, _>>()?;
7804 indexed.sort_by(|a, b| {
7805 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7806 let mut cmp = value_cmp(ka, kb);
7807 if stmt.order_by[idx].desc {
7808 cmp = cmp.reverse();
7809 }
7810 if cmp != core::cmp::Ordering::Equal {
7811 return cmp;
7812 }
7813 }
7814 core::cmp::Ordering::Equal
7815 });
7816 projected_rows = indexed
7817 .into_iter()
7818 .map(|(i, _)| projected_rows[i].clone())
7819 .collect();
7820 }
7821 if let Some(offset) = stmt.offset_literal() {
7822 let off = (offset as usize).min(projected_rows.len());
7823 projected_rows.drain(..off);
7824 }
7825 if let Some(limit) = stmt.limit_literal() {
7826 projected_rows.truncate(limit as usize);
7827 }
7828 Ok(QueryResult::Rows {
7829 columns,
7830 rows: projected_rows,
7831 })
7832 }
7833
7834 fn exec_bare_select_cancel(
7835 &self,
7836 stmt: &SelectStatement,
7837 cancel: CancelToken<'_>,
7838 ) -> Result<QueryResult, EngineError> {
7839 check_with_ties_requires_order_by(stmt)?;
7844 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7852 return self.exec_select_with_meta_views(stmt, cancel);
7853 }
7854 if select_has_window(stmt) {
7859 return self.exec_select_with_window(stmt, cancel);
7860 }
7861 let Some(from) = &stmt.from else {
7866 let empty_schema: Vec<ColumnSchema> = Vec::new();
7867 let ctx = self.ev_ctx(&empty_schema, None);
7868 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7869 let dummy_row = Row::new(Vec::new());
7870 let mut values = Vec::with_capacity(projection.len());
7871 for p in &projection {
7872 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7873 }
7874 let columns: Vec<ColumnSchema> = projection
7875 .into_iter()
7876 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7877 .collect();
7878 return Ok(QueryResult::Rows {
7879 columns,
7880 rows: alloc::vec![Row::new(values)],
7881 });
7882 };
7883 if !from.joins.is_empty() {
7887 return self.exec_joined_select(stmt, from);
7888 }
7889 if from.primary.unnest_expr.is_some() {
7896 return self.exec_select_unnest(stmt, &from.primary, cancel);
7897 }
7898 if from.primary.generate_series_args.is_some() {
7904 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7905 }
7906 let primary = &from.primary;
7907 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7908 StorageError::TableNotFound {
7909 name: primary.name.clone(),
7910 }
7911 })?;
7912 let schema_cols = &table.schema().columns;
7913 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7916 let ctx = self.ev_ctx(schema_cols, Some(alias));
7917
7918 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7923 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7924 }
7925
7926 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7934 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7937 .or_else(|| {
7938 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7944 })
7945 .or_else(|| {
7946 try_trgm_seek(w, schema_cols, table, alias)
7952 })
7953 });
7954
7955 if aggregate::uses_aggregate(stmt) {
7958 let mut filtered: Vec<&Row> = Vec::new();
7959 let mut memo = memoize::MemoizeCache::new();
7963 if let Some(rows) = &indexed_rows {
7964 for cow in rows {
7965 let row = cow.as_ref();
7966 if let Some(where_expr) = &stmt.where_ {
7967 let cond = self.eval_expr_with_correlated(
7968 where_expr,
7969 row,
7970 &ctx,
7971 cancel,
7972 Some(&mut memo),
7973 )?;
7974 if !matches!(cond, Value::Bool(true)) {
7975 continue;
7976 }
7977 }
7978 filtered.push(row);
7979 }
7980 } else {
7981 for i in 0..table.row_count() {
7982 let row = &table.rows()[i];
7983 if let Some(where_expr) = &stmt.where_ {
7984 let cond = self.eval_expr_with_correlated(
7985 where_expr,
7986 row,
7987 &ctx,
7988 cancel,
7989 Some(&mut memo),
7990 )?;
7991 if !matches!(cond, Value::Bool(true)) {
7992 continue;
7993 }
7994 }
7995 filtered.push(row);
7996 }
7997 }
7998 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
7999 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8000 return Ok(QueryResult::Rows {
8001 columns: agg.columns,
8002 rows: agg.rows,
8003 });
8004 }
8005
8006 let projection = build_projection(&stmt.items, schema_cols, alias)?;
8007 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
8015
8016 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8019 let mut memo = memoize::MemoizeCache::new();
8021 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
8024 if loop_idx.is_multiple_of(256) {
8025 cancel.check()?;
8026 }
8027 if let Some(where_expr) = &stmt.where_ {
8028 let cond =
8029 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
8030 if !matches!(cond, Value::Bool(true)) {
8031 return Ok(());
8032 }
8033 }
8034 let order_keys = if stmt.order_by.is_empty() {
8035 Vec::new()
8036 } else {
8037 build_order_keys(&stmt.order_by, row, &ctx)?
8038 };
8039 if let Some(srf_idx) = srf_position {
8040 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
8041 .expect("checked by is_top_level_unnest above");
8042 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
8043 let elements = array_value_to_elements(&arr_val)?;
8044 for elem in elements {
8045 let mut values = Vec::with_capacity(projection.len());
8046 for (i, p) in projection.iter().enumerate() {
8047 if i == srf_idx {
8048 values.push(elem.clone());
8049 } else {
8050 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8051 }
8052 }
8053 tagged.push((order_keys.clone(), Row::new(values)));
8054 }
8055 } else {
8056 let mut values = Vec::with_capacity(projection.len());
8057 for p in &projection {
8058 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8059 }
8060 tagged.push((order_keys, Row::new(values)));
8061 }
8062 Ok(())
8063 };
8064 if let Some(rows) = &indexed_rows {
8065 for (loop_idx, cow) in rows.iter().enumerate() {
8066 process_row(cow.as_ref(), loop_idx)?;
8067 }
8068 } else {
8069 for i in 0..table.row_count() {
8070 process_row(&table.rows()[i], i)?;
8071 }
8072 }
8073
8074 if !stmt.order_by.is_empty() {
8075 let keep = if stmt.distinct || stmt.limit_with_ties {
8083 None
8084 } else {
8085 stmt.limit_literal()
8086 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8087 };
8088 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8089 partial_sort_tagged(&mut tagged, keep, &descs);
8090 }
8091
8092 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
8102 apply_offset_and_limit_tagged(
8103 &mut tagged,
8104 stmt.offset_literal(),
8105 stmt.limit_literal(),
8106 true,
8107 );
8108 tagged.into_iter().map(|(_, r)| r).collect()
8109 } else {
8110 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8111 if stmt.distinct {
8112 output_rows = dedup_rows(output_rows);
8113 }
8114 apply_offset_and_limit(
8115 &mut output_rows,
8116 stmt.offset_literal(),
8117 stmt.limit_literal(),
8118 );
8119 output_rows
8120 };
8121
8122 let columns: Vec<ColumnSchema> = projection
8123 .into_iter()
8124 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8125 .collect();
8126
8127 Ok(QueryResult::Rows {
8128 columns,
8129 rows: output_rows,
8130 })
8131 }
8132
8133 #[allow(clippy::too_many_lines)]
8140 fn materialise_table_ref(
8148 &self,
8149 tref: &TableRef,
8150 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8151 if let Some(expr) = tref.unnest_expr.as_deref() {
8152 let empty_schema: Vec<ColumnSchema> = Vec::new();
8153 let ctx = EvalContext::new(&empty_schema, None);
8154 let dummy_row = Row::new(Vec::new());
8155 let (elem_dtype, rows) =
8156 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8157 Value::Null => (DataType::Text, Vec::new()),
8158 Value::TextArray(items) => (
8159 DataType::Text,
8160 items
8161 .into_iter()
8162 .map(|item| {
8163 Row::new(alloc::vec![match item {
8164 Some(s) => Value::Text(s),
8165 None => Value::Null,
8166 }])
8167 })
8168 .collect(),
8169 ),
8170 Value::IntArray(items) => (
8171 DataType::Int,
8172 items
8173 .into_iter()
8174 .map(|item| {
8175 Row::new(alloc::vec![match item {
8176 Some(n) => Value::Int(n),
8177 None => Value::Null,
8178 }])
8179 })
8180 .collect(),
8181 ),
8182 Value::BigIntArray(items) => (
8183 DataType::BigInt,
8184 items
8185 .into_iter()
8186 .map(|item| {
8187 Row::new(alloc::vec![match item {
8188 Some(n) => Value::BigInt(n),
8189 None => Value::Null,
8190 }])
8191 })
8192 .collect(),
8193 ),
8194 other => {
8195 return Err(EngineError::Unsupported(alloc::format!(
8196 "unnest() expects an array argument, got {:?}",
8197 other.data_type()
8198 )));
8199 }
8200 };
8201 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8202 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8203 return Ok((
8204 rows,
8205 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8206 ));
8207 }
8208 let table =
8209 self.active_catalog()
8210 .get(&tref.name)
8211 .ok_or_else(|| StorageError::TableNotFound {
8212 name: tref.name.clone(),
8213 })?;
8214 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8215 let cols = table.schema().columns.clone();
8216 Ok((rows, cols))
8217 }
8218
8219 fn build_joined_filtered_rows(
8231 &self,
8232 from: &FromClause,
8233 where_: Option<&Expr>,
8234 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8235 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8236 let primary_alias = from
8237 .primary
8238 .alias
8239 .as_deref()
8240 .unwrap_or(from.primary.name.as_str())
8241 .to_string();
8242 #[allow(clippy::type_complexity)]
8249 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8250 for j in &from.joins {
8251 let a = j
8252 .table
8253 .alias
8254 .as_deref()
8255 .unwrap_or(j.table.name.as_str())
8256 .to_string();
8257 if let Some(inner_box) = &j.table.lateral_subquery {
8258 let schema = self.lateral_probe_schema(inner_box)?;
8263 joined.push(JoinedPeer {
8264 eager_rows: None,
8265 cols: schema,
8266 alias: a,
8267 kind: j.kind,
8268 on: j.on.as_ref(),
8269 lateral: Some(inner_box.as_ref()),
8270 });
8271 } else {
8272 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8273 joined.push(JoinedPeer {
8274 eager_rows: Some(rows),
8275 cols,
8276 alias: a,
8277 kind: j.kind,
8278 on: j.on.as_ref(),
8279 lateral: None,
8280 });
8281 }
8282 }
8283 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8284 for col in &primary_cols {
8285 combined_schema.push(ColumnSchema::new(
8286 alloc::format!("{primary_alias}.{}", col.name),
8287 col.ty,
8288 col.nullable,
8289 ));
8290 }
8291 for peer in &joined {
8292 for col in &peer.cols {
8293 combined_schema.push(ColumnSchema::new(
8294 alloc::format!("{}.{}", peer.alias, col.name),
8295 col.ty,
8296 col.nullable,
8297 ));
8298 }
8299 }
8300 let ctx = EvalContext::new(&combined_schema, None);
8301 let mut working: Vec<Row> = primary_rows;
8302 let mut consumed_cols = primary_cols.len();
8305 for peer in &joined {
8306 let right_arity = peer.cols.len();
8307 let mut next: Vec<Row> = Vec::new();
8308 for left in &working {
8309 let mut left_matched = false;
8310 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8311 Some(inner) => {
8312 let outer_schema = &combined_schema[..consumed_cols];
8316 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8317 alloc::borrow::Cow::Owned(rows)
8318 }
8319 None => {
8320 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8321 alloc::borrow::Cow::Borrowed(r.as_slice())
8322 }
8323 };
8324 for right in per_left_rrows.as_ref() {
8325 let mut combined_vals = left.values.clone();
8326 combined_vals.extend(right.values.iter().cloned());
8327 let combined = Row::new(combined_vals);
8328 let keep = if let Some(on_expr) = peer.on {
8329 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
8330 matches!(cond, Value::Bool(true))
8331 } else {
8332 true
8333 };
8334 if keep {
8335 next.push(combined);
8336 left_matched = true;
8337 }
8338 }
8339 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8340 let mut combined_vals = left.values.clone();
8341 for _ in 0..right_arity {
8342 combined_vals.push(Value::Null);
8343 }
8344 next.push(Row::new(combined_vals));
8345 }
8346 }
8347 working = next;
8348 consumed_cols += right_arity;
8349 debug_assert!(consumed_cols <= combined_schema.len());
8350 }
8351 let mut filtered: Vec<Row> = Vec::new();
8352 for row in working {
8353 if let Some(where_expr) = where_ {
8354 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
8355 if !matches!(cond, Value::Bool(true)) {
8356 continue;
8357 }
8358 }
8359 filtered.push(row);
8360 }
8361 Ok((combined_schema, filtered))
8362 }
8363
8364 fn lateral_probe_schema(
8370 &self,
8371 inner: &SelectStatement,
8372 ) -> Result<Vec<ColumnSchema>, EngineError> {
8373 match self.execute_readonly_select_for_lateral_probe(inner) {
8383 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8384 _ => {
8390 let mut out: Vec<ColumnSchema> = Vec::new();
8391 for (i, item) in inner.items.iter().enumerate() {
8392 let name = match item {
8393 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8394 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8395 SelectItem::Wildcard => alloc::format!("col{i}"),
8396 };
8397 out.push(ColumnSchema::new(name, DataType::Text, true));
8398 }
8399 Ok(out)
8400 }
8401 }
8402 }
8403
8404 fn execute_readonly_select_for_lateral_probe(
8410 &self,
8411 inner: &SelectStatement,
8412 ) -> Result<QueryResult, EngineError> {
8413 self.exec_bare_select_cancel(inner, CancelToken::none())
8414 }
8415
8416 fn materialise_lateral_for_outer(
8422 &self,
8423 inner: &SelectStatement,
8424 outer_schema: &[ColumnSchema],
8425 outer_row: &Row,
8426 ) -> Result<Vec<Row>, EngineError> {
8427 let mut substituted = inner.clone();
8428 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8429 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8430 match result {
8431 QueryResult::Rows { rows, .. } => Ok(rows),
8432 _ => Err(EngineError::Unsupported(
8433 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8434 )),
8435 }
8436 }
8437
8438 fn exec_joined_select(
8439 &self,
8440 stmt: &SelectStatement,
8441 from: &FromClause,
8442 ) -> Result<QueryResult, EngineError> {
8443 let (combined_schema, filtered) =
8451 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8452 let ctx = EvalContext::new(&combined_schema, None);
8453 if aggregate::uses_aggregate(stmt) {
8456 let refs: Vec<&Row> = filtered.iter().collect();
8457 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8458 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8459 return Ok(QueryResult::Rows {
8460 columns: agg.columns,
8461 rows: agg.rows,
8462 });
8463 }
8464
8465 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8466 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8467 for row in &filtered {
8468 let mut values = Vec::with_capacity(projection.len());
8469 for p in &projection {
8470 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8471 }
8472 let order_keys = if stmt.order_by.is_empty() {
8473 Vec::new()
8474 } else {
8475 build_order_keys(&stmt.order_by, row, &ctx)?
8476 };
8477 tagged.push((order_keys, Row::new(values)));
8478 }
8479 if !stmt.order_by.is_empty() {
8480 let keep = if stmt.distinct {
8481 None
8482 } else {
8483 stmt.limit_literal()
8484 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8485 };
8486 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8487 partial_sort_tagged(&mut tagged, keep, &descs);
8488 }
8489 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8490 if stmt.distinct {
8491 output_rows = dedup_rows(output_rows);
8492 }
8493 apply_offset_and_limit(
8494 &mut output_rows,
8495 stmt.offset_literal(),
8496 stmt.limit_literal(),
8497 );
8498 let columns: Vec<ColumnSchema> = projection
8499 .into_iter()
8500 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8501 .collect();
8502 Ok(QueryResult::Rows {
8503 columns,
8504 rows: output_rows,
8505 })
8506 }
8507}
8508
8509#[derive(Debug, Clone)]
8512struct ProjectedItem {
8513 expr: Expr,
8514 output_name: String,
8515 ty: DataType,
8516 nullable: bool,
8517}
8518
8519fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8525 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8526 for r in rows {
8527 if !out.iter().any(|seen| seen == &r) {
8528 out.push(r);
8529 }
8530 }
8531 out
8532}
8533
8534fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8538 match v {
8539 Value::Null => Ok(f64::INFINITY),
8540 Value::SmallInt(n) => Ok(f64::from(*n)),
8541 Value::Int(n) => Ok(f64::from(*n)),
8542 Value::Date(d) => Ok(f64::from(*d)),
8543 #[allow(clippy::cast_precision_loss)]
8544 Value::Timestamp(t) => Ok(*t as f64),
8545 #[allow(clippy::cast_precision_loss)]
8548 Value::Time(us) => Ok(*us as f64),
8549 Value::Year(y) => Ok(f64::from(*y)),
8553 #[allow(clippy::cast_precision_loss)]
8558 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8559 #[allow(clippy::cast_precision_loss)]
8561 Value::Money(c) => Ok(*c as f64),
8562 Value::Range { .. } => Err(EngineError::Unsupported(
8565 "ORDER BY of a range value is not supported in v7.17.0".into(),
8566 )),
8567 Value::Hstore(_) => Err(EngineError::Unsupported(
8569 "ORDER BY of a hstore value is not supported".into(),
8570 )),
8571 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8573 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8574 ),
8575 #[allow(clippy::cast_precision_loss)]
8576 Value::Numeric { scaled, scale } => {
8577 let mut divisor = 1.0_f64;
8583 for _ in 0..*scale {
8584 divisor *= 10.0;
8585 }
8586 Ok((*scaled as f64) / divisor)
8587 }
8588 #[allow(clippy::cast_precision_loss)]
8589 Value::BigInt(n) => Ok(*n as f64),
8590 Value::Float(x) => Ok(*x),
8591 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8592 Value::Text(s) => {
8593 let mut key: u64 = 0;
8597 for &b in s.as_bytes().iter().take(8) {
8598 key = (key << 8) | u64::from(b);
8599 }
8600 #[allow(clippy::cast_precision_loss)]
8601 Ok(key as f64)
8602 }
8603 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8604 Err(EngineError::Unsupported(
8605 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8606 ))
8607 }
8608 Value::Interval { .. } => Err(EngineError::Unsupported(
8609 "ORDER BY of an INTERVAL is not supported in v2.11 \
8610 (months vs micros has no single canonical ordering)"
8611 .into(),
8612 )),
8613 Value::Json(_) => Err(EngineError::Unsupported(
8614 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8615 )),
8616 _ => Err(EngineError::Unsupported(
8620 "ORDER BY of this value type is not supported".into(),
8621 )),
8622 }
8623}
8624
8625fn try_nsw_knn(
8639 stmt: &SelectStatement,
8640 table: &Table,
8641 schema_cols: &[ColumnSchema],
8642 table_alias: &str,
8643) -> Option<Vec<usize>> {
8644 if stmt.distinct {
8645 return None;
8646 }
8647 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8648 if limit == 0 {
8649 return None;
8650 }
8651 if stmt.order_by.len() != 1 {
8655 return None;
8656 }
8657 let order = &stmt.order_by[0];
8658 if order.desc {
8662 return None;
8663 }
8664 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8665 return None;
8666 };
8667 let metric = match op {
8668 BinOp::L2Distance => spg_storage::NswMetric::L2,
8669 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8670 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8671 _ => return None,
8672 };
8673 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8675 (lhs.as_ref(), rhs.as_ref())
8676 else {
8677 return None;
8678 };
8679 if let Some(q) = &col.qualifier
8680 && q != table_alias
8681 {
8682 return None;
8683 }
8684 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8685 let query = literal_to_vector(literal)?;
8686 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8687 if let Some(where_expr) = &stmt.where_ {
8688 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8692 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8693 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8694 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8695 for i in candidates {
8696 let row = &table.rows()[i];
8697 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8698 if matches!(cond, Value::Bool(true)) {
8699 kept.push(i);
8700 if kept.len() >= limit {
8701 break;
8702 }
8703 }
8704 }
8705 Some(kept)
8706 } else {
8707 Some(spg_storage::nsw_query(
8708 table, &idx.name, &query, limit, metric,
8709 ))
8710 }
8711}
8712
8713const NSW_OVER_FETCH_FLOOR: usize = 32;
8717
8718fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8721 match e {
8722 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8723 Expr::Cast { expr, .. } => literal_to_vector(expr),
8724 _ => None,
8725 }
8726}
8727
8728fn materialise_in_order(
8732 stmt: &SelectStatement,
8733 table: &Table,
8734 schema_cols: &[ColumnSchema],
8735 table_alias: &str,
8736 ordered_rows: &[usize],
8737) -> Result<QueryResult, EngineError> {
8738 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8739 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8740 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8741 for &i in ordered_rows {
8742 let row = &table.rows()[i];
8743 let mut values = Vec::with_capacity(projection.len());
8744 for p in &projection {
8745 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8746 }
8747 output_rows.push(Row::new(values));
8748 }
8749 apply_offset_and_limit(
8750 &mut output_rows,
8751 stmt.offset_literal(),
8752 stmt.limit_literal(),
8753 );
8754 let columns: Vec<ColumnSchema> = projection
8755 .into_iter()
8756 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8757 .collect();
8758 Ok(QueryResult::Rows {
8759 columns,
8760 rows: output_rows,
8761 })
8762}
8763
8764fn try_index_seek_positions(
8777 where_expr: &Expr,
8778 schema_cols: &[ColumnSchema],
8779 table: &Table,
8780 table_alias: &str,
8781) -> Option<Vec<usize>> {
8782 if let Expr::Binary {
8783 lhs,
8784 op: BinOp::And,
8785 rhs,
8786 } = where_expr
8787 {
8788 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8789 return Some(p);
8790 }
8791 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8792 }
8793 let Expr::Binary {
8794 lhs,
8795 op: BinOp::Eq,
8796 rhs,
8797 } = where_expr
8798 else {
8799 return None;
8800 };
8801 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8802 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8803 let idx = table.index_on(col_pos)?;
8804 let key = IndexKey::from_value(&value)?;
8805 let locators = idx.lookup_eq(&key);
8806 let mut out = Vec::with_capacity(locators.len());
8807 for loc in locators {
8808 match *loc {
8809 spg_storage::RowLocator::Hot(i) => out.push(i),
8810 spg_storage::RowLocator::Cold { .. } => return None,
8811 }
8812 }
8813 Some(out)
8814}
8815
8816fn try_index_seek<'a>(
8817 where_expr: &Expr,
8818 schema_cols: &[ColumnSchema],
8819 catalog: &'a Catalog,
8820 table: &'a Table,
8821 table_alias: &str,
8822) -> Option<Vec<Cow<'a, Row>>> {
8823 if let Expr::Binary {
8830 lhs,
8831 op: BinOp::And,
8832 rhs,
8833 } = where_expr
8834 {
8835 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8838 return Some(rows);
8839 }
8840 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8841 }
8842 let Expr::Binary {
8843 lhs,
8844 op: BinOp::Eq,
8845 rhs,
8846 } = where_expr
8847 else {
8848 return None;
8849 };
8850 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8851 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8852 let idx = table.index_on(col_pos)?;
8853 let key = IndexKey::from_value(&value)?;
8854 let locators = idx.lookup_eq(&key);
8855 let table_name = table.schema().name.as_str();
8856 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8864 for loc in locators {
8865 match *loc {
8866 spg_storage::RowLocator::Hot(i) => {
8867 if let Some(row) = table.rows().get(i) {
8868 out.push(Cow::Borrowed(row));
8869 }
8870 }
8871 spg_storage::RowLocator::Cold { segment_id, .. } => {
8872 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8873 out.push(Cow::Owned(row));
8874 }
8875 }
8876 }
8877 }
8878 Some(out)
8879}
8880
8881fn try_gin_seek<'a>(
8900 where_expr: &Expr,
8901 schema_cols: &[ColumnSchema],
8902 catalog: &'a Catalog,
8903 table: &'a Table,
8904 table_alias: &str,
8905 ctx: &eval::EvalContext<'_>,
8906) -> Option<Vec<Cow<'a, Row>>> {
8907 if let Expr::Binary {
8908 lhs,
8909 op: BinOp::And,
8910 rhs,
8911 } = where_expr
8912 {
8913 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8914 return Some(rows);
8915 }
8916 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8917 }
8918 if let Expr::Binary {
8927 lhs,
8928 op: BinOp::Or,
8929 rhs,
8930 } = where_expr
8931 {
8932 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8933 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8934 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8935 out.extend(left);
8936 out.extend(right);
8937 return Some(out);
8938 }
8939 let Expr::Binary {
8940 lhs,
8941 op: BinOp::TsMatch,
8942 rhs,
8943 } = where_expr
8944 else {
8945 return None;
8946 };
8947 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8952 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8953 let idx = table
8960 .indices()
8961 .iter()
8962 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8963 let candidates = gin_query_candidates(idx, &query)?;
8964 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8966 for loc in candidates {
8967 match loc {
8968 spg_storage::RowLocator::Hot(i) => {
8969 if let Some(row) = table.rows().get(i) {
8970 out.push(Cow::Borrowed(row));
8971 }
8972 }
8973 spg_storage::RowLocator::Cold { .. } => {}
8980 }
8981 }
8982 Some(out)
8983}
8984
8985fn try_trgm_seek<'a>(
9001 where_expr: &Expr,
9002 schema_cols: &[ColumnSchema],
9003 table: &'a Table,
9004 table_alias: &str,
9005) -> Option<Vec<Cow<'a, Row>>> {
9006 if let Expr::Binary {
9007 lhs,
9008 op: BinOp::And,
9009 rhs,
9010 } = where_expr
9011 {
9012 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9013 return Some(rows);
9014 }
9015 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9016 }
9017 let Expr::Like { expr, pattern, .. } = where_expr else {
9023 return None;
9024 };
9025 let Expr::Column(c) = expr.as_ref() else {
9027 return None;
9028 };
9029 if let Some(q) = &c.qualifier
9030 && q != table_alias
9031 {
9032 return None;
9033 }
9034 let col_pos = schema_cols
9035 .iter()
9036 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9037 let idx = table
9039 .indices()
9040 .iter()
9041 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9042 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9046 return None;
9047 };
9048 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9049 let mut iter = trigrams.iter();
9052 let first = iter.next()?;
9053 let mut acc: Vec<spg_storage::RowLocator> = {
9054 let mut v = idx.gin_trgm_lookup(first).to_vec();
9055 v.sort_by_key(locator_sort_key);
9056 v.dedup_by_key(|l| locator_sort_key(l));
9057 v
9058 };
9059 for tri in iter {
9060 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9061 next.sort_by_key(locator_sort_key);
9062 next.dedup_by_key(|l| locator_sort_key(l));
9063 let mut merged: Vec<spg_storage::RowLocator> =
9065 Vec::with_capacity(acc.len().min(next.len()));
9066 let (mut i, mut j) = (0usize, 0usize);
9067 while i < acc.len() && j < next.len() {
9068 let lk = locator_sort_key(&acc[i]);
9069 let rk = locator_sort_key(&next[j]);
9070 match lk.cmp(&rk) {
9071 core::cmp::Ordering::Less => i += 1,
9072 core::cmp::Ordering::Greater => j += 1,
9073 core::cmp::Ordering::Equal => {
9074 merged.push(acc[i]);
9075 i += 1;
9076 j += 1;
9077 }
9078 }
9079 }
9080 acc = merged;
9081 if acc.is_empty() {
9082 break;
9083 }
9084 }
9085 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9086 for loc in acc {
9087 if let spg_storage::RowLocator::Hot(i) = loc
9088 && let Some(row) = table.rows().get(i)
9089 {
9090 out.push(Cow::Borrowed(row));
9091 }
9092 }
9094 Some(out)
9095}
9096
9097fn resolve_gin_col_query(
9103 col_side: &Expr,
9104 query_side: &Expr,
9105 schema_cols: &[ColumnSchema],
9106 table_alias: &str,
9107 ctx: &eval::EvalContext<'_>,
9108) -> Option<(usize, spg_storage::TsQueryAst)> {
9109 let column = match col_side {
9114 Expr::Column(c) => c,
9115 Expr::FunctionCall { name, args }
9116 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9117 {
9118 if let Expr::Column(c) = args.last().unwrap() {
9122 c
9123 } else {
9124 return None;
9125 }
9126 }
9127 _ => return None,
9128 };
9129 let c = column;
9130 if let Some(q) = &c.qualifier
9131 && q != table_alias
9132 {
9133 return None;
9134 }
9135 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9136 let empty_row = Row::new(Vec::new());
9140 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9141 let Value::TsQuery(q) = v else { return None };
9142 Some((pos, q))
9143}
9144
9145fn gin_query_candidates(
9156 idx: &spg_storage::Index,
9157 query: &spg_storage::TsQueryAst,
9158) -> Option<Vec<spg_storage::RowLocator>> {
9159 use spg_storage::TsQueryAst;
9160 match query {
9161 TsQueryAst::Term { word, .. } => {
9162 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9163 v.sort_by_key(locator_sort_key);
9164 v.dedup_by_key(|l| locator_sort_key(l));
9165 Some(v)
9166 }
9167 TsQueryAst::And(l, r) => {
9168 let mut left = gin_query_candidates(idx, l)?;
9169 let mut right = gin_query_candidates(idx, r)?;
9170 left.sort_by_key(locator_sort_key);
9171 right.sort_by_key(locator_sort_key);
9172 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9174 let (mut i, mut j) = (0usize, 0usize);
9175 while i < left.len() && j < right.len() {
9176 let lk = locator_sort_key(&left[i]);
9177 let rk = locator_sort_key(&right[j]);
9178 match lk.cmp(&rk) {
9179 core::cmp::Ordering::Less => i += 1,
9180 core::cmp::Ordering::Greater => j += 1,
9181 core::cmp::Ordering::Equal => {
9182 out.push(left[i]);
9183 i += 1;
9184 j += 1;
9185 }
9186 }
9187 }
9188 Some(out)
9189 }
9190 TsQueryAst::Or(l, r) => {
9191 let mut out = gin_query_candidates(idx, l)?;
9192 out.extend(gin_query_candidates(idx, r)?);
9193 out.sort_by_key(locator_sort_key);
9194 out.dedup_by_key(|l| locator_sort_key(l));
9195 Some(out)
9196 }
9197 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9202 }
9203}
9204
9205fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9210 match *l {
9211 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9212 spg_storage::RowLocator::Cold {
9213 segment_id,
9214 page_offset,
9215 } => (1, u64::from(segment_id), u64::from(page_offset)),
9216 }
9217}
9218
9219fn try_pk_predicate(
9231 where_expr: &Expr,
9232 schema_cols: &[ColumnSchema],
9233 table_alias: &str,
9234) -> Option<(usize, IndexKey)> {
9235 let Expr::Binary {
9236 lhs,
9237 op: BinOp::Eq,
9238 rhs,
9239 } = where_expr
9240 else {
9241 return None;
9242 };
9243 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9244 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9245 let key = IndexKey::from_value(&value)?;
9246 Some((col_pos, key))
9247}
9248
9249fn resolve_col_literal_pair(
9250 col_side: &Expr,
9251 lit_side: &Expr,
9252 schema_cols: &[ColumnSchema],
9253 table_alias: &str,
9254) -> Option<(usize, Value)> {
9255 let Expr::Column(c) = col_side else {
9256 return None;
9257 };
9258 if let Some(q) = &c.qualifier
9259 && q != table_alias
9260 {
9261 return None;
9262 }
9263 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9264 let Expr::Literal(l) = lit_side else {
9265 return None;
9266 };
9267 let v = match l {
9268 Literal::Integer(n) => {
9269 if let Ok(small) = i32::try_from(*n) {
9270 Value::Int(small)
9271 } else {
9272 Value::BigInt(*n)
9273 }
9274 }
9275 Literal::Float(x) => Value::Float(*x),
9276 Literal::String(s) => Value::Text(s.clone()),
9277 Literal::Bool(b) => Value::Bool(*b),
9278 Literal::Null => Value::Null,
9279 Literal::Vector(_)
9282 | Literal::Interval { .. }
9283 | Literal::TextArray(_)
9284 | Literal::IntArray(_)
9285 | Literal::BigIntArray(_) => return None,
9286 };
9287 Some((pos, v))
9288}
9289
9290fn resolve_projection_column<'a>(
9295 c: &ColumnName,
9296 schema_cols: &'a [ColumnSchema],
9297 table_alias: &str,
9298) -> Result<&'a ColumnSchema, EngineError> {
9299 if let Some(q) = &c.qualifier {
9300 let composite = alloc::format!("{q}.{name}", name = c.name);
9301 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9302 return Ok(s);
9303 }
9304 if q == table_alias
9307 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9308 {
9309 return Ok(s);
9310 }
9311 let prefix = alloc::format!("{q}.");
9315 let qualifier_known =
9316 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9317 if !qualifier_known {
9318 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9319 qualifier: q.clone(),
9320 }));
9321 }
9322 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9323 name: c.name.clone(),
9324 }));
9325 }
9326 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9327 return Ok(s);
9328 }
9329 let suffix = alloc::format!(".{name}", name = c.name);
9330 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9331 let first = matches.next();
9332 let extra = matches.next();
9333 match (first, extra) {
9334 (Some(s), None) => Ok(s),
9335 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9336 detail: alloc::format!("ambiguous column reference: {}", c.name),
9337 })),
9338 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9339 name: c.name.clone(),
9340 })),
9341 }
9342}
9343
9344fn build_projection(
9345 items: &[SelectItem],
9346 schema_cols: &[ColumnSchema],
9347 table_alias: &str,
9348) -> Result<Vec<ProjectedItem>, EngineError> {
9349 let mut out = Vec::new();
9350 for item in items {
9351 match item {
9352 SelectItem::Wildcard => {
9353 for col in schema_cols {
9354 out.push(ProjectedItem {
9355 expr: Expr::Column(ColumnName {
9356 qualifier: None,
9357 name: col.name.clone(),
9358 }),
9359 output_name: col.name.clone(),
9360 ty: col.ty,
9361 nullable: col.nullable,
9362 });
9363 }
9364 }
9365 SelectItem::Expr { expr, alias } => {
9366 if let Expr::Column(c) = expr {
9373 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9374 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9375 out.push(ProjectedItem {
9376 expr: expr.clone(),
9377 output_name,
9378 ty: sch.ty,
9379 nullable: sch.nullable,
9380 });
9381 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9382 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9383 out.push(ProjectedItem {
9384 expr: expr.clone(),
9385 output_name,
9386 ty: shape.ty,
9387 nullable: shape.nullable,
9388 });
9389 } else {
9390 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9391 out.push(ProjectedItem {
9392 expr: expr.clone(),
9393 output_name,
9394 ty: DataType::Text,
9395 nullable: true,
9396 });
9397 }
9398 }
9399 }
9400 }
9401 Ok(out)
9402}
9403
9404fn numeric_from_integer(
9408 n: i128,
9409 precision: u8,
9410 scale: u8,
9411 col_name: &str,
9412) -> Result<Value, EngineError> {
9413 let factor = pow10_i128(scale);
9414 let scaled = n.checked_mul(factor).ok_or_else(|| {
9415 EngineError::Unsupported(alloc::format!(
9416 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9417 ))
9418 })?;
9419 check_precision(scaled, precision, col_name)?;
9420 Ok(Value::Numeric { scaled, scale })
9421}
9422
9423#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9426fn numeric_from_float(
9427 x: f64,
9428 precision: u8,
9429 scale: u8,
9430 col_name: &str,
9431) -> Result<Value, EngineError> {
9432 if !x.is_finite() {
9433 return Err(EngineError::Unsupported(alloc::format!(
9434 "cannot store non-finite float in NUMERIC column `{col_name}`"
9435 )));
9436 }
9437 let mut factor = 1.0_f64;
9438 for _ in 0..scale {
9439 factor *= 10.0;
9440 }
9441 let shifted = x * factor;
9446 let biased = if shifted >= 0.0 {
9447 shifted + 0.5
9448 } else {
9449 shifted - 0.5
9450 };
9451 if !(-1e38..=1e38).contains(&biased) {
9454 return Err(EngineError::Unsupported(alloc::format!(
9455 "value {x} overflows NUMERIC range for column `{col_name}`"
9456 )));
9457 }
9458 let scaled = biased as i128;
9459 check_precision(scaled, precision, col_name)?;
9460 Ok(Value::Numeric { scaled, scale })
9461}
9462
9463fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9470 let s = s.trim();
9471 if s.is_empty() {
9472 return None;
9473 }
9474 let (negative, rest) = match s.as_bytes()[0] {
9475 b'-' => (true, &s[1..]),
9476 b'+' => (false, &s[1..]),
9477 _ => (false, s),
9478 };
9479 if rest.is_empty() {
9480 return None;
9481 }
9482 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9486 return None;
9487 }
9488 let (int_part, frac_part) = match rest.find('.') {
9489 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9490 None => (rest, ""),
9491 };
9492 if int_part.is_empty() && frac_part.is_empty() {
9493 return None;
9494 }
9495 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9496 return None;
9497 }
9498 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9499 return None;
9500 }
9501 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9502 if scale_u32 > u32::from(u8::MAX) {
9503 return None;
9504 }
9505 let scale = scale_u32 as u8;
9506 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9507 if negative {
9508 digits.push('-');
9509 }
9510 digits.push_str(int_part);
9511 digits.push_str(frac_part);
9512 let digits = if digits == "-" {
9514 return None;
9515 } else if digits.is_empty() {
9516 "0"
9517 } else {
9518 digits.as_str()
9519 };
9520 let mantissa: i128 = digits.parse().ok()?;
9521 Some((mantissa, scale))
9522}
9523
9524fn numeric_rescale(
9527 scaled: i128,
9528 src_scale: u8,
9529 precision: u8,
9530 dst_scale: u8,
9531 col_name: &str,
9532) -> Result<Value, EngineError> {
9533 let new_scaled = if dst_scale >= src_scale {
9534 let bump = pow10_i128(dst_scale - src_scale);
9535 scaled.checked_mul(bump).ok_or_else(|| {
9536 EngineError::Unsupported(alloc::format!(
9537 "overflow rescaling NUMERIC for column `{col_name}`"
9538 ))
9539 })?
9540 } else {
9541 let drop = pow10_i128(src_scale - dst_scale);
9542 let half = drop / 2;
9543 if scaled >= 0 {
9544 (scaled + half) / drop
9545 } else {
9546 (scaled - half) / drop
9547 }
9548 };
9549 check_precision(new_scaled, precision, col_name)?;
9550 Ok(Value::Numeric {
9551 scaled: new_scaled,
9552 scale: dst_scale,
9553 })
9554}
9555
9556const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9559 if scale == 0 {
9560 return scaled;
9561 }
9562 let factor = pow10_i128_const(scale);
9563 scaled / factor
9564}
9565
9566fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9570 if precision == 0 {
9571 return Ok(());
9572 }
9573 let limit = pow10_i128(precision);
9574 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9575 return Err(EngineError::Unsupported(alloc::format!(
9576 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9577 )));
9578 }
9579 Ok(())
9580}
9581
9582const fn pow10_i128_const(p: u8) -> i128 {
9583 let mut acc: i128 = 1;
9584 let mut i = 0;
9585 while i < p {
9586 acc *= 10;
9587 i += 1;
9588 }
9589 acc
9590}
9591
9592fn pow10_i128(p: u8) -> i128 {
9593 pow10_i128_const(p)
9594}
9595
9596impl Engine {
9611 #[allow(
9622 clippy::too_many_lines,
9623 clippy::type_complexity,
9624 clippy::needless_range_loop
9625 )] fn exec_select_with_window(
9627 &self,
9628 stmt: &SelectStatement,
9629 cancel: CancelToken<'_>,
9630 ) -> Result<QueryResult, EngineError> {
9631 let from = stmt.from.as_ref().ok_or_else(|| {
9632 EngineError::Unsupported("window functions require a FROM clause".into())
9633 })?;
9634 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9643 let filtered: Vec<Row>;
9644 if from.joins.is_empty() {
9645 let primary = &from.primary;
9646 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9647 StorageError::TableNotFound {
9648 name: primary.name.clone(),
9649 }
9650 })?;
9651 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9652 schema_cols_owned = table.schema().columns.clone();
9653 alias_opt = Some(alias);
9654 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9659 let mut owned: Vec<Row> = Vec::new();
9660 for (i, row) in table.rows().iter().enumerate() {
9661 if i.is_multiple_of(256) {
9662 cancel.check()?;
9663 }
9664 if let Some(w) = &stmt.where_ {
9665 let cond = eval::eval_expr(w, row, &ctx)?;
9666 if !matches!(cond, Value::Bool(true)) {
9667 continue;
9668 }
9669 }
9670 owned.push(row.clone());
9671 }
9672 filtered = owned;
9673 } else {
9674 let (combined_schema, rows) =
9675 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9676 schema_cols_owned = combined_schema;
9677 alias_opt = None;
9678 filtered = rows;
9679 }
9680 let schema_cols = &schema_cols_owned;
9681 let ctx = self.ev_ctx(schema_cols, alias_opt);
9682 let alias = alias_opt.unwrap_or("");
9683 let n_rows = filtered.len();
9684 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9688
9689 let mut window_nodes: Vec<Expr> = Vec::new();
9691 for item in &stmt.items {
9692 if let SelectItem::Expr { expr, .. } = item {
9693 collect_window_nodes(expr, &mut window_nodes);
9694 }
9695 }
9696
9697 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9700 for wnode in &window_nodes {
9701 let Expr::WindowFunction {
9702 name,
9703 args,
9704 partition_by,
9705 order_by,
9706 frame,
9707 null_treatment,
9708 } = wnode
9709 else {
9710 unreachable!("collect_window_nodes pushes only WindowFunction");
9711 };
9712 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9714 Vec::with_capacity(n_rows);
9715 for (i, row) in filtered.iter().enumerate() {
9716 let pkey: Vec<Value> = partition_by
9717 .iter()
9718 .map(|p| eval::eval_expr(p, row, &ctx))
9719 .collect::<Result<_, _>>()?;
9720 let okey: Vec<(Value, bool)> = order_by
9721 .iter()
9722 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9723 .collect::<Result<_, _>>()?;
9724 indexed.push((pkey, okey, i));
9725 }
9726 indexed.sort_by(|a, b| {
9729 let p_cmp = partition_key_cmp(&a.0, &b.0);
9730 if p_cmp != core::cmp::Ordering::Equal {
9731 return p_cmp;
9732 }
9733 order_key_cmp(&a.1, &b.1)
9734 });
9735 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9737 let mut p_start = 0;
9738 while p_start < indexed.len() {
9739 let mut p_end = p_start + 1;
9740 while p_end < indexed.len()
9741 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9742 == core::cmp::Ordering::Equal
9743 {
9744 p_end += 1;
9745 }
9746 compute_window_partition(
9748 name,
9749 args,
9750 !order_by.is_empty(),
9751 frame.as_ref(),
9752 *null_treatment,
9753 &indexed[p_start..p_end],
9754 &filtered_refs,
9755 &ctx,
9756 &mut out_vals,
9757 )?;
9758 p_start = p_end;
9759 }
9760 win_vals.push(out_vals);
9761 }
9762
9763 let mut ext_cols = schema_cols.clone();
9765 for i in 0..window_nodes.len() {
9766 ext_cols.push(ColumnSchema::new(
9767 alloc::format!("__win_{i}"),
9768 DataType::Text, true,
9770 ));
9771 }
9772 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9774 for i in 0..n_rows {
9775 let mut values = filtered[i].values.clone();
9776 for w in 0..window_nodes.len() {
9777 values.push(win_vals[w][i].clone());
9778 }
9779 ext_rows.push(Row::new(values));
9780 }
9781 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9783 for item in &stmt.items {
9784 let new_item = match item {
9785 SelectItem::Wildcard => SelectItem::Wildcard,
9786 SelectItem::Expr { expr, alias } => {
9787 let mut e = expr.clone();
9788 rewrite_window_to_columns(&mut e, &window_nodes);
9789 SelectItem::Expr {
9790 expr: e,
9791 alias: alias.clone(),
9792 }
9793 }
9794 };
9795 rewritten_items.push(new_item);
9796 }
9797
9798 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9804 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9805 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9806 for (i, row) in ext_rows.iter().enumerate() {
9807 if i.is_multiple_of(256) {
9808 cancel.check()?;
9809 }
9810 let mut values = Vec::with_capacity(projection.len());
9811 for p in &projection {
9812 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9813 }
9814 let order_keys = if stmt.order_by.is_empty() {
9815 Vec::new()
9816 } else {
9817 let mut keys = Vec::with_capacity(stmt.order_by.len());
9818 for o in &stmt.order_by {
9819 let mut e = o.expr.clone();
9820 rewrite_window_to_columns(&mut e, &window_nodes);
9821 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9822 keys.push(value_to_order_key(&key)?);
9823 }
9824 keys
9825 };
9826 tagged.push((order_keys, Row::new(values)));
9827 }
9828 if !stmt.order_by.is_empty() {
9830 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9831 sort_by_keys(&mut tagged, &descs);
9832 }
9833 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9834 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9835 let final_cols: Vec<ColumnSchema> = projection
9836 .into_iter()
9837 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9838 .collect();
9839 Ok(QueryResult::Rows {
9840 columns: final_cols,
9841 rows: out_rows,
9842 })
9843 }
9844
9845 fn exec_select_with_meta_views(
9862 &self,
9863 stmt: &SelectStatement,
9864 cancel: CancelToken<'_>,
9865 ) -> Result<QueryResult, EngineError> {
9866 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9867 collect_meta_view_names(stmt, &mut needed);
9868 let mut catalog = self.active_catalog().clone();
9869 for view in &needed {
9870 if catalog.get(view).is_some() {
9871 continue;
9872 }
9873 match view.as_str() {
9874 "__spg_info_columns" => {
9875 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9876 materialise_meta_view(&mut catalog, view, schema, rows)?;
9877 }
9878 "__spg_info_tables" => {
9879 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9880 materialise_meta_view(&mut catalog, view, schema, rows)?;
9881 }
9882 "__spg_pg_class" => {
9883 let (schema, rows) = synth_pg_class(self.active_catalog());
9884 materialise_meta_view(&mut catalog, view, schema, rows)?;
9885 }
9886 "__spg_pg_attribute" => {
9887 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9888 materialise_meta_view(&mut catalog, view, schema, rows)?;
9889 }
9890 "__spg_pg_type" => {
9893 let (schema, rows) = synth_pg_type(self.active_catalog());
9894 materialise_meta_view(&mut catalog, view, schema, rows)?;
9895 }
9896 "__spg_pg_proc" => {
9899 let (schema, rows) = synth_pg_proc(self.active_catalog());
9900 materialise_meta_view(&mut catalog, view, schema, rows)?;
9901 }
9902 "__spg_pg_namespace" => {
9905 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9906 materialise_meta_view(&mut catalog, view, schema, rows)?;
9907 }
9908 "__spg_pg_indexes" => {
9911 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9912 materialise_meta_view(&mut catalog, view, schema, rows)?;
9913 }
9914 "__spg_pg_index" => {
9917 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9918 materialise_meta_view(&mut catalog, view, schema, rows)?;
9919 }
9920 "__spg_pg_constraint" => {
9923 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9924 materialise_meta_view(&mut catalog, view, schema, rows)?;
9925 }
9926 "__spg_pg_database" => {
9931 let (schema, rows) = synth_pg_database(self.active_catalog());
9932 materialise_meta_view(&mut catalog, view, schema, rows)?;
9933 }
9934 "__spg_pg_roles" | "__spg_pg_user" => {
9935 let (schema, rows) = synth_pg_roles(self);
9936 materialise_meta_view(&mut catalog, view, schema, rows)?;
9937 }
9938 "__spg_pg_views" => {
9942 let (schema, rows) = synth_pg_views(self.active_catalog());
9943 materialise_meta_view(&mut catalog, view, schema, rows)?;
9944 }
9945 "__spg_pg_matviews" => {
9949 let (schema, _) = synth_pg_views(self.active_catalog());
9950 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9951 }
9952 "__spg_pg_extension" => {
9955 let (schema, rows) = synth_pg_extension();
9956 materialise_meta_view(&mut catalog, view, schema, rows)?;
9957 }
9958 "__spg_pg_settings" => {
9960 let (schema, rows) = synth_pg_settings(self);
9961 materialise_meta_view(&mut catalog, view, schema, rows)?;
9962 }
9963 "__spg_info_key_column_usage" => {
9965 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9966 materialise_meta_view(&mut catalog, view, schema, rows)?;
9967 }
9968 "__spg_info_referential_constraints" => {
9970 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9971 materialise_meta_view(&mut catalog, view, schema, rows)?;
9972 }
9973 "__spg_info_statistics" => {
9975 let (schema, rows) = synth_info_statistics(self.active_catalog());
9976 materialise_meta_view(&mut catalog, view, schema, rows)?;
9977 }
9978 "__spg_info_routines" => {
9980 let (schema, rows) = synth_info_routines();
9981 materialise_meta_view(&mut catalog, view, schema, rows)?;
9982 }
9983 "__spg_mysql_user" => {
9985 let (schema, rows) = synth_mysql_user(self);
9986 materialise_meta_view(&mut catalog, view, schema, rows)?;
9987 }
9988 "__spg_mysql_db" => {
9989 let (schema, rows) = synth_mysql_db();
9990 materialise_meta_view(&mut catalog, view, schema, rows)?;
9991 }
9992 _ => {
9993 return Err(EngineError::Unsupported(alloc::format!(
9994 "meta view {view:?} is not yet materialisable; \
9995 v7.16.2 covers information_schema.columns / .tables \
9996 and pg_catalog.pg_class / pg_attribute; \
9997 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9998 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
9999 pg_user / pg_views / pg_matviews / pg_settings"
10000 )));
10001 }
10002 }
10003 }
10004 let mut temp = Engine::restore(catalog);
10005 if let Some(c) = self.clock {
10006 temp = temp.with_clock(c);
10007 }
10008 if let Some(f) = self.salt_fn {
10009 temp = temp.with_salt_fn(f);
10010 }
10011 temp.meta_views_materialised = true;
10012 temp.exec_select_cancel(stmt, cancel)
10013 }
10014
10015 fn exec_with_ctes(
10016 &self,
10017 stmt: &SelectStatement,
10018 cancel: CancelToken<'_>,
10019 ) -> Result<QueryResult, EngineError> {
10020 cancel.check()?;
10021 let mut catalog = self.active_catalog().clone();
10022 for cte in &stmt.ctes {
10023 if catalog.get(&cte.name).is_some() {
10024 return Err(EngineError::Unsupported(alloc::format!(
10025 "CTE name {:?} shadows an existing table; rename the CTE",
10026 cte.name
10027 )));
10028 }
10029 let (columns, rows) = if cte.recursive {
10030 self.materialise_recursive_cte(cte, &catalog, cancel)?
10031 } else {
10032 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
10033 let QueryResult::Rows { columns, rows } = body_result else {
10034 return Err(EngineError::Unsupported(alloc::format!(
10035 "CTE {:?} body did not return rows",
10036 cte.name
10037 )));
10038 };
10039 (columns, rows)
10040 };
10041 let inferred = infer_column_types(&columns, &rows);
10046 let mut columns = inferred;
10047 if !cte.column_overrides.is_empty() {
10049 if cte.column_overrides.len() != columns.len() {
10050 return Err(EngineError::Unsupported(alloc::format!(
10051 "CTE {:?} column list has {} names but body returns {} columns",
10052 cte.name,
10053 cte.column_overrides.len(),
10054 columns.len()
10055 )));
10056 }
10057 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10058 col.name.clone_from(name);
10059 }
10060 }
10061 let schema = TableSchema::new(cte.name.clone(), columns);
10062 catalog.create_table(schema).map_err(EngineError::Storage)?;
10063 let table = catalog
10064 .get_mut(&cte.name)
10065 .expect("just-created CTE table must exist");
10066 for row in rows {
10067 table.insert(row).map_err(EngineError::Storage)?;
10068 }
10069 }
10070 let mut body = stmt.clone();
10073 body.ctes = Vec::new();
10074 let mut temp = Engine::restore(catalog);
10075 if let Some(c) = self.clock {
10076 temp = temp.with_clock(c);
10077 }
10078 if let Some(f) = self.salt_fn {
10079 temp = temp.with_salt_fn(f);
10080 }
10081 temp.exec_select_cancel(&body, cancel)
10082 }
10083
10084 #[allow(clippy::too_many_lines)]
10094 fn materialise_recursive_cte(
10095 &self,
10096 cte: &spg_sql::ast::Cte,
10097 base_catalog: &Catalog,
10098 cancel: CancelToken<'_>,
10099 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10100 const MAX_TOTAL_ROWS: usize = 1_000_000;
10101 const MAX_ITERATIONS: usize = 100_000;
10102 cancel.check()?;
10103 if cte.body.unions.is_empty() {
10104 return Err(EngineError::Unsupported(alloc::format!(
10105 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10106 cte.name
10107 )));
10108 }
10109 let mut anchor = cte.body.clone();
10111 let union_terms = core::mem::take(&mut anchor.unions);
10112 anchor.ctes = Vec::new();
10113 if select_refers_to(&anchor, &cte.name) {
10115 return Err(EngineError::Unsupported(alloc::format!(
10116 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10117 cte.name
10118 )));
10119 }
10120 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10121 let QueryResult::Rows {
10122 columns: anchor_cols,
10123 rows: anchor_rows,
10124 } = anchor_result
10125 else {
10126 return Err(EngineError::Unsupported(alloc::format!(
10127 "WITH RECURSIVE {:?}: anchor did not return rows",
10128 cte.name
10129 )));
10130 };
10131 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10135 if !cte.column_overrides.is_empty() {
10136 if cte.column_overrides.len() != columns.len() {
10137 return Err(EngineError::Unsupported(alloc::format!(
10138 "CTE {:?} column list has {} names but anchor returns {} columns",
10139 cte.name,
10140 cte.column_overrides.len(),
10141 columns.len()
10142 )));
10143 }
10144 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10145 col.name.clone_from(name);
10146 }
10147 }
10148 let mut all_rows: Vec<Row> = anchor_rows.clone();
10149 let mut working_set: Vec<Row> = anchor_rows;
10150 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10151 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10154 if !all_union_all {
10155 for r in &all_rows {
10156 seen.insert(encode_row_key(r));
10157 }
10158 }
10159 for iter in 0..MAX_ITERATIONS {
10160 cancel.check()?;
10161 if working_set.is_empty() {
10162 break;
10163 }
10164 let mut iter_catalog = base_catalog.clone();
10166 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10167 iter_catalog
10168 .create_table(schema)
10169 .map_err(EngineError::Storage)?;
10170 {
10171 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10172 for row in &working_set {
10173 table.insert(row.clone()).map_err(EngineError::Storage)?;
10174 }
10175 }
10176 let mut iter_engine = Engine::restore(iter_catalog);
10177 if let Some(c) = self.clock {
10178 iter_engine = iter_engine.with_clock(c);
10179 }
10180 if let Some(f) = self.salt_fn {
10181 iter_engine = iter_engine.with_salt_fn(f);
10182 }
10183 let mut next_set: Vec<Row> = Vec::new();
10185 for (_, term) in &union_terms {
10186 let mut term = term.clone();
10187 term.ctes = Vec::new();
10188 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10189 let QueryResult::Rows {
10190 columns: rc,
10191 rows: rs,
10192 } = r
10193 else {
10194 return Err(EngineError::Unsupported(alloc::format!(
10195 "WITH RECURSIVE {:?}: recursive term did not return rows",
10196 cte.name
10197 )));
10198 };
10199 if rc.len() != columns.len() {
10200 return Err(EngineError::Unsupported(alloc::format!(
10201 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10202 cte.name,
10203 rc.len(),
10204 columns.len()
10205 )));
10206 }
10207 for row in rs {
10208 if !all_union_all {
10209 let key = encode_row_key(&row);
10210 if !seen.insert(key) {
10211 continue;
10212 }
10213 }
10214 next_set.push(row);
10215 }
10216 }
10217 if next_set.is_empty() {
10218 break;
10219 }
10220 all_rows.extend(next_set.iter().cloned());
10221 working_set = next_set;
10222 if all_rows.len() > MAX_TOTAL_ROWS {
10223 return Err(EngineError::Unsupported(alloc::format!(
10224 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10225 cte.name
10226 )));
10227 }
10228 if iter + 1 == MAX_ITERATIONS {
10229 return Err(EngineError::Unsupported(alloc::format!(
10230 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10231 cte.name
10232 )));
10233 }
10234 }
10235 Ok((columns, all_rows))
10236 }
10237
10238 fn resolve_select_subqueries(
10239 &self,
10240 stmt: &mut SelectStatement,
10241 cancel: CancelToken<'_>,
10242 ) -> Result<(), EngineError> {
10243 for item in &mut stmt.items {
10244 if let SelectItem::Expr { expr, .. } = item {
10245 self.resolve_expr_subqueries(expr, cancel)?;
10246 }
10247 }
10248 if let Some(w) = &mut stmt.where_ {
10249 self.resolve_expr_subqueries(w, cancel)?;
10250 }
10251 if let Some(gs) = &mut stmt.group_by {
10252 for g in gs {
10253 self.resolve_expr_subqueries(g, cancel)?;
10254 }
10255 }
10256 if let Some(h) = &mut stmt.having {
10257 self.resolve_expr_subqueries(h, cancel)?;
10258 }
10259 for o in &mut stmt.order_by {
10260 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10261 }
10262 for (_, peer) in &mut stmt.unions {
10263 self.resolve_select_subqueries(peer, cancel)?;
10264 }
10265 Ok(())
10266 }
10267
10268 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10270 &self,
10271 e: &mut Expr,
10272 cancel: CancelToken<'_>,
10273 ) -> Result<(), EngineError> {
10274 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10276 *e = replacement;
10277 return Ok(());
10278 }
10279 match e {
10280 Expr::Binary { lhs, rhs, .. } => {
10281 self.resolve_expr_subqueries(lhs, cancel)?;
10282 self.resolve_expr_subqueries(rhs, cancel)?;
10283 }
10284 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10285 self.resolve_expr_subqueries(expr, cancel)?;
10286 }
10287 Expr::FunctionCall { args, .. } => {
10288 for a in args {
10289 self.resolve_expr_subqueries(a, cancel)?;
10290 }
10291 }
10292 Expr::Like { expr, pattern, .. } => {
10293 self.resolve_expr_subqueries(expr, cancel)?;
10294 self.resolve_expr_subqueries(pattern, cancel)?;
10295 }
10296 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10297 Expr::WindowFunction {
10300 args,
10301 partition_by,
10302 order_by,
10303 ..
10304 } => {
10305 for a in args {
10306 self.resolve_expr_subqueries(a, cancel)?;
10307 }
10308 for p in partition_by {
10309 self.resolve_expr_subqueries(p, cancel)?;
10310 }
10311 for (e, _) in order_by {
10312 self.resolve_expr_subqueries(e, cancel)?;
10313 }
10314 }
10315 Expr::ScalarSubquery(_)
10319 | Expr::Exists { .. }
10320 | Expr::InSubquery { .. }
10321 | Expr::Literal(_)
10322 | Expr::Placeholder(_)
10323 | Expr::Column(_) => {}
10324 Expr::Array(items) => {
10326 for elem in items {
10327 self.resolve_expr_subqueries(elem, cancel)?;
10328 }
10329 }
10330 Expr::ArraySubscript { target, index } => {
10331 self.resolve_expr_subqueries(target, cancel)?;
10332 self.resolve_expr_subqueries(index, cancel)?;
10333 }
10334 Expr::AnyAll { expr, array, .. } => {
10335 self.resolve_expr_subqueries(expr, cancel)?;
10336 self.resolve_expr_subqueries(array, cancel)?;
10337 }
10338 Expr::Case {
10339 operand,
10340 branches,
10341 else_branch,
10342 } => {
10343 if let Some(o) = operand {
10344 self.resolve_expr_subqueries(o, cancel)?;
10345 }
10346 for (w, t) in branches {
10347 self.resolve_expr_subqueries(w, cancel)?;
10348 self.resolve_expr_subqueries(t, cancel)?;
10349 }
10350 if let Some(e) = else_branch {
10351 self.resolve_expr_subqueries(e, cancel)?;
10352 }
10353 }
10354 }
10355 Ok(())
10356 }
10357
10358 fn eval_expr_with_correlated(
10366 &self,
10367 expr: &Expr,
10368 row: &Row,
10369 ctx: &EvalContext<'_>,
10370 cancel: CancelToken<'_>,
10371 memo: Option<&mut memoize::MemoizeCache>,
10372 ) -> Result<Value, EngineError> {
10373 if !expr_has_subquery(expr) {
10374 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10375 }
10376 let mut e = expr.clone();
10377 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10378 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10379 }
10380
10381 fn resolve_correlated_in_expr(
10382 &self,
10383 e: &mut Expr,
10384 row: &Row,
10385 ctx: &EvalContext<'_>,
10386 cancel: CancelToken<'_>,
10387 mut memo: Option<&mut memoize::MemoizeCache>,
10388 ) -> Result<(), EngineError> {
10389 match e {
10390 Expr::ScalarSubquery(inner) => {
10391 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10396 subquery_repr: alloc::format!("{}", **inner),
10397 outer_values: row.values.clone(),
10398 });
10399 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10400 && let Some(cached) = cache.get(k)
10401 {
10402 *e = value_to_literal_expr(cached)?;
10403 return Ok(());
10404 }
10405 let mut s = (**inner).clone();
10406 substitute_outer_columns(&mut s, row, ctx);
10407 let r = self.exec_select_cancel(&s, cancel)?;
10408 let QueryResult::Rows { rows, .. } = r else {
10409 return Err(EngineError::Unsupported(
10410 "scalar subquery: inner did not return rows".into(),
10411 ));
10412 };
10413 let value = match rows.as_slice() {
10414 [] => Value::Null,
10415 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10416 _ => {
10417 return Err(EngineError::Unsupported(alloc::format!(
10418 "scalar subquery returned {} rows; expected 0 or 1",
10419 rows.len()
10420 )));
10421 }
10422 };
10423 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10424 cache.insert(k, value.clone());
10425 }
10426 *e = value_to_literal_expr(value)?;
10427 }
10428 Expr::Exists { subquery, negated } => {
10429 let mut s = (**subquery).clone();
10430 substitute_outer_columns(&mut s, row, ctx);
10431 let r = self.exec_select_cancel(&s, cancel)?;
10432 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10433 let bit = if *negated { !exists } else { exists };
10434 *e = Expr::Literal(Literal::Bool(bit));
10435 }
10436 Expr::InSubquery {
10437 expr: lhs,
10438 subquery,
10439 negated,
10440 } => {
10441 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10442 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10443 let mut s = (**subquery).clone();
10444 substitute_outer_columns(&mut s, row, ctx);
10445 let r = self.exec_select_cancel(&s, cancel)?;
10446 let QueryResult::Rows { columns, rows, .. } = r else {
10447 return Err(EngineError::Unsupported(
10448 "IN-subquery: inner did not return rows".into(),
10449 ));
10450 };
10451 if columns.len() != 1 {
10452 return Err(EngineError::Unsupported(alloc::format!(
10453 "IN-subquery must project exactly one column; got {}",
10454 columns.len()
10455 )));
10456 }
10457 let mut found = false;
10458 let mut any_null = false;
10459 for r0 in rows {
10460 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10461 if v.is_null() {
10462 any_null = true;
10463 continue;
10464 }
10465 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10466 found = true;
10467 break;
10468 }
10469 }
10470 let bit = if found {
10471 !*negated
10472 } else if any_null {
10473 return Err(EngineError::Unsupported(
10474 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10475 ));
10476 } else {
10477 *negated
10478 };
10479 *e = Expr::Literal(Literal::Bool(bit));
10480 }
10481 Expr::Binary { lhs, rhs, .. } => {
10482 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10483 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10484 }
10485 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10486 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10487 }
10488 Expr::Like { expr, pattern, .. } => {
10489 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10490 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10491 }
10492 Expr::FunctionCall { args, .. } => {
10493 for a in args {
10494 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10495 }
10496 }
10497 Expr::Extract { source, .. } => {
10498 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10499 }
10500 Expr::WindowFunction { .. }
10501 | Expr::Literal(_)
10502 | Expr::Placeholder(_)
10503 | Expr::Column(_) => {}
10504 Expr::Array(items) => {
10506 for elem in items {
10507 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10508 }
10509 }
10510 Expr::ArraySubscript { target, index } => {
10511 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10512 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10513 }
10514 Expr::AnyAll { expr, array, .. } => {
10515 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10516 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10517 }
10518 Expr::Case {
10519 operand,
10520 branches,
10521 else_branch,
10522 } => {
10523 if let Some(o) = operand {
10524 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10525 }
10526 for (w, t) in branches {
10527 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10528 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10529 }
10530 if let Some(e) = else_branch {
10531 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10532 }
10533 }
10534 }
10535 Ok(())
10536 }
10537
10538 fn subquery_replacement(
10539 &self,
10540 e: &Expr,
10541 cancel: CancelToken<'_>,
10542 ) -> Result<Option<Expr>, EngineError> {
10543 match e {
10544 Expr::ScalarSubquery(inner) => {
10545 let mut s = (**inner).clone();
10546 self.resolve_select_subqueries(&mut s, cancel)?;
10549 let r = match self.exec_bare_select_cancel(&s, cancel) {
10550 Ok(r) => r,
10551 Err(e) if is_correlation_error(&e) => return Ok(None),
10552 Err(e) => return Err(e),
10553 };
10554 let QueryResult::Rows { rows, .. } = r else {
10555 return Err(EngineError::Unsupported(
10556 "scalar subquery: inner statement did not return rows".into(),
10557 ));
10558 };
10559 let value = match rows.as_slice() {
10560 [] => Value::Null,
10561 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10562 _ => {
10563 return Err(EngineError::Unsupported(alloc::format!(
10564 "scalar subquery returned {} rows; expected 0 or 1",
10565 rows.len()
10566 )));
10567 }
10568 };
10569 Ok(Some(value_to_literal_expr(value)?))
10570 }
10571 Expr::Exists { subquery, negated } => {
10572 let mut s = (**subquery).clone();
10573 self.resolve_select_subqueries(&mut s, cancel)?;
10574 let r = match self.exec_bare_select_cancel(&s, cancel) {
10575 Ok(r) => r,
10576 Err(e) if is_correlation_error(&e) => return Ok(None),
10577 Err(e) => return Err(e),
10578 };
10579 let exists = match r {
10580 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10581 QueryResult::CommandOk { .. } => false,
10582 };
10583 let bit = if *negated { !exists } else { exists };
10584 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10585 }
10586 Expr::InSubquery {
10587 expr,
10588 subquery,
10589 negated,
10590 } => {
10591 let mut s = (**subquery).clone();
10592 self.resolve_select_subqueries(&mut s, cancel)?;
10593 let r = match self.exec_bare_select_cancel(&s, cancel) {
10594 Ok(r) => r,
10595 Err(e) if is_correlation_error(&e) => return Ok(None),
10596 Err(e) => return Err(e),
10597 };
10598 let QueryResult::Rows { columns, rows, .. } = r else {
10599 return Err(EngineError::Unsupported(
10600 "IN-subquery: inner statement did not return rows".into(),
10601 ));
10602 };
10603 if columns.len() != 1 {
10604 return Err(EngineError::Unsupported(alloc::format!(
10605 "IN-subquery must project exactly one column; got {}",
10606 columns.len()
10607 )));
10608 }
10609 let mut acc: Option<Expr> = None;
10612 for row in rows {
10613 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10614 let lit = value_to_literal_expr(v)?;
10615 let cmp = Expr::Binary {
10616 lhs: expr.clone(),
10617 op: BinOp::Eq,
10618 rhs: Box::new(lit),
10619 };
10620 acc = Some(match acc {
10621 None => cmp,
10622 Some(prev) => Expr::Binary {
10623 lhs: Box::new(prev),
10624 op: BinOp::Or,
10625 rhs: Box::new(cmp),
10626 },
10627 });
10628 }
10629 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10630 let final_expr = if *negated {
10631 Expr::Unary {
10632 op: UnOp::Not,
10633 expr: Box::new(combined),
10634 }
10635 } else {
10636 combined
10637 };
10638 Ok(Some(final_expr))
10639 }
10640 _ => Ok(None),
10641 }
10642 }
10643}
10644
10645fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10657 if let Some(from) = &stmt.from
10658 && from_refers_to(from, target)
10659 {
10660 return true;
10661 }
10662 for (_, peer) in &stmt.unions {
10663 if select_refers_to(peer, target) {
10664 return true;
10665 }
10666 }
10667 for item in &stmt.items {
10668 if let SelectItem::Expr { expr, .. } = item
10669 && expr_refers_to(expr, target)
10670 {
10671 return true;
10672 }
10673 }
10674 if let Some(w) = &stmt.where_
10675 && expr_refers_to(w, target)
10676 {
10677 return true;
10678 }
10679 false
10680}
10681
10682fn from_refers_to(from: &FromClause, target: &str) -> bool {
10683 if from.primary.name.eq_ignore_ascii_case(target) {
10684 return true;
10685 }
10686 from.joins
10687 .iter()
10688 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10689}
10690
10691fn expr_refers_to(e: &Expr, target: &str) -> bool {
10692 match e {
10693 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10694 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10695 select_refers_to(subquery, target)
10696 }
10697 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10698 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10699 expr_refers_to(expr, target)
10700 }
10701 Expr::Like { expr, pattern, .. } => {
10702 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10703 }
10704 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10705 Expr::Extract { source, .. } => expr_refers_to(source, target),
10706 Expr::WindowFunction {
10707 args,
10708 partition_by,
10709 order_by,
10710 ..
10711 } => {
10712 args.iter().any(|a| expr_refers_to(a, target))
10713 || partition_by.iter().any(|p| expr_refers_to(p, target))
10714 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10715 }
10716 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10717 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10718 Expr::ArraySubscript { target: t, index } => {
10719 expr_refers_to(t, target) || expr_refers_to(index, target)
10720 }
10721 Expr::AnyAll { expr, array, .. } => {
10722 expr_refers_to(expr, target) || expr_refers_to(array, target)
10723 }
10724 Expr::Case {
10725 operand,
10726 branches,
10727 else_branch,
10728 } => {
10729 operand
10730 .as_deref()
10731 .is_some_and(|o| expr_refers_to(o, target))
10732 || branches
10733 .iter()
10734 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10735 || else_branch
10736 .as_deref()
10737 .is_some_and(|e| expr_refers_to(e, target))
10738 }
10739 }
10740}
10741
10742fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10753 let s = match ty {
10754 DataType::Int => "integer",
10755 DataType::BigInt => "bigint",
10756 DataType::SmallInt => "smallint",
10757 DataType::Float => "double precision",
10758 DataType::Bool => "boolean",
10759 DataType::Text => "text",
10760 DataType::Varchar(_) => "character varying",
10761 DataType::Date => "date",
10762 DataType::Timestamp => "timestamp without time zone",
10763 DataType::Timestamptz => "timestamp with time zone",
10764 DataType::Json => "jsonb",
10765 DataType::Bytes => "bytea",
10766 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10767 DataType::TsVector => "tsvector",
10768 DataType::TsQuery => "tsquery",
10769 DataType::Vector { .. } => "USER-DEFINED",
10770 _ => "USER-DEFINED",
10773 };
10774 alloc::string::String::from(s)
10775}
10776
10777fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10784 let schema = alloc::vec![
10785 ColumnSchema::new("table_catalog", DataType::Text, false),
10786 ColumnSchema::new("table_schema", DataType::Text, false),
10787 ColumnSchema::new("table_name", DataType::Text, false),
10788 ColumnSchema::new("column_name", DataType::Text, false),
10789 ColumnSchema::new("ordinal_position", DataType::Int, false),
10790 ColumnSchema::new("is_nullable", DataType::Text, false),
10791 ColumnSchema::new("data_type", DataType::Text, false),
10792 ];
10793 let mut rows: Vec<Row> = Vec::new();
10794 for tname in cat.table_names() {
10795 let Some(t) = cat.get(&tname) else { continue };
10796 for (i, col) in t.schema().columns.iter().enumerate() {
10797 #[allow(clippy::cast_possible_wrap)]
10798 let ordinal = (i + 1) as i32;
10799 rows.push(Row::new(alloc::vec![
10800 Value::Text("spg".into()),
10801 Value::Text("public".into()),
10802 Value::Text(tname.clone()),
10803 Value::Text(col.name.clone()),
10804 Value::Int(ordinal),
10805 Value::Text(if col.nullable {
10806 "YES".into()
10807 } else {
10808 "NO".into()
10809 }),
10810 Value::Text(pg_data_type_text(col.ty)),
10811 ]));
10812 }
10813 }
10814 (schema, rows)
10815}
10816
10817fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10819 let schema = alloc::vec![
10820 ColumnSchema::new("table_catalog", DataType::Text, false),
10821 ColumnSchema::new("table_schema", DataType::Text, false),
10822 ColumnSchema::new("table_name", DataType::Text, false),
10823 ColumnSchema::new("table_type", DataType::Text, false),
10824 ];
10825 let mut rows: Vec<Row> = Vec::new();
10826 for tname in cat.table_names() {
10827 rows.push(Row::new(alloc::vec![
10828 Value::Text("spg".into()),
10829 Value::Text("public".into()),
10830 Value::Text(tname.clone()),
10831 Value::Text("BASE TABLE".into()),
10832 ]));
10833 }
10834 (schema, rows)
10835}
10836
10837fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10841 let schema = alloc::vec![
10842 ColumnSchema::new("relname", DataType::Text, false),
10843 ColumnSchema::new("relkind", DataType::Text, false),
10844 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10845 ];
10846 let mut rows: Vec<Row> = Vec::new();
10847 for tname in cat.table_names() {
10848 rows.push(Row::new(alloc::vec![
10849 Value::Text(tname.clone()),
10850 Value::Text("r".into()),
10851 Value::BigInt(2200), ]));
10853 }
10854 (schema, rows)
10855}
10856
10857fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10861 let schema = alloc::vec![
10862 ColumnSchema::new("attrelid", DataType::Text, false),
10863 ColumnSchema::new("attname", DataType::Text, false),
10864 ColumnSchema::new("attnum", DataType::Int, false),
10865 ColumnSchema::new("atttypid", DataType::Text, false),
10866 ColumnSchema::new("attnotnull", DataType::Bool, false),
10867 ];
10868 let mut rows: Vec<Row> = Vec::new();
10869 for tname in cat.table_names() {
10870 let Some(t) = cat.get(&tname) else { continue };
10871 for (i, col) in t.schema().columns.iter().enumerate() {
10872 #[allow(clippy::cast_possible_wrap)]
10873 let ordinal = (i + 1) as i32;
10874 rows.push(Row::new(alloc::vec![
10875 Value::Text(tname.clone()),
10876 Value::Text(col.name.clone()),
10877 Value::Int(ordinal),
10878 Value::Text(pg_data_type_text(col.ty)),
10879 Value::Bool(!col.nullable),
10880 ]));
10881 }
10882 }
10883 (schema, rows)
10884}
10885
10886fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10903 let schema = alloc::vec![
10904 ColumnSchema::new("oid", DataType::BigInt, false),
10905 ColumnSchema::new("typname", DataType::Text, false),
10906 ColumnSchema::new("typlen", DataType::SmallInt, false),
10907 ColumnSchema::new("typtype", DataType::Text, false),
10908 ColumnSchema::new("typcategory", DataType::Text, false),
10909 ColumnSchema::new("typelem", DataType::BigInt, false),
10910 ColumnSchema::new("typarray", DataType::BigInt, false),
10911 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10912 ];
10913 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10916 (16, "bool", 1, "b", "B", 0, 1000),
10918 (17, "bytea", -1, "b", "U", 0, 1001),
10919 (18, "char", 1, "b", "S", 0, 1002),
10920 (19, "name", 64, "b", "S", 0, 1003),
10921 (20, "int8", 8, "b", "N", 0, 1016),
10922 (21, "int2", 2, "b", "N", 0, 1005),
10923 (23, "int4", 4, "b", "N", 0, 1007),
10924 (24, "regproc", 4, "b", "N", 0, 1008),
10925 (25, "text", -1, "b", "S", 0, 1009),
10926 (26, "oid", 4, "b", "N", 0, 1028),
10927 (114, "json", -1, "b", "U", 0, 199),
10928 (142, "xml", -1, "b", "U", 0, 143),
10929 (700, "float4", 4, "b", "N", 0, 1021),
10930 (701, "float8", 8, "b", "N", 0, 1022),
10931 (650, "cidr", -1, "b", "I", 0, 651),
10932 (869, "inet", -1, "b", "I", 0, 1041),
10933 (829, "macaddr", 6, "b", "U", 0, 1040),
10934 (1042, "bpchar", -1, "b", "S", 0, 1014),
10935 (1043, "varchar", -1, "b", "S", 0, 1015),
10936 (1082, "date", 4, "b", "D", 0, 1182),
10937 (1083, "time", 8, "b", "D", 0, 1183),
10938 (1114, "timestamp", 8, "b", "D", 0, 1115),
10939 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10940 (1186, "interval", 16, "b", "T", 0, 1187),
10941 (1266, "timetz", 12, "b", "D", 0, 1270),
10942 (1700, "numeric", -1, "b", "N", 0, 1231),
10943 (790, "money", 8, "b", "N", 0, 791),
10944 (2950, "uuid", 16, "b", "U", 0, 2951),
10945 (3802, "jsonb", -1, "b", "U", 0, 3807),
10946 (3614, "tsvector", -1, "b", "U", 0, 3643),
10947 (3615, "tsquery", -1, "b", "U", 0, 3645),
10948 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10950 (3910, "tsrange", -1, "r", "R", 0, 3911),
10951 (3904, "int4range", -1, "r", "R", 0, 3905),
10952 (3926, "int8range", -1, "r", "R", 0, 3927),
10953 (3906, "numrange", -1, "r", "R", 0, 3907),
10954 (3912, "daterange", -1, "r", "R", 0, 3913),
10955 ];
10956 let arrays: &[(i64, &str, i64)] = &[
10959 (1000, "_bool", 16),
10960 (1001, "_bytea", 17),
10961 (1002, "_char", 18),
10962 (1003, "_name", 19),
10963 (1016, "_int8", 20),
10964 (1005, "_int2", 21),
10965 (1007, "_int4", 23),
10966 (1008, "_regproc", 24),
10967 (1009, "_text", 25),
10968 (1028, "_oid", 26),
10969 (199, "_json", 114),
10970 (143, "_xml", 142),
10971 (1021, "_float4", 700),
10972 (1022, "_float8", 701),
10973 (651, "_cidr", 650),
10974 (1041, "_inet", 869),
10975 (1040, "_macaddr", 829),
10976 (1014, "_bpchar", 1042),
10977 (1015, "_varchar", 1043),
10978 (1182, "_date", 1082),
10979 (1183, "_time", 1083),
10980 (1115, "_timestamp", 1114),
10981 (1185, "_timestamptz", 1184),
10982 (1187, "_interval", 1186),
10983 (1270, "_timetz", 1266),
10984 (1231, "_numeric", 1700),
10985 (791, "_money", 790),
10986 (2951, "_uuid", 2950),
10987 (3807, "_jsonb", 3802),
10988 (3643, "_tsvector", 3614),
10989 (3645, "_tsquery", 3615),
10990 ];
10991 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10992 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10993 rows.push(Row::new(alloc::vec![
10994 Value::BigInt(oid),
10995 Value::Text(name.into()),
10996 Value::SmallInt(len),
10997 Value::Text(ty.into()),
10998 Value::Text(cat.into()),
10999 Value::BigInt(elem),
11000 Value::BigInt(arr),
11001 Value::BigInt(2200),
11002 ]));
11003 }
11004 for &(oid, name, elem) in arrays {
11005 rows.push(Row::new(alloc::vec![
11006 Value::BigInt(oid),
11007 Value::Text(name.into()),
11008 Value::SmallInt(-1),
11009 Value::Text("b".into()),
11010 Value::Text("A".into()),
11011 Value::BigInt(elem),
11012 Value::BigInt(0),
11013 Value::BigInt(2200),
11014 ]));
11015 }
11016 (schema, rows)
11017}
11018
11019fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11033 let schema = alloc::vec![
11034 ColumnSchema::new("oid", DataType::BigInt, false),
11035 ColumnSchema::new("proname", DataType::Text, false),
11036 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11037 ColumnSchema::new("prokind", DataType::Text, false),
11038 ColumnSchema::new("pronargs", DataType::Int, false),
11039 ColumnSchema::new("prorettype", DataType::BigInt, false),
11040 ];
11041 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11044 (1318, "length", "f", 1, 23),
11046 (871, "upper", "f", 1, 25),
11047 (870, "lower", "f", 1, 25),
11048 (936, "substring", "f", 3, 25),
11049 (937, "substring", "f", 2, 25),
11050 (3055, "btrim", "f", 1, 25),
11051 (885, "btrim", "f", 2, 25),
11052 (3056, "ltrim", "f", 1, 25),
11053 (875, "ltrim", "f", 2, 25),
11054 (3057, "rtrim", "f", 1, 25),
11055 (876, "rtrim", "f", 2, 25),
11056 (1397, "abs", "f", 1, 23),
11057 (1396, "abs", "f", 1, 20),
11058 (1606, "round", "f", 1, 1700),
11059 (1707, "round", "f", 2, 1700),
11060 (2308, "ceil", "f", 1, 701),
11061 (2309, "ceiling", "f", 1, 701),
11062 (2310, "floor", "f", 1, 701),
11063 (1376, "sqrt", "f", 1, 701),
11064 (1369, "ln", "f", 1, 701),
11065 (1373, "exp", "f", 1, 701),
11066 (1368, "power", "f", 2, 701),
11067 (2228, "random", "f", 0, 701),
11068 (1299, "now", "f", 0, 1184),
11070 (1274, "current_timestamp", "f", 0, 1184),
11071 (1140, "current_date", "f", 0, 1082),
11072 (2050, "current_time", "f", 0, 1083),
11073 (1158, "date_trunc", "f", 2, 1184),
11074 (1171, "date_part", "f", 2, 701),
11075 (1172, "age", "f", 1, 1186),
11076 (936, "to_char", "f", 2, 25),
11077 (861, "current_database", "f", 0, 19),
11079 (745, "current_user", "f", 0, 19),
11080 (745, "session_user", "f", 0, 19),
11081 (1402, "current_schema", "f", 0, 19),
11082 (3058, "concat", "f", -1, 25),
11084 (3059, "concat_ws", "f", -1, 25),
11085 (3539, "format", "f", -1, 25),
11086 (2877, "pg_typeof", "f", 1, 2206),
11088 (3198, "json_build_object", "f", -1, 114),
11090 (3199, "jsonb_build_object", "f", -1, 3802),
11091 (3271, "json_build_array", "f", -1, 114),
11092 (3272, "jsonb_build_array", "f", -1, 3802),
11093 (3253, "gen_random_uuid", "f", 0, 2950),
11095 (3252, "uuid_generate_v4", "f", 0, 2950),
11096 (2147, "count", "a", 0, 20),
11098 (2803, "count", "a", -1, 20),
11099 (2116, "max", "a", 1, 23),
11100 (2132, "min", "a", 1, 23),
11101 (2108, "sum", "a", 1, 20),
11102 (2100, "avg", "a", 1, 1700),
11103 (2517, "string_agg", "a", 2, 25),
11104 (2747, "array_agg", "a", 1, 1009),
11105 (2517, "bool_and", "a", 1, 16),
11106 (2518, "bool_or", "a", 1, 16),
11107 (2519, "every", "a", 1, 16),
11108 (3100, "row_number", "w", 0, 20),
11110 (3101, "rank", "w", 0, 20),
11111 (3102, "dense_rank", "w", 0, 20),
11112 (3103, "percent_rank", "w", 0, 701),
11113 (3104, "cume_dist", "w", 0, 701),
11114 (3105, "lag", "w", -1, 2283),
11115 (3106, "lead", "w", -1, 2283),
11116 (3107, "first_value", "w", 1, 2283),
11117 (3108, "last_value", "w", 1, 2283),
11118 (3109, "nth_value", "w", 2, 2283),
11119 ];
11120 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11121 for &(oid, name, kind, nargs, rettype) in funcs {
11122 rows.push(Row::new(alloc::vec![
11123 Value::BigInt(oid),
11124 Value::Text(name.into()),
11125 Value::BigInt(11),
11126 Value::Text(kind.into()),
11127 Value::Int(nargs),
11128 Value::BigInt(rettype),
11129 ]));
11130 }
11131 (schema, rows)
11132}
11133
11134fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11140 let schema = alloc::vec![
11141 ColumnSchema::new("user", DataType::Text, false),
11142 ColumnSchema::new("host", DataType::Text, false),
11143 ColumnSchema::new("select_priv", DataType::Text, false),
11144 ];
11145 let mut rows: Vec<Row> = Vec::new();
11146 rows.push(Row::new(alloc::vec![
11147 Value::Text("root".into()),
11148 Value::Text("localhost".into()),
11149 Value::Text("Y".into()),
11150 ]));
11151 for (name, _) in engine.users.iter() {
11152 if name != "root" {
11153 rows.push(Row::new(alloc::vec![
11154 Value::Text(name.to_string()),
11155 Value::Text("%".into()),
11156 Value::Text("Y".into()),
11157 ]));
11158 }
11159 }
11160 (schema, rows)
11161}
11162
11163fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11168 let schema = alloc::vec![
11169 ColumnSchema::new("host", DataType::Text, false),
11170 ColumnSchema::new("db", DataType::Text, false),
11171 ColumnSchema::new("user", DataType::Text, false),
11172 ColumnSchema::new("select_priv", DataType::Text, false),
11173 ];
11174 let rows = alloc::vec![Row::new(alloc::vec![
11175 Value::Text("localhost".into()),
11176 Value::Text("postgres".into()),
11177 Value::Text("root".into()),
11178 Value::Text("Y".into()),
11179 ])];
11180 (schema, rows)
11181}
11182
11183fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11196 let schema = alloc::vec![
11197 ColumnSchema::new("constraint_name", DataType::Text, false),
11198 ColumnSchema::new("table_name", DataType::Text, false),
11199 ColumnSchema::new("column_name", DataType::Text, false),
11200 ColumnSchema::new("ordinal_position", DataType::Int, false),
11201 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11202 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11203 ];
11204 let mut rows: Vec<Row> = Vec::new();
11205 for tname in cat.table_names() {
11206 let Some(t) = cat.get(&tname) else { continue };
11207 let cols = &t.schema().columns;
11208 let col_name_at = |pos: usize| -> String {
11209 cols.get(pos)
11210 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11211 };
11212 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11214 let conname = fk
11215 .name
11216 .clone()
11217 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11218 for (i, (&local, &parent)) in fk
11219 .local_columns
11220 .iter()
11221 .zip(fk.parent_columns.iter())
11222 .enumerate()
11223 {
11224 let parent_name = cat
11225 .get(&fk.parent_table)
11226 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11227 .unwrap_or_else(|| alloc::format!("col{parent}"));
11228 #[allow(clippy::cast_possible_wrap)]
11229 let ordinal = (i + 1) as i32;
11230 rows.push(Row::new(alloc::vec![
11231 Value::Text(conname.clone()),
11232 Value::Text(tname.clone()),
11233 Value::Text(col_name_at(local)),
11234 Value::Int(ordinal),
11235 Value::Text(fk.parent_table.clone()),
11236 Value::Text(parent_name),
11237 ]));
11238 }
11239 }
11240 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11242 let conname = if uc.is_primary_key {
11243 alloc::format!("{}_pkey", tname)
11244 } else {
11245 alloc::format!("{}_uniq{ci}", tname)
11246 };
11247 for (i, &local) in uc.columns.iter().enumerate() {
11248 #[allow(clippy::cast_possible_wrap)]
11249 let ordinal = (i + 1) as i32;
11250 rows.push(Row::new(alloc::vec![
11251 Value::Text(conname.clone()),
11252 Value::Text(tname.clone()),
11253 Value::Text(col_name_at(local)),
11254 Value::Int(ordinal),
11255 Value::Text(String::new()),
11256 Value::Text(String::new()),
11257 ]));
11258 }
11259 }
11260 }
11261 (schema, rows)
11262}
11263
11264fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11267 let schema = alloc::vec![
11268 ColumnSchema::new("constraint_name", DataType::Text, false),
11269 ColumnSchema::new("table_name", DataType::Text, false),
11270 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11271 ColumnSchema::new("update_rule", DataType::Text, false),
11272 ColumnSchema::new("delete_rule", DataType::Text, false),
11273 ];
11274 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11275 match a {
11276 spg_storage::FkAction::Cascade => "CASCADE",
11277 spg_storage::FkAction::SetNull => "SET NULL",
11278 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11279 spg_storage::FkAction::Restrict => "RESTRICT",
11280 spg_storage::FkAction::NoAction => "NO ACTION",
11281 }
11282 }
11283 let mut rows: Vec<Row> = Vec::new();
11284 for tname in cat.table_names() {
11285 let Some(t) = cat.get(&tname) else { continue };
11286 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11287 let conname = fk
11288 .name
11289 .clone()
11290 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11291 rows.push(Row::new(alloc::vec![
11292 Value::Text(conname),
11293 Value::Text(tname.clone()),
11294 Value::Text(fk.parent_table.clone()),
11295 Value::Text(rule_name(fk.on_update).into()),
11296 Value::Text(rule_name(fk.on_delete).into()),
11297 ]));
11298 }
11299 }
11300 (schema, rows)
11301}
11302
11303fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11307 let schema = alloc::vec![
11308 ColumnSchema::new("table_name", DataType::Text, false),
11309 ColumnSchema::new("index_name", DataType::Text, false),
11310 ColumnSchema::new("column_name", DataType::Text, false),
11311 ColumnSchema::new("seq_in_index", DataType::Int, false),
11312 ColumnSchema::new("non_unique", DataType::Int, false),
11313 ColumnSchema::new("index_type", DataType::Text, false),
11314 ];
11315 let mut rows: Vec<Row> = Vec::new();
11316 for tname in cat.table_names() {
11317 let Some(t) = cat.get(&tname) else { continue };
11318 for idx in t.indices() {
11319 let col = t
11320 .schema()
11321 .columns
11322 .get(idx.column_position)
11323 .map_or("?".into(), |c| c.name.clone());
11324 rows.push(Row::new(alloc::vec![
11325 Value::Text(tname.clone()),
11326 Value::Text(idx.name.clone()),
11327 Value::Text(col),
11328 Value::Int(1),
11329 Value::Int(i32::from(!idx.is_unique)),
11330 Value::Text("BTREE".into()),
11331 ]));
11332 }
11333 }
11334 (schema, rows)
11335}
11336
11337fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11341 let schema = alloc::vec![
11342 ColumnSchema::new("routine_name", DataType::Text, false),
11343 ColumnSchema::new("routine_type", DataType::Text, false),
11344 ColumnSchema::new("data_type", DataType::Text, false),
11345 ];
11346 (schema, Vec::new())
11347}
11348
11349fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11364 let schema = alloc::vec![
11365 ColumnSchema::new("conname", DataType::Text, false),
11366 ColumnSchema::new("contype", DataType::Text, false),
11367 ColumnSchema::new("conrelid", DataType::Text, false),
11368 ColumnSchema::new("confrelid", DataType::Text, false),
11369 ColumnSchema::new("conkey", DataType::Text, false),
11370 ColumnSchema::new("confkey", DataType::Text, false),
11371 ];
11372 let mut rows: Vec<Row> = Vec::new();
11373 for tname in cat.table_names() {
11374 let Some(t) = cat.get(&tname) else { continue };
11375 let cols = &t.schema().columns;
11376 let col_name_at = |pos: usize| -> String {
11377 cols.get(pos)
11378 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11379 };
11380 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11382 let kind = if uc.is_primary_key { "p" } else { "u" };
11383 let conname = if uc.is_primary_key {
11384 alloc::format!("{}_pkey", tname)
11385 } else {
11386 alloc::format!("{}_uniq{ci}", tname)
11387 };
11388 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11389 rows.push(Row::new(alloc::vec![
11390 Value::Text(conname),
11391 Value::Text(kind.into()),
11392 Value::Text(tname.clone()),
11393 Value::Text(String::new()),
11394 Value::Text(conkey.join(",")),
11395 Value::Text(String::new()),
11396 ]));
11397 }
11398 for idx in t.indices() {
11403 if !idx.is_unique {
11404 continue;
11405 }
11406 let is_primary = idx.name.ends_with("_pkey");
11407 let conname = idx.name.clone();
11408 let kind = if is_primary { "p" } else { "u" };
11409 let col_name = col_name_at(idx.column_position);
11410 let already = t
11413 .schema()
11414 .uniqueness_constraints
11415 .iter()
11416 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11417 if already {
11418 continue;
11419 }
11420 rows.push(Row::new(alloc::vec![
11421 Value::Text(conname),
11422 Value::Text(kind.into()),
11423 Value::Text(tname.clone()),
11424 Value::Text(String::new()),
11425 Value::Text(col_name),
11426 Value::Text(String::new()),
11427 ]));
11428 }
11429 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11431 let conname = fk
11432 .name
11433 .clone()
11434 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11435 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11436 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11439 fk.parent_columns
11440 .iter()
11441 .map(|&p| {
11442 parent
11443 .schema()
11444 .columns
11445 .get(p)
11446 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11447 })
11448 .collect()
11449 } else {
11450 fk.parent_columns
11451 .iter()
11452 .map(|p| alloc::format!("col{p}"))
11453 .collect()
11454 };
11455 rows.push(Row::new(alloc::vec![
11456 Value::Text(conname),
11457 Value::Text("f".into()),
11458 Value::Text(tname.clone()),
11459 Value::Text(fk.parent_table.clone()),
11460 Value::Text(conkey.join(",")),
11461 Value::Text(confkey.join(",")),
11462 ]));
11463 }
11464 }
11465 (schema, rows)
11466}
11467
11468fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11473 let schema = alloc::vec![
11474 ColumnSchema::new("oid", DataType::BigInt, false),
11475 ColumnSchema::new("datname", DataType::Text, false),
11476 ColumnSchema::new("datdba", DataType::BigInt, false),
11477 ColumnSchema::new("encoding", DataType::Int, false),
11478 ColumnSchema::new("datcollate", DataType::Text, false),
11479 ];
11480 let rows = alloc::vec![Row::new(alloc::vec![
11481 Value::BigInt(16384),
11482 Value::Text("postgres".into()),
11483 Value::BigInt(10),
11484 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11486 ])];
11487 (schema, rows)
11488}
11489
11490fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11495 let schema = alloc::vec![
11496 ColumnSchema::new("oid", DataType::BigInt, false),
11497 ColumnSchema::new("rolname", DataType::Text, false),
11498 ColumnSchema::new("rolsuper", DataType::Bool, false),
11499 ColumnSchema::new("rolinherit", DataType::Bool, false),
11500 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11501 ];
11502 let mut rows: Vec<Row> = Vec::new();
11503 let oid: i64 = 10;
11504 for (i, (name, _)) in engine.users.iter().enumerate() {
11505 rows.push(Row::new(alloc::vec![
11506 Value::BigInt(oid + (i as i64) + 1),
11507 Value::Text(name.to_string()),
11508 Value::Bool(false),
11509 Value::Bool(true),
11510 Value::Bool(true),
11511 ]));
11512 }
11513 if !rows
11516 .iter()
11517 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11518 {
11519 rows.insert(
11520 0,
11521 Row::new(alloc::vec![
11522 Value::BigInt(10),
11523 Value::Text("postgres".into()),
11524 Value::Bool(true),
11525 Value::Bool(true),
11526 Value::Bool(true),
11527 ]),
11528 );
11529 }
11530 (schema, rows)
11531}
11532
11533fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11542 let schema = alloc::vec![
11543 ColumnSchema::new("oid", DataType::BigInt, false),
11544 ColumnSchema::new("extname", DataType::Text, false),
11545 ColumnSchema::new("extversion", DataType::Text, false),
11546 ColumnSchema::new("extnamespace", DataType::Text, false),
11547 ];
11548 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11549 let rows = exts
11550 .iter()
11551 .enumerate()
11552 .map(|(i, (name, ver))| {
11553 Row::new(alloc::vec![
11554 Value::BigInt(16384 + i as i64),
11555 Value::Text((*name).into()),
11556 Value::Text((*ver).into()),
11557 Value::Text("pg_catalog".into()),
11558 ])
11559 })
11560 .collect();
11561 (schema, rows)
11562}
11563
11564fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11565 let schema = alloc::vec![
11566 ColumnSchema::new("schemaname", DataType::Text, false),
11567 ColumnSchema::new("viewname", DataType::Text, false),
11568 ColumnSchema::new("definition", DataType::Text, false),
11569 ];
11570 let mut rows: Vec<Row> = Vec::new();
11571 for (name, def) in cat.views() {
11572 rows.push(Row::new(alloc::vec![
11573 Value::Text("public".into()),
11574 Value::Text(name.clone()),
11575 Value::Text(def.body.clone()),
11576 ]));
11577 }
11578 (schema, rows)
11579}
11580
11581fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11587 let schema = alloc::vec![
11588 ColumnSchema::new("name", DataType::Text, false),
11589 ColumnSchema::new("setting", DataType::Text, false),
11590 ColumnSchema::new("category", DataType::Text, false),
11591 ];
11592 let mut rows: Vec<Row> = Vec::new();
11593 let defaults: &[(&str, &str, &str)] = &[
11595 ("server_version", "16.0 (spg)", "Preset Options"),
11596 ("server_encoding", "UTF8", "Client Connection Defaults"),
11597 ("client_encoding", "UTF8", "Client Connection Defaults"),
11598 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11599 ("TimeZone", "UTC", "Client Connection Defaults"),
11600 ("standard_conforming_strings", "on", "Compatibility"),
11601 ("integer_datetimes", "on", "Compatibility"),
11602 ("max_connections", "100", "Connections and Authentication"),
11603 ];
11604 for &(name, val, cat) in defaults {
11605 rows.push(Row::new(alloc::vec![
11606 Value::Text(name.into()),
11607 Value::Text(val.into()),
11608 Value::Text(cat.into()),
11609 ]));
11610 }
11611 for (k, v) in &engine.session_params {
11613 if !defaults
11614 .iter()
11615 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11616 {
11617 rows.push(Row::new(alloc::vec![
11618 Value::Text(k.clone()),
11619 Value::Text(v.clone()),
11620 Value::Text("Session".into()),
11621 ]));
11622 }
11623 }
11624 (schema, rows)
11625}
11626
11627fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11638 let schema = alloc::vec![
11639 ColumnSchema::new("schemaname", DataType::Text, false),
11640 ColumnSchema::new("tablename", DataType::Text, false),
11641 ColumnSchema::new("indexname", DataType::Text, false),
11642 ColumnSchema::new("indexdef", DataType::Text, false),
11643 ];
11644 let mut rows: Vec<Row> = Vec::new();
11645 for tname in cat.table_names() {
11646 let Some(t) = cat.get(&tname) else { continue };
11647 for idx in t.indices() {
11648 let col_name = t
11649 .schema()
11650 .columns
11651 .get(idx.column_position)
11652 .map_or("?".into(), |c| c.name.clone());
11653 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11654 let indexdef = alloc::format!(
11655 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11656 idx.name,
11657 tname,
11658 col_name
11659 );
11660 rows.push(Row::new(alloc::vec![
11661 Value::Text("public".into()),
11662 Value::Text(tname.clone()),
11663 Value::Text(idx.name.clone()),
11664 Value::Text(indexdef),
11665 ]));
11666 }
11667 }
11668 (schema, rows)
11669}
11670
11671fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11683 let schema = alloc::vec![
11684 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11685 ColumnSchema::new("indrelid", DataType::BigInt, false),
11686 ColumnSchema::new("indnatts", DataType::Int, false),
11687 ColumnSchema::new("indisunique", DataType::Bool, false),
11688 ColumnSchema::new("indisprimary", DataType::Bool, false),
11689 ];
11690 let mut rows: Vec<Row> = Vec::new();
11691 let mut idx_oid: i64 = 100_000;
11692 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11693 let Some(t) = cat.get(tname) else { continue };
11694 for idx in t.indices() {
11695 idx_oid += 1;
11696 #[allow(clippy::cast_possible_wrap)]
11697 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11698 let is_primary = idx.name.ends_with("_pkey");
11701 rows.push(Row::new(alloc::vec![
11702 Value::BigInt(idx_oid),
11703 Value::BigInt((table_idx + 1) as i64),
11704 Value::Int(nattrs),
11705 Value::Bool(idx.is_unique),
11706 Value::Bool(is_primary),
11707 ]));
11708 }
11709 }
11710 (schema, rows)
11711}
11712
11713fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11718 let schema = alloc::vec![
11719 ColumnSchema::new("oid", DataType::BigInt, false),
11720 ColumnSchema::new("nspname", DataType::Text, false),
11721 ColumnSchema::new("nspowner", DataType::BigInt, false),
11722 ];
11723 let rows = alloc::vec![
11724 Row::new(alloc::vec![
11725 Value::BigInt(11),
11726 Value::Text("pg_catalog".into()),
11727 Value::BigInt(10),
11728 ]),
11729 Row::new(alloc::vec![
11730 Value::BigInt(2200),
11731 Value::Text("public".into()),
11732 Value::BigInt(10),
11733 ]),
11734 Row::new(alloc::vec![
11735 Value::BigInt(13000),
11736 Value::Text("information_schema".into()),
11737 Value::BigInt(10),
11738 ]),
11739 ];
11740 (schema, rows)
11741}
11742
11743fn materialise_meta_view(
11746 catalog: &mut Catalog,
11747 name: &str,
11748 columns: Vec<ColumnSchema>,
11749 rows: Vec<Row>,
11750) -> Result<(), EngineError> {
11751 let schema = TableSchema::new(name.to_string(), columns);
11752 catalog.create_table(schema).map_err(EngineError::Storage)?;
11753 let table = catalog
11754 .get_mut(name)
11755 .expect("just-created meta view must exist");
11756 for row in rows {
11757 table.insert(row).map_err(EngineError::Storage)?;
11758 }
11759 Ok(())
11760}
11761
11762fn collect_view_refs(
11775 tref: &spg_sql::ast::TableRef,
11776 cat: &spg_storage::Catalog,
11777 into: &mut Vec<String>,
11778) {
11779 if cat.views().contains_key(&tref.name)
11780 && cat.get(&tref.name).is_none()
11781 && !into.iter().any(|n| n == &tref.name)
11782 {
11783 into.push(tref.name.clone());
11784 }
11785}
11786
11787fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11788 fn is_meta(name: &str) -> bool {
11789 name.starts_with("__spg_info_")
11790 || name.starts_with("__spg_pg_")
11791 || name.starts_with("__spg_mysql_")
11792 }
11793 if let Some(from) = &stmt.from {
11794 if is_meta(&from.primary.name) {
11795 return true;
11796 }
11797 for j in &from.joins {
11798 if is_meta(&j.table.name) {
11799 return true;
11800 }
11801 }
11802 }
11803 for cte in &stmt.ctes {
11804 if select_references_meta_view(&cte.body) {
11805 return true;
11806 }
11807 }
11808 false
11809}
11810
11811fn collect_meta_view_names(
11816 stmt: &SelectStatement,
11817 into: &mut alloc::collections::BTreeSet<String>,
11818) {
11819 fn is_meta(name: &str) -> bool {
11820 name.starts_with("__spg_info_")
11821 || name.starts_with("__spg_pg_")
11822 || name.starts_with("__spg_mysql_")
11823 }
11824 if let Some(from) = &stmt.from {
11825 if is_meta(&from.primary.name) {
11826 into.insert(from.primary.name.clone());
11827 }
11828 for j in &from.joins {
11829 if is_meta(&j.table.name) {
11830 into.insert(j.table.name.clone());
11831 }
11832 }
11833 }
11834 for cte in &stmt.ctes {
11835 collect_meta_view_names(&cte.body, into);
11836 }
11837}
11838
11839fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11840 let mut out = columns.to_vec();
11841 for (col_idx, col) in out.iter_mut().enumerate() {
11842 if col.ty != DataType::Text {
11843 continue;
11844 }
11845 let mut inferred: Option<DataType> = None;
11846 let mut all_null = true;
11847 for row in rows {
11848 let Some(v) = row.values.get(col_idx) else {
11849 continue;
11850 };
11851 let ty = match v {
11852 Value::Null => continue,
11853 Value::SmallInt(_) => DataType::SmallInt,
11854 Value::Int(_) => DataType::Int,
11855 Value::BigInt(_) => DataType::BigInt,
11856 Value::Float(_) => DataType::Float,
11857 Value::Bool(_) => DataType::Bool,
11858 Value::Vector(_) => DataType::Vector {
11859 dim: 0,
11860 encoding: VecEncoding::F32,
11861 },
11862 _ => DataType::Text,
11863 };
11864 all_null = false;
11865 inferred = Some(match inferred {
11866 None => ty,
11867 Some(prev) if prev == ty => prev,
11868 Some(_) => DataType::Text,
11869 });
11870 }
11871 if let Some(t) = inferred {
11872 col.ty = t;
11873 col.nullable = true;
11874 } else if all_null {
11875 col.nullable = true;
11876 }
11877 }
11878 out
11879}
11880
11881#[allow(clippy::too_many_lines, clippy::format_push_string)]
11886fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11903 use alloc::collections::BTreeSet;
11904 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11905 let mut out: Vec<String> = Vec::new();
11906 let cat = engine.active_catalog();
11907 let Some(from) = &stmt.from else {
11911 return out;
11912 };
11913 let mut tables: Vec<String> = Vec::new();
11914 tables.push(from.primary.name.clone());
11915 for j in &from.joins {
11916 tables.push(j.table.name.clone());
11917 }
11918 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11921 if let Some(w) = &stmt.where_ {
11922 collect_column_refs(w, &mut col_refs);
11923 }
11924 for j in &from.joins {
11925 if let Some(on) = &j.on {
11926 collect_column_refs(on, &mut col_refs);
11927 }
11928 }
11929 for cn in &col_refs {
11930 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11933 tables.iter().find(|t| t == &q).cloned()
11934 } else {
11935 tables.iter().find_map(|t| {
11936 cat.get(t).and_then(|tbl| {
11937 if tbl.schema().column_position(&cn.name).is_some() {
11938 Some(t.clone())
11939 } else {
11940 None
11941 }
11942 })
11943 })
11944 };
11945 let Some(owner) = owner else {
11946 continue;
11947 };
11948 let Some(tbl) = cat.get(&owner) else {
11949 continue;
11950 };
11951 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11952 continue;
11953 };
11954 let already_indexed = tbl.indices().iter().any(|i| {
11957 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11958 && i.column_position == col_pos
11959 && i.expression.is_none()
11960 && i.partial_predicate.is_none()
11961 });
11962 if already_indexed {
11963 continue;
11964 }
11965 if seen.insert((owner.clone(), cn.name.clone())) {
11966 out.push(alloc::format!(
11967 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11968 owner,
11969 cn.name,
11970 owner,
11971 cn.name
11972 ));
11973 }
11974 }
11975 out
11976}
11977
11978fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11981 match expr {
11982 Expr::Column(cn) => out.push(cn.clone()),
11983 Expr::FunctionCall { args, .. } => {
11984 for a in args {
11985 collect_column_refs(a, out);
11986 }
11987 }
11988 Expr::Binary { lhs, rhs, .. } => {
11989 collect_column_refs(lhs, out);
11990 collect_column_refs(rhs, out);
11991 }
11992 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11993 _ => {}
11994 }
11995}
11996
11997fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11998 let catalog = engine.active_catalog();
11999 let cold_ids = catalog.cold_segment_ids_global();
12000 let any_cold = !cold_ids.is_empty();
12001 let cold_ids_repr = if any_cold {
12002 let mut s = alloc::string::String::from("[");
12003 for (i, id) in cold_ids.iter().enumerate() {
12004 if i > 0 {
12005 s.push(',');
12006 }
12007 s.push_str(&alloc::format!("{id}"));
12008 }
12009 s.push(']');
12010 s
12011 } else {
12012 alloc::string::String::new()
12013 };
12014 for (idx, line) in lines.iter_mut().enumerate() {
12015 let trimmed = line.trim_start();
12016 let is_top_level = idx == 0;
12017 if is_top_level {
12018 line.push_str(&alloc::format!(" (rows={total_rows})"));
12019 continue;
12020 }
12021 if let Some(rest) = trimmed.strip_prefix("From: ") {
12022 let (name, scan_kind) = match rest.split_once(" [") {
12023 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12024 None => (rest.trim(), ""),
12025 };
12026 let bare = name.split_whitespace().next().unwrap_or(name);
12027 let hot = catalog.get(bare).map(|t| t.rows().len());
12028 let annot = match (hot, scan_kind) {
12033 (Some(h), "full scan") => {
12034 let mut s = alloc::format!(" (hot_rows={h}");
12035 if any_cold {
12036 s.push_str(&alloc::format!(
12037 ", cold_tier=present, cold_segments={cold_ids_repr}"
12038 ));
12039 }
12040 s.push(')');
12041 s
12042 }
12043 (Some(h), "index seek") => {
12044 let mut s = alloc::format!(" (hot_rows≤{h}");
12045 if any_cold {
12046 s.push_str(&alloc::format!(
12047 ", cold_tier=present, cold_segments={cold_ids_repr}"
12048 ));
12049 }
12050 s.push(')');
12051 s
12052 }
12053 _ => " (rows=—)".to_string(),
12054 };
12055 line.push_str(&annot);
12056 continue;
12057 }
12058 line.push_str(" (rows=—)");
12060 }
12061}
12062
12063fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
12064 let pad = " ".repeat(depth);
12065 let top = if !stmt.ctes.is_empty() {
12067 if stmt.ctes.iter().any(|c| c.recursive) {
12068 "CTEScan (WITH RECURSIVE)"
12069 } else {
12070 "CTEScan (WITH)"
12071 }
12072 } else if !stmt.unions.is_empty() {
12073 "UnionScan"
12074 } else if select_has_window(stmt) {
12075 "WindowAgg"
12076 } else if aggregate::uses_aggregate(stmt) {
12077 "Aggregate"
12078 } else if stmt.distinct {
12079 "Distinct"
12080 } else if stmt.from.is_some() {
12081 "TableScan"
12082 } else {
12083 "Result"
12084 };
12085 out.push(alloc::format!("{pad}{top}"));
12086 let child = " ".repeat(depth + 1);
12087 for cte in &stmt.ctes {
12089 let head = if cte.recursive {
12090 alloc::format!("{child}CTE (recursive): {}", cte.name)
12091 } else {
12092 alloc::format!("{child}CTE: {}", cte.name)
12093 };
12094 out.push(head);
12095 explain_select(&cte.body, engine, depth + 2, out);
12096 }
12097 if let Some(from) = &stmt.from {
12099 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12100 if let Some(alias) = &from.primary.alias {
12101 tag.push_str(&alloc::format!(" AS {alias}"));
12102 }
12103 if let Some(w) = &stmt.where_
12106 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12107 {
12108 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12109 let cols = &table.schema().columns;
12110 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12111 tag.push_str(" [index seek]");
12112 } else {
12113 tag.push_str(" [full scan]");
12114 }
12115 } else {
12116 tag.push_str(" [full scan]");
12117 }
12118 out.push(tag);
12119 for j in &from.joins {
12120 let kind = match j.kind {
12121 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12122 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12123 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12124 };
12125 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12126 if let Some(alias) = &j.table.alias {
12127 s.push_str(&alloc::format!(" AS {alias}"));
12128 }
12129 if j.on.is_some() {
12130 s.push_str(" (ON …)");
12131 }
12132 out.push(s);
12133 }
12134 }
12135 if let Some(w) = &stmt.where_ {
12137 let mut s = alloc::format!("{child}Filter: {w}");
12138 if expr_has_subquery(w) {
12139 s.push_str(" [subquery]");
12140 }
12141 out.push(s);
12142 }
12143 if let Some(gs) = &stmt.group_by {
12144 let mut parts = Vec::new();
12145 for g in gs {
12146 parts.push(alloc::format!("{g}"));
12147 }
12148 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12149 }
12150 if let Some(h) = &stmt.having {
12151 out.push(alloc::format!("{child}Having: {h}"));
12152 }
12153 for o in &stmt.order_by {
12154 let dir = if o.desc { "DESC" } else { "ASC" };
12155 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12156 }
12157 if let Some(lim) = stmt.limit {
12158 out.push(alloc::format!("{child}Limit: {lim}"));
12159 }
12160 if let Some(off) = stmt.offset {
12161 out.push(alloc::format!("{child}Offset: {off}"));
12162 }
12163 if stmt
12165 .items
12166 .iter()
12167 .any(|it| matches!(it, SelectItem::Wildcard))
12168 {
12169 out.push(alloc::format!("{child}Project: *"));
12170 } else {
12171 out.push(alloc::format!(
12172 "{child}Project: {} item(s)",
12173 stmt.items.len()
12174 ));
12175 }
12176 for (kind, peer) in &stmt.unions {
12178 let label = match kind {
12179 UnionKind::All => "UNION ALL",
12180 UnionKind::Distinct => "UNION",
12181 };
12182 out.push(alloc::format!("{child}{label}"));
12183 explain_select(peer, engine, depth + 2, out);
12184 }
12185}
12186
12187fn is_correlation_error(e: &EngineError) -> bool {
12192 matches!(
12193 e,
12194 EngineError::Eval(
12195 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12196 )
12197 )
12198}
12199
12200struct JoinedPeer<'a> {
12211 eager_rows: Option<Vec<Row>>,
12212 cols: Vec<ColumnSchema>,
12213 alias: String,
12214 kind: JoinKind,
12215 on: Option<&'a Expr>,
12216 lateral: Option<&'a SelectStatement>,
12217}
12218
12219fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12226 match expr {
12227 Expr::Column(c) => c.name.clone(),
12229 Expr::FunctionCall { name, .. } => name.clone(),
12232 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12234 _ => alloc::format!("column{}", idx + 1),
12236 }
12237}
12238
12239fn substitute_outer_columns_multi(
12246 stmt: &mut SelectStatement,
12247 outer_row: &Row,
12248 outer_schema: &[ColumnSchema],
12249) {
12250 substitute_outer_in_select(stmt, outer_row, outer_schema);
12251}
12252
12253fn substitute_outer_in_select(
12254 stmt: &mut SelectStatement,
12255 outer_row: &Row,
12256 outer_schema: &[ColumnSchema],
12257) {
12258 for item in &mut stmt.items {
12259 if let SelectItem::Expr { expr, .. } = item {
12260 substitute_outer_in_expr(expr, outer_row, outer_schema);
12261 }
12262 }
12263 if let Some(w) = &mut stmt.where_ {
12264 substitute_outer_in_expr(w, outer_row, outer_schema);
12265 }
12266 if let Some(gs) = &mut stmt.group_by {
12267 for g in gs {
12268 substitute_outer_in_expr(g, outer_row, outer_schema);
12269 }
12270 }
12271 if let Some(h) = &mut stmt.having {
12272 substitute_outer_in_expr(h, outer_row, outer_schema);
12273 }
12274 for o in &mut stmt.order_by {
12275 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12276 }
12277 for (_, peer) in &mut stmt.unions {
12278 substitute_outer_in_select(peer, outer_row, outer_schema);
12279 }
12280}
12281
12282fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12283 if let Expr::Column(c) = e
12284 && let Some(qual) = &c.qualifier
12285 {
12286 let composite = alloc::format!("{qual}.{}", c.name);
12287 if let Some(idx) = outer_schema
12288 .iter()
12289 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12290 {
12291 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12292 if let Ok(lit) = value_to_literal_expr(v) {
12293 *e = lit;
12294 return;
12295 }
12296 }
12297 }
12298 match e {
12299 Expr::Binary { lhs, rhs, .. } => {
12300 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12301 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12302 }
12303 Expr::Unary { expr: inner, .. } => {
12304 substitute_outer_in_expr(inner, outer_row, outer_schema);
12305 }
12306 Expr::FunctionCall { args, .. } => {
12307 for a in args {
12308 substitute_outer_in_expr(a, outer_row, outer_schema);
12309 }
12310 }
12311 Expr::Cast { expr: inner, .. } => {
12312 substitute_outer_in_expr(inner, outer_row, outer_schema);
12313 }
12314 Expr::Case {
12315 operand,
12316 branches,
12317 else_branch,
12318 } => {
12319 if let Some(op) = operand {
12320 substitute_outer_in_expr(op, outer_row, outer_schema);
12321 }
12322 for (cond, val) in branches {
12323 substitute_outer_in_expr(cond, outer_row, outer_schema);
12324 substitute_outer_in_expr(val, outer_row, outer_schema);
12325 }
12326 if let Some(e) = else_branch {
12327 substitute_outer_in_expr(e, outer_row, outer_schema);
12328 }
12329 }
12330 _ => {}
12331 }
12332}
12333
12334fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12335 let Some(outer_alias) = ctx.table_alias else {
12336 return;
12337 };
12338 substitute_in_select(stmt, row, ctx, outer_alias);
12339}
12340
12341fn substitute_in_select(
12342 stmt: &mut SelectStatement,
12343 row: &Row,
12344 ctx: &EvalContext<'_>,
12345 outer_alias: &str,
12346) {
12347 for item in &mut stmt.items {
12348 if let SelectItem::Expr { expr, .. } = item {
12349 substitute_in_expr(expr, row, ctx, outer_alias);
12350 }
12351 }
12352 if let Some(w) = &mut stmt.where_ {
12353 substitute_in_expr(w, row, ctx, outer_alias);
12354 }
12355 if let Some(gs) = &mut stmt.group_by {
12356 for g in gs {
12357 substitute_in_expr(g, row, ctx, outer_alias);
12358 }
12359 }
12360 if let Some(h) = &mut stmt.having {
12361 substitute_in_expr(h, row, ctx, outer_alias);
12362 }
12363 for o in &mut stmt.order_by {
12364 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12365 }
12366 for (_, peer) in &mut stmt.unions {
12367 substitute_in_select(peer, row, ctx, outer_alias);
12368 }
12369}
12370
12371fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12372 if let Expr::Column(c) = e
12373 && let Some(qual) = &c.qualifier
12374 && qual.eq_ignore_ascii_case(outer_alias)
12375 {
12376 if let Some(idx) = ctx
12378 .columns
12379 .iter()
12380 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12381 {
12382 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12383 if let Ok(lit) = value_to_literal_expr(v) {
12384 *e = lit;
12385 return;
12386 }
12387 }
12388 }
12389 match e {
12390 Expr::Binary { lhs, rhs, .. } => {
12391 substitute_in_expr(lhs, row, ctx, outer_alias);
12392 substitute_in_expr(rhs, row, ctx, outer_alias);
12393 }
12394 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12395 substitute_in_expr(expr, row, ctx, outer_alias);
12396 }
12397 Expr::Like { expr, pattern, .. } => {
12398 substitute_in_expr(expr, row, ctx, outer_alias);
12399 substitute_in_expr(pattern, row, ctx, outer_alias);
12400 }
12401 Expr::FunctionCall { args, .. } => {
12402 for a in args {
12403 substitute_in_expr(a, row, ctx, outer_alias);
12404 }
12405 }
12406 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12407 Expr::WindowFunction {
12408 args,
12409 partition_by,
12410 order_by,
12411 ..
12412 } => {
12413 for a in args {
12414 substitute_in_expr(a, row, ctx, outer_alias);
12415 }
12416 for p in partition_by {
12417 substitute_in_expr(p, row, ctx, outer_alias);
12418 }
12419 for (o, _) in order_by {
12420 substitute_in_expr(o, row, ctx, outer_alias);
12421 }
12422 }
12423 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12424 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12425 substitute_in_select(subquery, row, ctx, outer_alias);
12426 }
12427 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12428 Expr::Array(items) => {
12429 for elem in items {
12430 substitute_in_expr(elem, row, ctx, outer_alias);
12431 }
12432 }
12433 Expr::ArraySubscript { target, index } => {
12434 substitute_in_expr(target, row, ctx, outer_alias);
12435 substitute_in_expr(index, row, ctx, outer_alias);
12436 }
12437 Expr::AnyAll { expr, array, .. } => {
12438 substitute_in_expr(expr, row, ctx, outer_alias);
12439 substitute_in_expr(array, row, ctx, outer_alias);
12440 }
12441 Expr::Case {
12442 operand,
12443 branches,
12444 else_branch,
12445 } => {
12446 if let Some(o) = operand {
12447 substitute_in_expr(o, row, ctx, outer_alias);
12448 }
12449 for (w, t) in branches {
12450 substitute_in_expr(w, row, ctx, outer_alias);
12451 substitute_in_expr(t, row, ctx, outer_alias);
12452 }
12453 if let Some(e) = else_branch {
12454 substitute_in_expr(e, row, ctx, outer_alias);
12455 }
12456 }
12457 }
12458}
12459
12460fn encode_row_key(row: &Row) -> Vec<u8> {
12464 let mut out = Vec::new();
12465 for v in &row.values {
12466 let s = alloc::format!("{v:?}|");
12467 out.extend_from_slice(s.as_bytes());
12468 }
12469 out
12470}
12471
12472fn select_has_window(stmt: &SelectStatement) -> bool {
12473 for item in &stmt.items {
12474 if let SelectItem::Expr { expr, .. } = item
12475 && expr_has_window(expr)
12476 {
12477 return true;
12478 }
12479 }
12480 false
12481}
12482
12483fn expr_has_window(e: &Expr) -> bool {
12484 match e {
12485 Expr::WindowFunction { .. } => true,
12486 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12487 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12488 expr_has_window(expr)
12489 }
12490 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12491 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12492 Expr::Extract { source, .. } => expr_has_window(source),
12493 Expr::ScalarSubquery(_)
12494 | Expr::Exists { .. }
12495 | Expr::InSubquery { .. }
12496 | Expr::Literal(_)
12497 | Expr::Placeholder(_)
12498 | Expr::Column(_) => false,
12499 Expr::Array(items) => items.iter().any(expr_has_window),
12500 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12501 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12502 Expr::Case {
12503 operand,
12504 branches,
12505 else_branch,
12506 } => {
12507 operand.as_deref().is_some_and(expr_has_window)
12508 || branches
12509 .iter()
12510 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12511 || else_branch.as_deref().is_some_and(expr_has_window)
12512 }
12513 }
12514}
12515
12516fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12517 if let Expr::WindowFunction { .. } = e {
12518 if !out.iter().any(|x| x == e) {
12523 out.push(e.clone());
12524 }
12525 return;
12526 }
12527 match e {
12528 Expr::WindowFunction { .. } => unreachable!(),
12530 Expr::Binary { lhs, rhs, .. } => {
12531 collect_window_nodes(lhs, out);
12532 collect_window_nodes(rhs, out);
12533 }
12534 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12535 collect_window_nodes(expr, out);
12536 }
12537 Expr::FunctionCall { args, .. } => {
12538 for a in args {
12539 collect_window_nodes(a, out);
12540 }
12541 }
12542 Expr::Like { expr, pattern, .. } => {
12543 collect_window_nodes(expr, out);
12544 collect_window_nodes(pattern, out);
12545 }
12546 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12547 _ => {}
12548 }
12549}
12550
12551fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12552 if let Expr::WindowFunction { .. } = e
12553 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12554 {
12555 *e = Expr::Column(spg_sql::ast::ColumnName {
12556 qualifier: None,
12557 name: alloc::format!("__win_{idx}"),
12558 });
12559 return;
12560 }
12561 match e {
12562 Expr::Binary { lhs, rhs, .. } => {
12563 rewrite_window_to_columns(lhs, window_nodes);
12564 rewrite_window_to_columns(rhs, window_nodes);
12565 }
12566 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12567 rewrite_window_to_columns(expr, window_nodes);
12568 }
12569 Expr::FunctionCall { args, .. } => {
12570 for a in args {
12571 rewrite_window_to_columns(a, window_nodes);
12572 }
12573 }
12574 Expr::Like { expr, pattern, .. } => {
12575 rewrite_window_to_columns(expr, window_nodes);
12576 rewrite_window_to_columns(pattern, window_nodes);
12577 }
12578 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12579 _ => {}
12580 }
12581}
12582
12583fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12587 for (x, y) in a.iter().zip(b.iter()) {
12588 let c = value_cmp(x, y);
12589 if c != core::cmp::Ordering::Equal {
12590 return c;
12591 }
12592 }
12593 a.len().cmp(&b.len())
12594}
12595
12596fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12597 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12598 let c = value_cmp(va, vb);
12599 let c = if *desc { c.reverse() } else { c };
12600 if c != core::cmp::Ordering::Equal {
12601 return c;
12602 }
12603 }
12604 a.len().cmp(&b.len())
12605}
12606
12607const fn value_is_integer(v: &Value) -> bool {
12613 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12614}
12615
12616const fn value_to_i64(v: &Value) -> i64 {
12620 match v {
12621 Value::SmallInt(n) => *n as i64,
12622 Value::Int(n) => *n as i64,
12623 Value::BigInt(n) => *n,
12624 _ => panic!("value_to_i64 called on non-integer Value"),
12625 }
12626}
12627
12628fn generate_series_integers(
12634 start: i64,
12635 stop: i64,
12636 step: i64,
12637 cancel: &CancelToken<'_>,
12638) -> Result<alloc::vec::Vec<Row>, EngineError> {
12639 if step == 0 {
12640 return Err(EngineError::Unsupported(
12641 "generate_series(): step argument cannot be zero".into(),
12642 ));
12643 }
12644 let mut out = alloc::vec::Vec::new();
12645 let mut cur = start;
12646 const MAX_ROWS: usize = 10_000_000;
12650 loop {
12651 cancel.check()?;
12652 if step > 0 && cur > stop {
12653 break;
12654 }
12655 if step < 0 && cur < stop {
12656 break;
12657 }
12658 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12659 if out.len() > MAX_ROWS {
12660 return Err(EngineError::Unsupported(alloc::format!(
12661 "generate_series(): exceeded {MAX_ROWS} rows; \
12662 narrow start/stop or use a larger step"
12663 )));
12664 }
12665 cur = match cur.checked_add(step) {
12666 Some(n) => n,
12667 None => break,
12668 };
12669 }
12670 Ok(out)
12671}
12672
12673fn generate_series_timestamps(
12678 start: i64,
12679 stop: i64,
12680 step: Value,
12681 cancel: &CancelToken<'_>,
12682) -> Result<alloc::vec::Vec<Row>, EngineError> {
12683 let (months, micros) = match &step {
12684 Value::Interval { months, micros } => (*months, *micros),
12685 _ => unreachable!("caller guards step.is_interval"),
12686 };
12687 if months == 0 && micros == 0 {
12688 return Err(EngineError::Unsupported(
12689 "generate_series(): INTERVAL step cannot be zero".into(),
12690 ));
12691 }
12692 let ascending = months > 0 || micros > 0;
12693 let mut out = alloc::vec::Vec::new();
12694 let mut cur = Value::Timestamp(start);
12695 const MAX_ROWS: usize = 10_000_000;
12696 loop {
12697 cancel.check()?;
12698 let cur_t = match cur {
12699 Value::Timestamp(t) => t,
12700 _ => unreachable!("loop invariant: cur is Timestamp"),
12701 };
12702 if ascending && cur_t > stop {
12703 break;
12704 }
12705 if !ascending && cur_t < stop {
12706 break;
12707 }
12708 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12709 if out.len() > MAX_ROWS {
12710 return Err(EngineError::Unsupported(alloc::format!(
12711 "generate_series(): exceeded {MAX_ROWS} rows; \
12712 narrow start/stop or use a larger step"
12713 )));
12714 }
12715 let next = eval::apply_binary_interval(
12716 spg_sql::ast::BinOp::Add,
12717 &cur,
12718 &Value::Interval { months, micros },
12719 )
12720 .map_err(EngineError::Eval)?;
12721 cur = match next {
12722 Some(v) => v,
12723 None => break,
12724 };
12725 }
12726 Ok(out)
12727}
12728
12729#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12731 use core::cmp::Ordering;
12732 match (a, b) {
12733 (Value::Null, Value::Null) => Ordering::Equal,
12734 (Value::Null, _) => Ordering::Less,
12735 (_, Value::Null) => Ordering::Greater,
12736 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12737 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12738 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12739 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12740 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12741 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12742 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12743 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12744 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12747 }
12748}
12749
12750#[allow(
12756 clippy::too_many_arguments,
12757 clippy::cast_possible_truncation,
12758 clippy::cast_possible_wrap,
12759 clippy::cast_precision_loss,
12760 clippy::cast_sign_loss,
12761 clippy::doc_markdown,
12762 clippy::too_many_lines,
12763 clippy::type_complexity,
12764 clippy::match_same_arms
12765)]
12766fn compute_window_partition(
12767 name: &str,
12768 args: &[Expr],
12769 ordered: bool,
12770 frame: Option<&WindowFrame>,
12771 null_treatment: spg_sql::ast::NullTreatment,
12772 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12773 filtered_rows: &[&Row],
12774 ctx: &EvalContext<'_>,
12775 out_vals: &mut [Value],
12776) -> Result<(), EngineError> {
12777 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12778 let lower = name.to_ascii_lowercase();
12779 match lower.as_str() {
12780 "row_number" => {
12781 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12782 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12783 }
12784 Ok(())
12785 }
12786 "rank" => {
12787 let mut prev_key: Option<&[(Value, bool)]> = None;
12788 let mut current_rank: i64 = 1;
12789 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12790 if let Some(p) = prev_key
12791 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12792 {
12793 current_rank = (i + 1) as i64;
12794 }
12795 if prev_key.is_none() {
12796 current_rank = 1;
12797 }
12798 out_vals[*idx] = Value::BigInt(current_rank);
12799 prev_key = Some(okey.as_slice());
12800 }
12801 Ok(())
12802 }
12803 "dense_rank" => {
12804 let mut prev_key: Option<&[(Value, bool)]> = None;
12805 let mut current_rank: i64 = 0;
12806 for (_, okey, idx) in slice {
12807 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12808 current_rank += 1;
12809 }
12810 out_vals[*idx] = Value::BigInt(current_rank);
12811 prev_key = Some(okey.as_slice());
12812 }
12813 Ok(())
12814 }
12815 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12816 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12819 slice.iter().map(|_| Value::Null).collect()
12820 } else {
12821 slice
12822 .iter()
12823 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12824 .collect::<Result<_, _>>()
12825 .map_err(EngineError::Eval)?
12826 };
12827 let eff = effective_frame(frame, ordered)?;
12831 #[allow(clippy::needless_range_loop)]
12832 for i in 0..slice.len() {
12833 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12834 let mut sum: f64 = 0.0;
12835 let mut count: i64 = 0;
12836 let mut min_v: Option<f64> = None;
12837 let mut max_v: Option<f64> = None;
12838 let mut row_count: i64 = 0;
12839 if lo <= hi {
12840 for j in lo..=hi {
12841 let v = &arg_values[j];
12842 match lower.as_str() {
12843 "count_star" => row_count += 1,
12844 "count" => {
12845 if !v.is_null() {
12846 count += 1;
12847 }
12848 }
12849 _ => {
12850 if let Some(x) = value_to_f64(v) {
12851 sum += x;
12852 count += 1;
12853 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12854 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12855 }
12856 }
12857 }
12858 }
12859 }
12860 let value = match lower.as_str() {
12861 "count_star" => Value::BigInt(row_count),
12862 "count" => Value::BigInt(count),
12863 "sum" => Value::Float(sum),
12864 "avg" => {
12865 if count == 0 {
12866 Value::Null
12867 } else {
12868 Value::Float(sum / count as f64)
12869 }
12870 }
12871 "min" => min_v.map_or(Value::Null, Value::Float),
12872 "max" => max_v.map_or(Value::Null, Value::Float),
12873 _ => unreachable!(),
12874 };
12875 let (_, _, idx) = &slice[i];
12876 out_vals[*idx] = value;
12877 }
12878 Ok(())
12879 }
12880 "lag" | "lead" => {
12881 if args.is_empty() {
12884 return Err(EngineError::Unsupported(alloc::format!(
12885 "{lower}() requires at least one argument"
12886 )));
12887 }
12888 let offset: i64 = if args.len() >= 2 {
12889 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12890 .map_err(EngineError::Eval)?;
12891 match v {
12892 Value::SmallInt(n) => i64::from(n),
12893 Value::Int(n) => i64::from(n),
12894 Value::BigInt(n) => n,
12895 _ => {
12896 return Err(EngineError::Unsupported(alloc::format!(
12897 "{lower}() offset must be integer"
12898 )));
12899 }
12900 }
12901 } else {
12902 1
12903 };
12904 let default: Value = if args.len() >= 3 {
12905 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12906 .map_err(EngineError::Eval)?
12907 } else {
12908 Value::Null
12909 };
12910 let values: Vec<Value> = slice
12911 .iter()
12912 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12913 .collect::<Result<_, _>>()
12914 .map_err(EngineError::Eval)?;
12915 let n = slice.len();
12916 for (i, (_, _, idx)) in slice.iter().enumerate() {
12917 let signed_offset = if lower == "lag" { -offset } else { offset };
12918 let v = if ignore_nulls {
12919 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12923 let needed: i64 = signed_offset.abs();
12924 if needed == 0 {
12925 values[i].clone()
12926 } else {
12927 let mut j: i64 = i as i64;
12928 let mut hits: i64 = 0;
12929 let mut found: Option<Value> = None;
12930 loop {
12931 j += step;
12932 if j < 0 || j >= n as i64 {
12933 break;
12934 }
12935 #[allow(clippy::cast_sign_loss)]
12936 let v = &values[j as usize];
12937 if !v.is_null() {
12938 hits += 1;
12939 if hits == needed {
12940 found = Some(v.clone());
12941 break;
12942 }
12943 }
12944 }
12945 found.unwrap_or_else(|| default.clone())
12946 }
12947 } else {
12948 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12949 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12950 default.clone()
12951 } else {
12952 #[allow(clippy::cast_sign_loss)]
12953 {
12954 values[target_signed as usize].clone()
12955 }
12956 }
12957 };
12958 out_vals[*idx] = v;
12959 }
12960 Ok(())
12961 }
12962 "first_value" | "last_value" | "nth_value" => {
12963 if args.is_empty() {
12964 return Err(EngineError::Unsupported(alloc::format!(
12965 "{lower}() requires at least one argument"
12966 )));
12967 }
12968 let values: Vec<Value> = slice
12969 .iter()
12970 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12971 .collect::<Result<_, _>>()
12972 .map_err(EngineError::Eval)?;
12973 let nth: usize = if lower == "nth_value" {
12974 if args.len() < 2 {
12975 return Err(EngineError::Unsupported(
12976 "nth_value() requires (expr, n)".into(),
12977 ));
12978 }
12979 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12980 .map_err(EngineError::Eval)?;
12981 let raw = match v {
12982 Value::SmallInt(n) => i64::from(n),
12983 Value::Int(n) => i64::from(n),
12984 Value::BigInt(n) => n,
12985 _ => {
12986 return Err(EngineError::Unsupported(
12987 "nth_value() n must be integer".into(),
12988 ));
12989 }
12990 };
12991 if raw < 1 {
12992 return Err(EngineError::Unsupported(
12993 "nth_value() n must be >= 1".into(),
12994 ));
12995 }
12996 #[allow(clippy::cast_sign_loss)]
12997 {
12998 raw as usize
12999 }
13000 } else {
13001 0
13002 };
13003 let eff = effective_frame(frame, ordered)?;
13004 for i in 0..slice.len() {
13005 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13006 let (_, _, idx) = &slice[i];
13007 let v = if lo > hi {
13008 Value::Null
13009 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
13010 if lower == "first_value" {
13013 (lo..=hi)
13014 .find_map(|j| {
13015 let v = &values[j];
13016 (!v.is_null()).then(|| v.clone())
13017 })
13018 .unwrap_or(Value::Null)
13019 } else {
13020 (lo..=hi)
13021 .rev()
13022 .find_map(|j| {
13023 let v = &values[j];
13024 (!v.is_null()).then(|| v.clone())
13025 })
13026 .unwrap_or(Value::Null)
13027 }
13028 } else {
13029 match lower.as_str() {
13030 "first_value" => values[lo].clone(),
13031 "last_value" => values[hi].clone(),
13032 "nth_value" => {
13033 let pos = lo + nth - 1;
13034 if pos > hi {
13035 Value::Null
13036 } else {
13037 values[pos].clone()
13038 }
13039 }
13040 _ => unreachable!(),
13041 }
13042 };
13043 out_vals[*idx] = v;
13044 }
13045 Ok(())
13046 }
13047 "ntile" => {
13048 if args.is_empty() {
13049 return Err(EngineError::Unsupported(
13050 "ntile(n) requires an integer argument".into(),
13051 ));
13052 }
13053 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
13054 .map_err(EngineError::Eval)?;
13055 let bucket_count: i64 = match v {
13056 Value::SmallInt(n) => i64::from(n),
13057 Value::Int(n) => i64::from(n),
13058 Value::BigInt(n) => n,
13059 _ => {
13060 return Err(EngineError::Unsupported(
13061 "ntile() argument must be integer".into(),
13062 ));
13063 }
13064 };
13065 if bucket_count < 1 {
13066 return Err(EngineError::Unsupported(
13067 "ntile() argument must be >= 1".into(),
13068 ));
13069 }
13070 #[allow(clippy::cast_sign_loss)]
13071 let buckets = bucket_count as usize;
13072 let n = slice.len();
13073 let base = n / buckets;
13076 let extras = n % buckets;
13077 let mut bucket: usize = 1;
13078 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
13079 let mut buckets_with_extra_remaining = extras;
13080 for (_, _, idx) in slice {
13081 if remaining_in_bucket == 0 {
13082 bucket += 1;
13083 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
13084 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
13085 base + 1
13086 } else {
13087 base
13088 };
13089 if remaining_in_bucket == 0 {
13092 remaining_in_bucket = 1;
13093 }
13094 }
13095 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13096 remaining_in_bucket -= 1;
13097 }
13098 Ok(())
13099 }
13100 "percent_rank" => {
13101 let n = slice.len();
13104 let mut prev_key: Option<&[(Value, bool)]> = None;
13105 let mut current_rank: i64 = 1;
13106 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13107 if let Some(p) = prev_key
13108 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13109 {
13110 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13111 }
13112 if prev_key.is_none() {
13113 current_rank = 1;
13114 }
13115 #[allow(clippy::cast_precision_loss)]
13116 let pr = if n <= 1 {
13117 0.0
13118 } else {
13119 (current_rank - 1) as f64 / (n - 1) as f64
13120 };
13121 out_vals[*idx] = Value::Float(pr);
13122 prev_key = Some(okey.as_slice());
13123 }
13124 Ok(())
13125 }
13126 "cume_dist" => {
13127 let n = slice.len();
13129 for i in 0..slice.len() {
13131 let peer_end = peer_group_end(slice, i);
13132 #[allow(clippy::cast_precision_loss)]
13133 let cd = (peer_end + 1) as f64 / n as f64;
13134 let (_, _, idx) = &slice[i];
13135 out_vals[*idx] = Value::Float(cd);
13136 }
13137 Ok(())
13138 }
13139 other => Err(EngineError::Unsupported(alloc::format!(
13140 "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)"
13141 ))),
13142 }
13143}
13144
13145fn effective_frame(
13152 frame: Option<&WindowFrame>,
13153 ordered: bool,
13154) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13155 match frame {
13156 None => {
13157 if ordered {
13158 Ok((
13159 FrameKind::Range,
13160 FrameBound::UnboundedPreceding,
13161 FrameBound::CurrentRow,
13162 ))
13163 } else {
13164 Ok((
13165 FrameKind::Rows,
13166 FrameBound::UnboundedPreceding,
13167 FrameBound::UnboundedFollowing,
13168 ))
13169 }
13170 }
13171 Some(fr) => {
13172 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13173 if matches!(fr.start, FrameBound::UnboundedFollowing)
13175 || matches!(end, FrameBound::UnboundedPreceding)
13176 {
13177 return Err(EngineError::Unsupported(alloc::format!(
13178 "invalid frame: start={:?} end={:?}",
13179 fr.start,
13180 end
13181 )));
13182 }
13183 if fr.kind == FrameKind::Range
13188 && (matches!(
13189 fr.start,
13190 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13191 ) || matches!(
13192 end,
13193 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13194 ))
13195 {
13196 return Err(EngineError::Unsupported(
13197 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13198 ));
13199 }
13200 Ok((fr.kind, fr.start.clone(), end))
13201 }
13202 }
13203}
13204
13205#[allow(clippy::type_complexity)]
13209fn frame_bounds_for_row(
13210 eff: &(FrameKind, FrameBound, FrameBound),
13211 i: usize,
13212 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
13213) -> (usize, usize) {
13214 let (kind, start, end) = eff;
13215 let n = slice.len();
13216 let last = n.saturating_sub(1);
13217 let (mut lo, mut hi) = match kind {
13218 FrameKind::Rows => {
13219 let lo = match start {
13220 FrameBound::UnboundedPreceding => 0,
13221 FrameBound::OffsetPreceding(k) => {
13222 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13223 i.saturating_sub(k)
13224 }
13225 FrameBound::CurrentRow => i,
13226 FrameBound::OffsetFollowing(k) => {
13227 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13228 i.saturating_add(k).min(last)
13229 }
13230 FrameBound::UnboundedFollowing => last,
13231 };
13232 let hi = match end {
13233 FrameBound::UnboundedPreceding => 0,
13234 FrameBound::OffsetPreceding(k) => {
13235 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13236 i.saturating_sub(k)
13237 }
13238 FrameBound::CurrentRow => i,
13239 FrameBound::OffsetFollowing(k) => {
13240 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13241 i.saturating_add(k).min(last)
13242 }
13243 FrameBound::UnboundedFollowing => last,
13244 };
13245 (lo, hi)
13246 }
13247 FrameKind::Range => {
13248 let lo = match start {
13254 FrameBound::UnboundedPreceding => 0,
13255 FrameBound::CurrentRow => peer_group_start(slice, i),
13256 FrameBound::UnboundedFollowing => last,
13257 _ => unreachable!("offset bounds rejected for RANGE"),
13258 };
13259 let hi = match end {
13260 FrameBound::UnboundedPreceding => 0,
13261 FrameBound::CurrentRow => peer_group_end(slice, i),
13262 FrameBound::UnboundedFollowing => last,
13263 _ => unreachable!("offset bounds rejected for RANGE"),
13264 };
13265 (lo, hi)
13266 }
13267 };
13268 if hi >= n {
13269 hi = last;
13270 }
13271 if lo >= n {
13272 lo = last;
13273 }
13274 (lo, hi)
13275}
13276
13277#[allow(clippy::type_complexity)]
13281fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13282 let key = &slice[i].1;
13283 let mut j = i;
13284 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13285 j -= 1;
13286 }
13287 j
13288}
13289
13290#[allow(clippy::type_complexity)]
13293fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13294 let key = &slice[i].1;
13295 let mut j = i;
13296 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13297 j += 1;
13298 }
13299 j
13300}
13301
13302fn value_to_f64(v: &Value) -> Option<f64> {
13303 match v {
13304 Value::SmallInt(n) => Some(f64::from(*n)),
13305 Value::Int(n) => Some(f64::from(*n)),
13306 #[allow(clippy::cast_precision_loss)]
13307 Value::BigInt(n) => Some(*n as f64),
13308 Value::Float(x) => Some(*x),
13309 _ => None,
13310 }
13311}
13312
13313fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13317 let mut any = false;
13318 for item in &stmt.items {
13319 if let SelectItem::Expr { expr, .. } = item {
13320 any = any || expr_has_subquery(expr);
13321 }
13322 }
13323 if let Some(w) = &stmt.where_ {
13324 any = any || expr_has_subquery(w);
13325 }
13326 if let Some(h) = &stmt.having {
13327 any = any || expr_has_subquery(h);
13328 }
13329 for o in &stmt.order_by {
13330 any = any || expr_has_subquery(&o.expr);
13331 }
13332 for (_, peer) in &stmt.unions {
13333 any = any || expr_tree_has_subquery(peer);
13334 }
13335 any
13336}
13337
13338fn expr_has_subquery(e: &Expr) -> bool {
13339 match e {
13340 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13341 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13342 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13343 expr_has_subquery(expr)
13344 }
13345 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13346 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13347 Expr::Extract { source, .. } => expr_has_subquery(source),
13348 Expr::WindowFunction {
13349 args,
13350 partition_by,
13351 order_by,
13352 ..
13353 } => {
13354 args.iter().any(expr_has_subquery)
13355 || partition_by.iter().any(expr_has_subquery)
13356 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13357 }
13358 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13359 Expr::Array(items) => items.iter().any(expr_has_subquery),
13360 Expr::ArraySubscript { target, index } => {
13361 expr_has_subquery(target) || expr_has_subquery(index)
13362 }
13363 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13364 Expr::Case {
13365 operand,
13366 branches,
13367 else_branch,
13368 } => {
13369 operand.as_deref().is_some_and(expr_has_subquery)
13370 || branches
13371 .iter()
13372 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13373 || else_branch.as_deref().is_some_and(expr_has_subquery)
13374 }
13375 }
13376}
13377
13378fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13385 let lit = match v {
13386 Value::Null => Literal::Null,
13387 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13388 Value::Int(n) => Literal::Integer(i64::from(n)),
13389 Value::BigInt(n) => Literal::Integer(n),
13390 Value::Float(x) => Literal::Float(x),
13391 Value::Text(s) | Value::Json(s) => Literal::String(s),
13392 Value::Bool(b) => Literal::Bool(b),
13393 other => {
13394 return Err(EngineError::Unsupported(alloc::format!(
13395 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13396 other.data_type()
13397 )));
13398 }
13399 };
13400 Ok(Expr::Literal(lit))
13401}
13402
13403fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13409 let lit = match v {
13410 Value::Null => Literal::Null,
13411 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13412 Value::Int(n) => Literal::Integer(i64::from(n)),
13413 Value::BigInt(n) => Literal::Integer(n),
13414 Value::Float(x) => Literal::Float(x),
13415 Value::Text(s) | Value::Json(s) => Literal::String(s),
13416 Value::Bool(b) => Literal::Bool(b),
13417 Value::Vector(xs) => Literal::Vector(xs),
13418 Value::Date(days) => {
13422 let micros = (i64::from(days)) * 86_400_000_000;
13423 Literal::String(format_timestamp_micros_as_date(micros))
13424 }
13425 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13426 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13427 other => {
13428 return Err(EngineError::Unsupported(alloc::format!(
13429 "INSERT … SELECT cannot materialise value of type {:?}; \
13430 add an explicit CAST in the inner SELECT",
13431 other.data_type()
13432 )));
13433 }
13434 };
13435 Ok(Expr::Literal(lit))
13436}
13437
13438fn format_timestamp_micros(us: i64) -> String {
13439 let days = us.div_euclid(86_400_000_000);
13441 let intra_day = us.rem_euclid(86_400_000_000);
13442 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13443 let secs = intra_day / 1_000_000;
13444 let us_rem = intra_day % 1_000_000;
13445 let h = (secs / 3600) % 24;
13446 let m = (secs / 60) % 60;
13447 let s = secs % 60;
13448 if us_rem == 0 {
13449 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13450 } else {
13451 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13452 }
13453}
13454
13455fn format_timestamp_micros_as_date(us: i64) -> String {
13456 let days = us.div_euclid(86_400_000_000);
13459 let jdn = days + 2_440_588;
13461 let (y, mo, d) = jdn_to_ymd(jdn);
13462 alloc::format!("{y:04}-{mo:02}-{d:02}")
13463}
13464
13465fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13466 let l = jdn + 68569;
13468 let n = (4 * l) / 146_097;
13469 let l = l - (146_097 * n + 3) / 4;
13470 let i = (4000 * (l + 1)) / 1_461_001;
13471 let l = l - (1461 * i) / 4 + 31;
13472 let j = (80 * l) / 2447;
13473 let day = (l - (2447 * j) / 80) as u32;
13474 let l = j / 11;
13475 let month = (j + 2 - 12 * l) as u32;
13476 let year = 100 * (n - 49) + i + l;
13477 (year, month, day)
13478}
13479
13480fn format_numeric(scaled: i128, scale: u8) -> String {
13481 if scale == 0 {
13482 return alloc::format!("{scaled}");
13483 }
13484 let abs = scaled.unsigned_abs();
13485 let divisor = 10u128.pow(u32::from(scale));
13486 let whole = abs / divisor;
13487 let frac = abs % divisor;
13488 let sign = if scaled < 0 { "-" } else { "" };
13489 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13490}
13491
13492fn rewrite_column_in_source(
13516 src: &str,
13517 old: &str,
13518 new: &str,
13519) -> Result<alloc::string::String, EngineError> {
13520 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13521 EngineError::Unsupported(alloc::format!(
13522 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13523 failed to parse for rewrite ({e})"
13524 ))
13525 })?;
13526 rewrite_column_in_expr(&mut expr, old, new);
13527 Ok(alloc::format!("{expr}"))
13528}
13529
13530fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13538 match e {
13539 Expr::Column(c) => {
13540 if c.name.eq_ignore_ascii_case(old) {
13541 c.name = new.to_string();
13542 }
13543 }
13544 Expr::Binary { lhs, rhs, .. } => {
13545 rewrite_column_in_expr(lhs, old, new);
13546 rewrite_column_in_expr(rhs, old, new);
13547 }
13548 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13549 rewrite_column_in_expr(expr, old, new);
13550 }
13551 Expr::FunctionCall { args, .. } => {
13552 for a in args {
13553 rewrite_column_in_expr(a, old, new);
13554 }
13555 }
13556 Expr::Like { expr, pattern, .. } => {
13557 rewrite_column_in_expr(expr, old, new);
13558 rewrite_column_in_expr(pattern, old, new);
13559 }
13560 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13561 Expr::WindowFunction {
13562 args,
13563 partition_by,
13564 order_by,
13565 ..
13566 } => {
13567 for a in args {
13568 rewrite_column_in_expr(a, old, new);
13569 }
13570 for p in partition_by {
13571 rewrite_column_in_expr(p, old, new);
13572 }
13573 for (o, _) in order_by {
13574 rewrite_column_in_expr(o, old, new);
13575 }
13576 }
13577 Expr::Array(items) => {
13578 for elem in items {
13579 rewrite_column_in_expr(elem, old, new);
13580 }
13581 }
13582 Expr::ArraySubscript { target, index } => {
13583 rewrite_column_in_expr(target, old, new);
13584 rewrite_column_in_expr(index, old, new);
13585 }
13586 Expr::AnyAll { expr, array, .. } => {
13587 rewrite_column_in_expr(expr, old, new);
13588 rewrite_column_in_expr(array, old, new);
13589 }
13590 Expr::Case {
13591 operand,
13592 branches,
13593 else_branch,
13594 } => {
13595 if let Some(o) = operand {
13596 rewrite_column_in_expr(o, old, new);
13597 }
13598 for (w, t) in branches {
13599 rewrite_column_in_expr(w, old, new);
13600 rewrite_column_in_expr(t, old, new);
13601 }
13602 if let Some(e) = else_branch {
13603 rewrite_column_in_expr(e, old, new);
13604 }
13605 }
13606 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13610 Expr::Literal(_) | Expr::Placeholder(_) => {}
13611 }
13612}
13613
13614pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13622 match stmt {
13623 Statement::Select(s) => substitute_select(s, params)?,
13624 Statement::Insert(ins) => {
13625 for row in &mut ins.rows {
13626 for e in row {
13627 substitute_expr(e, params)?;
13628 }
13629 }
13630 if let Some(clause) = &mut ins.on_conflict
13634 && let spg_sql::ast::OnConflictAction::Update {
13635 assignments,
13636 where_,
13637 } = &mut clause.action
13638 {
13639 for (_, e) in assignments.iter_mut() {
13640 substitute_expr(e, params)?;
13641 }
13642 if let Some(w) = where_ {
13643 substitute_expr(w, params)?;
13644 }
13645 }
13646 }
13647 Statement::Update(u) => {
13648 for (_, e) in &mut u.assignments {
13649 substitute_expr(e, params)?;
13650 }
13651 if let Some(w) = &mut u.where_ {
13652 substitute_expr(w, params)?;
13653 }
13654 }
13655 Statement::Delete(d) => {
13656 if let Some(w) = &mut d.where_ {
13657 substitute_expr(w, params)?;
13658 }
13659 }
13660 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13661 _ => {}
13664 }
13665 Ok(())
13666}
13667
13668fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13669 for item in &mut s.items {
13670 if let SelectItem::Expr { expr, .. } = item {
13671 substitute_expr(expr, params)?;
13672 }
13673 }
13674 if let Some(w) = &mut s.where_ {
13675 substitute_expr(w, params)?;
13676 }
13677 if let Some(gs) = &mut s.group_by {
13678 for g in gs {
13679 substitute_expr(g, params)?;
13680 }
13681 }
13682 if let Some(h) = &mut s.having {
13683 substitute_expr(h, params)?;
13684 }
13685 for o in &mut s.order_by {
13686 substitute_expr(&mut o.expr, params)?;
13687 }
13688 for (_, peer) in &mut s.unions {
13689 substitute_select(peer, params)?;
13690 }
13691 if let Some(le) = s.limit {
13696 s.limit = Some(resolve_limit_placeholder(le, params)?);
13697 }
13698 if let Some(le) = s.offset {
13699 s.offset = Some(resolve_limit_placeholder(le, params)?);
13700 }
13701 Ok(())
13702}
13703
13704fn resolve_limit_placeholder(
13705 le: spg_sql::ast::LimitExpr,
13706 params: &[Value],
13707) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13708 use spg_sql::ast::LimitExpr;
13709 match le {
13710 LimitExpr::Literal(_) => Ok(le),
13711 LimitExpr::Placeholder(n) => {
13712 let idx = usize::from(n).saturating_sub(1);
13713 let v = params.get(idx).ok_or_else(|| {
13714 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13715 n,
13716 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13717 })
13718 })?;
13719 let int = match v {
13720 Value::SmallInt(x) => Some(i64::from(*x)),
13721 Value::Int(x) => Some(i64::from(*x)),
13722 Value::BigInt(x) => Some(*x),
13723 _ => None,
13724 }
13725 .ok_or_else(|| {
13726 EngineError::Unsupported(alloc::format!(
13727 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13728 ))
13729 })?;
13730 if int < 0 {
13731 return Err(EngineError::Unsupported(alloc::format!(
13732 "LIMIT/OFFSET ${n} bound to negative value {int}"
13733 )));
13734 }
13735 let bounded = u32::try_from(int).map_err(|_| {
13736 EngineError::Unsupported(alloc::format!(
13737 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13738 ))
13739 })?;
13740 Ok(LimitExpr::Literal(bounded))
13741 }
13742 }
13743}
13744
13745fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13746 if let Expr::Placeholder(n) = e {
13747 let idx = usize::from(*n).saturating_sub(1);
13748 let v = params.get(idx).ok_or_else(|| {
13749 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13750 n: *n,
13751 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13752 })
13753 })?;
13754 *e = Expr::Literal(value_to_literal(v.clone()));
13755 return Ok(());
13756 }
13757 match e {
13758 Expr::Binary { lhs, rhs, .. } => {
13759 substitute_expr(lhs, params)?;
13760 substitute_expr(rhs, params)?;
13761 }
13762 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13763 substitute_expr(expr, params)?;
13764 }
13765 Expr::FunctionCall { args, .. } => {
13766 for a in args {
13767 substitute_expr(a, params)?;
13768 }
13769 }
13770 Expr::Like { expr, pattern, .. } => {
13771 substitute_expr(expr, params)?;
13772 substitute_expr(pattern, params)?;
13773 }
13774 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13775 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13776 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13777 Expr::InSubquery { expr, subquery, .. } => {
13778 substitute_expr(expr, params)?;
13779 substitute_select(subquery, params)?;
13780 }
13781 Expr::WindowFunction {
13782 args,
13783 partition_by,
13784 order_by,
13785 ..
13786 } => {
13787 for a in args {
13788 substitute_expr(a, params)?;
13789 }
13790 for p in partition_by {
13791 substitute_expr(p, params)?;
13792 }
13793 for (e, _) in order_by {
13794 substitute_expr(e, params)?;
13795 }
13796 }
13797 Expr::Literal(_) | Expr::Column(_) => {}
13798 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13800 Expr::Array(items) => {
13801 for elem in items {
13802 substitute_expr(elem, params)?;
13803 }
13804 }
13805 Expr::ArraySubscript { target, index } => {
13806 substitute_expr(target, params)?;
13807 substitute_expr(index, params)?;
13808 }
13809 Expr::AnyAll { expr, array, .. } => {
13810 substitute_expr(expr, params)?;
13811 substitute_expr(array, params)?;
13812 }
13813 Expr::Case {
13814 operand,
13815 branches,
13816 else_branch,
13817 } => {
13818 if let Some(o) = operand {
13819 substitute_expr(o, params)?;
13820 }
13821 for (w, t) in branches {
13822 substitute_expr(w, params)?;
13823 substitute_expr(t, params)?;
13824 }
13825 if let Some(e) = else_branch {
13826 substitute_expr(e, params)?;
13827 }
13828 }
13829 }
13830 Ok(())
13831}
13832
13833fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13851 use core::cmp::Ordering;
13852 match (a, b) {
13853 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13854 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13855 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13856 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13857 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13858 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13859 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13860 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13861 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13862 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13863 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13864 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13865 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13866 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13867 (Value::SmallInt(n), Value::Float(x)) => {
13869 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13870 }
13871 (Value::Float(x), Value::SmallInt(n)) => {
13872 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13873 }
13874 (Value::Int(n), Value::Float(x)) => {
13875 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13876 }
13877 (Value::Float(x), Value::Int(n)) => {
13878 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13879 }
13880 (Value::BigInt(n), Value::Float(x)) => {
13881 #[allow(clippy::cast_precision_loss)]
13882 let nf = *n as f64;
13883 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13884 }
13885 (Value::Float(x), Value::BigInt(n)) => {
13886 #[allow(clippy::cast_precision_loss)]
13887 let nf = *n as f64;
13888 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13889 }
13890 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13893 }
13894}
13895
13896fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13903 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13904 out.push('[');
13905 for (i, b) in bounds.iter().enumerate() {
13906 if i > 0 {
13907 out.push_str(", ");
13908 }
13909 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13910 if needs_quote {
13911 out.push('"');
13912 for ch in b.chars() {
13913 if ch == '"' || ch == '\\' {
13914 out.push('\\');
13915 }
13916 out.push(ch);
13917 }
13918 out.push('"');
13919 } else {
13920 out.push_str(b);
13921 }
13922 }
13923 out.push(']');
13924 out
13925}
13926
13927pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13937 match v {
13938 Value::Null => "NULL".to_string(),
13939 Value::SmallInt(n) => alloc::format!("{n}"),
13940 Value::Int(n) => alloc::format!("{n}"),
13941 Value::BigInt(n) => alloc::format!("{n}"),
13942 Value::Float(x) => alloc::format!("{x:?}"),
13943 Value::Text(s) | Value::Json(s) => s.clone(),
13944 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13945 Value::Date(d) => eval::format_date(*d),
13946 Value::Timestamp(t) => eval::format_timestamp(*t),
13947 Value::Time(us) => eval::format_time(*us),
13949 Value::Year(y) => alloc::format!("{y:04}"),
13951 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13953 Value::Money(c) => eval::format_money(*c),
13955 v @ Value::Range { .. } => format_range_str(v),
13957 Value::Hstore(pairs) => format_hstore_str(pairs),
13959 Value::IntArray2D(rows) => format_int_2d_text(rows),
13961 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13962 Value::TextArray2D(rows) => format_text_2d_text(rows),
13963 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13964 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13965 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13966 alloc::format!("{v:?}")
13970 }
13971 _ => alloc::format!("{v:?}"),
13975 }
13976}
13977
13978const fn is_internal_table_name(_name: &str) -> bool {
13985 false
13986}
13987
13988fn value_to_literal(v: Value) -> Literal {
13989 match v {
13990 Value::Null => Literal::Null,
13991 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13992 Value::Int(n) => Literal::Integer(i64::from(n)),
13993 Value::BigInt(n) => Literal::Integer(n),
13994 Value::Float(x) => Literal::Float(x),
13995 Value::Text(s) | Value::Json(s) => Literal::String(s),
13996 Value::Bool(b) => Literal::Bool(b),
13997 Value::Vector(v) => Literal::Vector(v),
13998 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
13999 Value::Date(d) => Literal::String(eval::format_date(d)),
14000 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14001 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14007 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14012 Value::TextArray(items) => Literal::TextArray(items),
14017 Value::IntArray(items) => Literal::IntArray(items),
14018 Value::BigIntArray(items) => Literal::BigIntArray(items),
14019 Value::Interval { months, micros } => Literal::Interval {
14020 months,
14021 micros,
14022 text: eval::format_interval(months, micros),
14023 },
14024 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14027 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14028 v => Literal::String(alloc::format!("{v:?}")),
14032 }
14033}
14034
14035fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14036 let Some(now) = now_micros else {
14037 return;
14038 };
14039 match stmt {
14040 Statement::Select(s) => rewrite_select_clock(s, now),
14041 Statement::Insert(ins) => {
14042 for row in &mut ins.rows {
14043 for e in row {
14044 rewrite_expr_clock(e, now);
14045 }
14046 }
14047 if let Some(clause) = &mut ins.on_conflict
14051 && let spg_sql::ast::OnConflictAction::Update {
14052 assignments,
14053 where_,
14054 } = &mut clause.action
14055 {
14056 for (_, e) in assignments.iter_mut() {
14057 rewrite_expr_clock(e, now);
14058 }
14059 if let Some(w) = where_ {
14060 rewrite_expr_clock(w, now);
14061 }
14062 }
14063 }
14064 Statement::Update(u) => {
14068 for (_, e) in &mut u.assignments {
14069 rewrite_expr_clock(e, now);
14070 }
14071 if let Some(w) = &mut u.where_ {
14072 rewrite_expr_clock(w, now);
14073 }
14074 }
14075 Statement::Delete(d) => {
14076 if let Some(w) = &mut d.where_ {
14077 rewrite_expr_clock(w, now);
14078 }
14079 }
14080 _ => {}
14081 }
14082}
14083
14084fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14085 for item in &mut s.items {
14086 if let SelectItem::Expr { expr, .. } = item {
14087 rewrite_expr_clock(expr, now);
14088 }
14089 }
14090 if let Some(w) = &mut s.where_ {
14091 rewrite_expr_clock(w, now);
14092 }
14093 if let Some(gs) = &mut s.group_by {
14094 for g in gs {
14095 rewrite_expr_clock(g, now);
14096 }
14097 }
14098 if let Some(h) = &mut s.having {
14099 rewrite_expr_clock(h, now);
14100 }
14101 for o in &mut s.order_by {
14102 rewrite_expr_clock(&mut o.expr, now);
14103 }
14104 for (_, peer) in &mut s.unions {
14105 rewrite_select_clock(peer, now);
14106 }
14107}
14108
14109fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14117 if let Some(replacement) = clock_replacement_for(e, now) {
14121 *e = replacement;
14122 return;
14123 }
14124 match e {
14125 Expr::Binary { lhs, rhs, .. } => {
14126 rewrite_expr_clock(lhs, now);
14127 rewrite_expr_clock(rhs, now);
14128 }
14129 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14130 rewrite_expr_clock(expr, now);
14131 }
14132 Expr::FunctionCall { args, .. } => {
14133 for a in args {
14134 rewrite_expr_clock(a, now);
14135 }
14136 }
14137 Expr::Like { expr, pattern, .. } => {
14138 rewrite_expr_clock(expr, now);
14139 rewrite_expr_clock(pattern, now);
14140 }
14141 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14142 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14146 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14147 Expr::InSubquery { expr, subquery, .. } => {
14148 rewrite_expr_clock(expr, now);
14149 rewrite_select_clock(subquery, now);
14150 }
14151 Expr::WindowFunction {
14154 args,
14155 partition_by,
14156 order_by,
14157 ..
14158 } => {
14159 for a in args {
14160 rewrite_expr_clock(a, now);
14161 }
14162 for p in partition_by {
14163 rewrite_expr_clock(p, now);
14164 }
14165 for (e, _) in order_by {
14166 rewrite_expr_clock(e, now);
14167 }
14168 }
14169 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14170 Expr::Array(items) => {
14171 for elem in items {
14172 rewrite_expr_clock(elem, now);
14173 }
14174 }
14175 Expr::ArraySubscript { target, index } => {
14176 rewrite_expr_clock(target, now);
14177 rewrite_expr_clock(index, now);
14178 }
14179 Expr::AnyAll { expr, array, .. } => {
14180 rewrite_expr_clock(expr, now);
14181 rewrite_expr_clock(array, now);
14182 }
14183 Expr::Case {
14184 operand,
14185 branches,
14186 else_branch,
14187 } => {
14188 if let Some(o) = operand {
14189 rewrite_expr_clock(o, now);
14190 }
14191 for (w, t) in branches {
14192 rewrite_expr_clock(w, now);
14193 rewrite_expr_clock(t, now);
14194 }
14195 if let Some(e) = else_branch {
14196 rewrite_expr_clock(e, now);
14197 }
14198 }
14199 }
14200}
14201
14202fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14209 let (kind, name) = match e {
14210 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14211 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14212 _ => return None,
14213 };
14214 enum ClockShape {
14222 Timestamp,
14223 Date,
14224 UnixSeconds,
14225 }
14226 let shape = match name.len() {
14227 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14228 Some(ClockShape::Timestamp)
14229 }
14230 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14231 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14232 Some(ClockShape::UnixSeconds)
14233 }
14234 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14235 _ => None,
14236 };
14237 let shape = shape?;
14238 let payload = match shape {
14239 ClockShape::Timestamp => now,
14240 ClockShape::Date => now.div_euclid(86_400_000_000),
14241 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14242 };
14243 let target = match shape {
14244 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14245 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14246 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14247 };
14248 Some(Expr::Cast {
14249 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14250 target,
14251 })
14252}
14253
14254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14255enum ClockSite {
14256 Fn,
14257 BareIdent,
14258}
14259
14260fn expand_group_by_all(s: &mut SelectStatement) {
14271 if !s.group_by_all {
14272 for (_, peer) in &mut s.unions {
14273 expand_group_by_all(peer);
14274 }
14275 return;
14276 }
14277 let mut groups: Vec<Expr> = Vec::new();
14278 for item in &s.items {
14279 if let SelectItem::Expr { expr, .. } = item
14280 && !aggregate::contains_aggregate(expr)
14281 {
14282 groups.push(expr.clone());
14283 }
14284 }
14285 s.group_by = Some(groups);
14286 s.group_by_all = false;
14287 for (_, peer) in &mut s.unions {
14288 expand_group_by_all(peer);
14289 }
14290}
14291
14292fn resolve_order_by_position(s: &mut SelectStatement) {
14293 for order in &mut s.order_by {
14298 match &order.expr {
14299 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14300 if let Ok(idx_one_based) = usize::try_from(*n) {
14301 let idx = idx_one_based - 1;
14302 if idx < s.items.len()
14303 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14304 {
14305 order.expr = expr.clone();
14306 }
14307 }
14308 }
14309 Expr::Column(c) if c.qualifier.is_none() => {
14310 for item in &s.items {
14312 if let SelectItem::Expr {
14313 expr,
14314 alias: Some(a),
14315 } = item
14316 && a == &c.name
14317 {
14318 order.expr = expr.clone();
14319 break;
14320 }
14321 }
14322 }
14323 _ => {}
14324 }
14325 }
14326 for (_, peer) in &mut s.unions {
14327 resolve_order_by_position(peer);
14328 }
14329}
14330
14331fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14344 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14345 match keep {
14346 Some(k) if k < tagged.len() && k > 0 => {
14347 let pivot = k - 1;
14348 tagged.select_nth_unstable_by(pivot, cmp);
14349 tagged[..k].sort_by(cmp);
14350 tagged.truncate(k);
14351 }
14352 _ => {
14353 tagged.sort_by(cmp);
14354 }
14355 }
14356}
14357
14358fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14359 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14360}
14361
14362fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14366 use core::cmp::Ordering;
14367 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14368 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14369 let ord = if descs.get(i).copied().unwrap_or(false) {
14370 ord.reverse()
14371 } else {
14372 ord
14373 };
14374 if ord != Ordering::Equal {
14375 return ord;
14376 }
14377 }
14378 Ordering::Equal
14379}
14380
14381fn build_order_keys(
14384 order_by: &[OrderBy],
14385 row: &Row,
14386 ctx: &EvalContext,
14387) -> Result<Vec<f64>, EngineError> {
14388 let mut keys = Vec::with_capacity(order_by.len());
14389 for o in order_by {
14390 let v = eval::eval_expr(&o.expr, row, ctx)?;
14391 keys.push(value_to_order_key(&v)?);
14392 }
14393 Ok(keys)
14394}
14395
14396fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14400 if let Some(off) = offset {
14401 let off = off as usize;
14402 if off >= rows.len() {
14403 rows.clear();
14404 } else {
14405 rows.drain(..off);
14406 }
14407 }
14408 if let Some(n) = limit {
14409 rows.truncate(n as usize);
14410 }
14411}
14412
14413fn apply_offset_and_limit_tagged(
14424 tagged: &mut Vec<(Vec<f64>, Row)>,
14425 offset: Option<u32>,
14426 limit: Option<u32>,
14427 with_ties: bool,
14428) {
14429 if let Some(off) = offset {
14430 let off = off as usize;
14431 if off >= tagged.len() {
14432 tagged.clear();
14433 } else {
14434 tagged.drain(..off);
14435 }
14436 }
14437 if let Some(n) = limit {
14438 let n = n as usize;
14439 if with_ties && n > 0 && n < tagged.len() {
14440 let cutoff_key = tagged[n - 1].0.clone();
14441 let mut end = n;
14442 while end < tagged.len() && tagged[end].0 == cutoff_key {
14443 end += 1;
14444 }
14445 tagged.truncate(end);
14446 } else {
14447 tagged.truncate(n);
14448 }
14449 }
14450}
14451
14452fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14458 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14459 return Err(EngineError::Unsupported(alloc::string::String::from(
14460 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14461 )));
14462 }
14463 Ok(())
14464}
14465
14466fn resolve_foreign_key(
14480 local_table_name: &str,
14481 local_cols: &[ColumnSchema],
14482 fk: spg_sql::ast::ForeignKeyConstraint,
14483 catalog: &Catalog,
14484) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14485 let mut local_columns = Vec::with_capacity(fk.columns.len());
14487 for name in &fk.columns {
14488 let pos = local_cols
14489 .iter()
14490 .position(|c| c.name == *name)
14491 .ok_or_else(|| {
14492 EngineError::Unsupported(alloc::format!(
14493 "FOREIGN KEY references unknown local column {name:?}"
14494 ))
14495 })?;
14496 local_columns.push(pos);
14497 }
14498 let is_self_ref = fk.parent_table == local_table_name;
14502 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14503 (local_cols, local_table_name)
14504 } else {
14505 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14506 EngineError::Storage(StorageError::TableNotFound {
14507 name: fk.parent_table.clone(),
14508 })
14509 })?;
14510 (
14511 parent_table.schema().columns.as_slice(),
14512 fk.parent_table.as_str(),
14513 )
14514 };
14515 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14520 if fk.columns.len() != 1 {
14521 return Err(EngineError::Unsupported(
14522 "composite FOREIGN KEY without explicit parent column list is not supported \
14523 — list the parent columns explicitly"
14524 .into(),
14525 ));
14526 }
14527 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14529 .ok_or_else(|| {
14530 EngineError::Unsupported(alloc::format!(
14531 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14532 to default the FOREIGN KEY against"
14533 ))
14534 })?;
14535 alloc::vec![pos]
14536 } else {
14537 let mut out = Vec::with_capacity(fk.parent_columns.len());
14538 for name in &fk.parent_columns {
14539 let pos = parent_cols_for_lookup
14540 .iter()
14541 .position(|c| c.name == *name)
14542 .ok_or_else(|| {
14543 EngineError::Unsupported(alloc::format!(
14544 "FOREIGN KEY references unknown parent column \
14545 {name:?} on table {parent_table_str:?}"
14546 ))
14547 })?;
14548 out.push(pos);
14549 }
14550 out
14551 };
14552 if parent_columns.len() != local_columns.len() {
14553 return Err(EngineError::Unsupported(alloc::format!(
14554 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14555 local_columns.len(),
14556 parent_columns.len()
14557 )));
14558 }
14559 if !is_self_ref {
14569 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14570 let primary_parent_col = parent_columns[0];
14571 let has_btree = parent_table
14572 .schema()
14573 .columns
14574 .get(primary_parent_col)
14575 .is_some()
14576 && parent_table.indices().iter().any(|idx| {
14577 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14578 && idx.column_position == primary_parent_col
14579 && idx.partial_predicate.is_none()
14580 });
14581 if !has_btree {
14582 return Err(EngineError::Unsupported(alloc::format!(
14583 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14584 index — create one with `CREATE INDEX ... ON {} ({})` first",
14585 parent_table_str,
14586 parent_table_str,
14587 parent_table.schema().columns[primary_parent_col].name,
14588 )));
14589 }
14590 }
14591 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14592 let on_update = fk_action_sql_to_storage(fk.on_update);
14593 Ok(spg_storage::ForeignKeyConstraint {
14594 name: fk.name,
14595 local_columns,
14596 parent_table: fk.parent_table,
14597 parent_columns,
14598 on_delete,
14599 on_update,
14600 })
14601}
14602
14603fn pick_pk_index_column(
14609 catalog: &Catalog,
14610 parent_name: &str,
14611 is_self_ref: bool,
14612 local_cols: &[ColumnSchema],
14613) -> Option<usize> {
14614 if is_self_ref {
14615 let _ = local_cols;
14619 return Some(0);
14620 }
14621 let parent = catalog.get(parent_name)?;
14622 parent.indices().iter().find_map(|idx| {
14623 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14624 && idx.partial_predicate.is_none()
14625 && idx.included_columns.is_empty()
14626 && idx.expression.is_none()
14627 {
14628 Some(idx.column_position)
14629 } else {
14630 None
14631 }
14632 })
14633}
14634
14635fn resolve_on_conflict_columns(
14642 catalog: &Catalog,
14643 table_name: &str,
14644 target: &[String],
14645) -> Result<Vec<usize>, EngineError> {
14646 let table = catalog.get(table_name).ok_or_else(|| {
14647 EngineError::Storage(StorageError::TableNotFound {
14648 name: table_name.into(),
14649 })
14650 })?;
14651 if target.is_empty() {
14652 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14662 return Ok(uc.columns.clone());
14663 }
14664 let pos = table
14665 .indices()
14666 .iter()
14667 .find_map(|idx| {
14668 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14669 && idx.partial_predicate.is_none()
14670 && idx.included_columns.is_empty()
14671 && idx.expression.is_none()
14672 {
14673 Some(idx.column_position)
14674 } else {
14675 None
14676 }
14677 })
14678 .ok_or_else(|| {
14679 EngineError::Unsupported(alloc::format!(
14680 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14681 ))
14682 })?;
14683 return Ok(alloc::vec![pos]);
14684 }
14685 let mut out = Vec::with_capacity(target.len());
14686 for name in target {
14687 let pos = table
14688 .schema()
14689 .columns
14690 .iter()
14691 .position(|c| c.name == *name)
14692 .ok_or_else(|| {
14693 EngineError::Unsupported(alloc::format!(
14694 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14695 ))
14696 })?;
14697 out.push(pos);
14698 }
14699 Ok(out)
14700}
14701
14702fn on_conflict_key_exists(
14705 catalog: &Catalog,
14706 table_name: &str,
14707 column_pos: usize,
14708 key: &Value,
14709) -> bool {
14710 let Some(table) = catalog.get(table_name) else {
14711 return false;
14712 };
14713 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14714 return false;
14715 };
14716 table.indices().iter().any(|idx| {
14717 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14718 && idx.column_position == column_pos
14719 && idx.partial_predicate.is_none()
14720 && !idx.lookup_eq(&idx_key).is_empty()
14721 })
14722}
14723
14724fn lookup_row_position_by_keys(
14730 catalog: &Catalog,
14731 table_name: &str,
14732 column_positions: &[usize],
14733 key: &[&Value],
14734) -> Option<usize> {
14735 let table = catalog.get(table_name)?;
14736 table.rows().iter().position(|r| {
14737 column_positions
14738 .iter()
14739 .enumerate()
14740 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14741 })
14742}
14743
14744fn on_conflict_keys_exist(
14749 catalog: &Catalog,
14750 table_name: &str,
14751 column_positions: &[usize],
14752 key: &[&Value],
14753) -> bool {
14754 if column_positions.len() == 1 {
14755 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14756 }
14757 let Some(table) = catalog.get(table_name) else {
14758 return false;
14759 };
14760 table.rows().iter().any(|r| {
14761 column_positions
14762 .iter()
14763 .enumerate()
14764 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14765 })
14766}
14767
14768fn apply_on_conflict_assignments(
14781 catalog: &Catalog,
14782 table_name: &str,
14783 target_pos: usize,
14784 incoming: &[Value],
14785 assignments: &[(String, Expr)],
14786 where_: Option<&Expr>,
14787) -> Result<Option<Vec<Value>>, EngineError> {
14788 let table = catalog.get(table_name).ok_or_else(|| {
14789 EngineError::Storage(StorageError::TableNotFound {
14790 name: table_name.into(),
14791 })
14792 })?;
14793 let schema_cols = table.schema().columns.clone();
14794 let existing = table
14795 .rows()
14796 .get(target_pos)
14797 .ok_or_else(|| {
14798 EngineError::Unsupported(alloc::format!(
14799 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14800 ))
14801 })?
14802 .clone();
14803 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14804 if let Some(w) = where_ {
14806 let pred = w.clone();
14807 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14808 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14809 if !matches!(v, Value::Bool(true)) {
14810 return Ok(None);
14811 }
14812 }
14813 let mut new_values = existing.values.clone();
14814 for (col_name, expr) in assignments {
14815 let target_idx = schema_cols
14816 .iter()
14817 .position(|c| c.name == *col_name)
14818 .ok_or_else(|| {
14819 EngineError::Eval(EvalError::ColumnNotFound {
14820 name: col_name.clone(),
14821 })
14822 })?;
14823 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14824 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14825 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14826 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14827 new_values[target_idx] = coerced;
14828 }
14829 Ok(Some(new_values))
14830}
14831
14832fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14837 use spg_sql::ast::ColumnName;
14838 match expr {
14839 Expr::Column(ColumnName { qualifier, name })
14840 if qualifier
14841 .as_deref()
14842 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14843 {
14844 let pos = schema_cols.iter().position(|c| c.name == name);
14845 match pos {
14846 Some(p) => {
14847 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14848 value_to_literal_expr(v)
14849 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14850 }
14851 None => Expr::Column(ColumnName { qualifier, name }),
14852 }
14853 }
14854 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14855 op,
14856 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14857 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14858 },
14859 Expr::Unary { op, expr } => Expr::Unary {
14860 op,
14861 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14862 },
14863 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14864 name,
14865 args: args
14866 .into_iter()
14867 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14868 .collect(),
14869 },
14870 other => other,
14871 }
14872}
14873
14874fn enforce_uniqueness_inserts(
14897 catalog: &Catalog,
14898 child_table: &str,
14899 constraints: &[spg_storage::UniquenessConstraint],
14900 rows: &[Vec<Value>],
14901) -> Result<(), EngineError> {
14902 if constraints.is_empty() {
14903 return Ok(());
14904 }
14905 let table = catalog.get(child_table).ok_or_else(|| {
14906 EngineError::Storage(StorageError::TableNotFound {
14907 name: child_table.into(),
14908 })
14909 })?;
14910 let schema = table.schema();
14911 for uc in constraints {
14912 for (batch_idx, row_values) in rows.iter().enumerate() {
14913 let key: Vec<Value> = uc
14922 .columns
14923 .iter()
14924 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14925 .collect();
14926 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14927 if has_null && !uc.nulls_not_distinct {
14932 continue;
14933 }
14934 let collides_in_table = table.rows().iter().any(|prow| {
14936 uc.columns.iter().enumerate().all(|(i, &p)| {
14937 prow.values
14938 .get(p)
14939 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14940 })
14941 });
14942 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14944 uc.columns.iter().enumerate().all(|(i, &p)| {
14945 earlier
14946 .get(p)
14947 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14948 })
14949 });
14950 if collides_in_table || collides_in_batch {
14951 let kind = if uc.is_primary_key {
14952 "PRIMARY KEY"
14953 } else {
14954 "UNIQUE"
14955 };
14956 let col_names: Vec<String> = uc
14957 .columns
14958 .iter()
14959 .map(|&i| table.schema().columns[i].name.clone())
14960 .collect();
14961 return Err(EngineError::Unsupported(alloc::format!(
14962 "{kind} violation on {child_table:?} columns {col_names:?}: \
14963 row #{batch_idx} duplicates an existing key"
14964 )));
14965 }
14966 }
14967 }
14968 Ok(())
14969}
14970
14971fn collated_key_cell(
14978 v: &spg_storage::Value,
14979 column_position: usize,
14980 schema: &spg_storage::TableSchema,
14981) -> spg_storage::Value {
14982 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14983 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14984 spg_storage::Value::Text(s.to_ascii_lowercase())
14985 }
14986 _ => v.clone(),
14987 }
14988}
14989
14990fn predicate_truthy(v: &spg_storage::Value) -> bool {
14998 use spg_storage::Value as V;
14999 match v {
15000 V::Bool(b) => *b,
15001 V::Int(n) => *n != 0,
15002 V::BigInt(n) => *n != 0,
15003 V::SmallInt(n) => *n != 0,
15004 _ => false,
15005 }
15006}
15007
15008fn check_existing_unique_violation(
15013 idx: &spg_storage::Index,
15014 schema: &spg_storage::TableSchema,
15015 rows: &[spg_storage::Row],
15016) -> Result<(), EngineError> {
15017 let predicate_expr = match idx.partial_predicate.as_deref() {
15018 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15019 EngineError::Unsupported(alloc::format!(
15020 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15021 ))
15022 })?),
15023 None => None,
15024 };
15025 let ctx = eval::EvalContext::new(&schema.columns, None);
15026 let key_positions = unique_key_positions(idx);
15027 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15028 for row in rows {
15029 if let Some(expr) = &predicate_expr {
15030 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15031 EngineError::Unsupported(alloc::format!(
15032 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15033 ))
15034 })?;
15035 if !predicate_truthy(&v) {
15036 continue;
15037 }
15038 }
15039 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15040 .iter()
15041 .map(|&p| {
15042 let v = row
15043 .values
15044 .get(p)
15045 .cloned()
15046 .unwrap_or(spg_storage::Value::Null);
15047 collated_key_cell(&v, p, schema)
15048 })
15049 .collect();
15050 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15051 continue;
15052 }
15053 if seen.iter().any(|other| *other == key) {
15054 return Err(EngineError::Unsupported(alloc::format!(
15055 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15056 idx.name
15057 )));
15058 }
15059 seen.push(key);
15060 }
15061 Ok(())
15062}
15063
15064fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15068 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15069 out.push(idx.column_position);
15070 out.extend_from_slice(&idx.extra_column_positions);
15071 out
15072}
15073
15074fn enforce_unique_index_inserts(
15082 catalog: &Catalog,
15083 table_name: &str,
15084 rows: &[alloc::vec::Vec<spg_storage::Value>],
15085) -> Result<(), EngineError> {
15086 let table = catalog.get(table_name).ok_or_else(|| {
15087 EngineError::Storage(StorageError::TableNotFound {
15088 name: table_name.into(),
15089 })
15090 })?;
15091 let schema = table.schema();
15092 let ctx = eval::EvalContext::new(&schema.columns, None);
15093 for idx in table.indices() {
15094 if !idx.is_unique {
15095 continue;
15096 }
15097 let predicate_expr = match idx.partial_predicate.as_deref() {
15099 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15100 EngineError::Unsupported(alloc::format!(
15101 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15102 idx.name
15103 ))
15104 })?),
15105 None => None,
15106 };
15107 let key_positions = unique_key_positions(idx);
15108 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15109 key_positions
15113 .iter()
15114 .map(|&p| {
15115 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15116 collated_key_cell(&v, p, schema)
15117 })
15118 .collect()
15119 };
15120 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15124 let Some(expr) = &predicate_expr else {
15125 return Ok(true);
15126 };
15127 let tmp_row = spg_storage::Row {
15128 values: values.to_vec(),
15129 };
15130 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15131 EngineError::Unsupported(alloc::format!(
15132 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15133 idx.name
15134 ))
15135 })?;
15136 Ok(predicate_truthy(&v))
15137 };
15138 for (batch_idx, row_values) in rows.iter().enumerate() {
15139 if !participates(row_values)? {
15140 continue;
15141 }
15142 let key = key_of(row_values);
15143 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15144 continue;
15145 }
15146 for prow in table.rows() {
15148 if !participates(&prow.values)? {
15149 continue;
15150 }
15151 if key_of(&prow.values) == key {
15152 return Err(EngineError::Unsupported(alloc::format!(
15153 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15154 row #{batch_idx} duplicates an existing key",
15155 idx.name
15156 )));
15157 }
15158 }
15159 for earlier in &rows[..batch_idx] {
15161 if !participates(earlier)? {
15162 continue;
15163 }
15164 if key_of(earlier) == key {
15165 return Err(EngineError::Unsupported(alloc::format!(
15166 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15167 row #{batch_idx} duplicates an earlier row in the same batch",
15168 idx.name
15169 )));
15170 }
15171 }
15172 }
15173 }
15174 Ok(())
15175}
15176
15177fn any_column_changed(
15185 filter_cols: &[String],
15186 schema_cols: &[ColumnSchema],
15187 old_row: &Row,
15188 new_row: &Row,
15189) -> bool {
15190 for col_name in filter_cols {
15191 let Some(pos) = schema_cols
15192 .iter()
15193 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15194 else {
15195 continue;
15196 };
15197 let old_v = old_row.values.get(pos);
15198 let new_v = new_row.values.get(pos);
15199 if old_v != new_v {
15200 return true;
15201 }
15202 }
15203 false
15204}
15205
15206fn enforce_check_constraints(
15211 catalog: &Catalog,
15212 table_name: &str,
15213 rows: &[alloc::vec::Vec<spg_storage::Value>],
15214) -> Result<(), EngineError> {
15215 let table = catalog.get(table_name).ok_or_else(|| {
15216 EngineError::Storage(StorageError::TableNotFound {
15217 name: table_name.into(),
15218 })
15219 })?;
15220 let schema = table.schema();
15221 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15225 alloc::vec::Vec::new();
15226 for (idx, col) in schema.columns.iter().enumerate() {
15227 let Some(dname) = &col.user_domain_type else {
15228 continue;
15229 };
15230 let Some(dom) = catalog.domain_types().get(dname) else {
15231 continue;
15232 };
15233 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15234 alloc::vec::Vec::with_capacity(dom.checks.len());
15235 for src in &dom.checks {
15236 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15237 EngineError::Unsupported(alloc::format!(
15238 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15239 col.name
15240 ))
15241 })?;
15242 parsed_for_col.push(expr);
15243 }
15244 if !parsed_for_col.is_empty() {
15245 domain_checks_per_col.push((idx, parsed_for_col));
15246 }
15247 }
15248 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15249 return Ok(());
15250 }
15251 let ctx = eval::EvalContext::new(&schema.columns, None);
15252 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15253 for (i, src) in schema.checks.iter().enumerate() {
15254 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15255 EngineError::Unsupported(alloc::format!(
15256 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15257 ))
15258 })?;
15259 parsed.push((i, expr));
15260 }
15261 for (batch_idx, row_values) in rows.iter().enumerate() {
15262 let tmp_row = spg_storage::Row {
15263 values: row_values.clone(),
15264 };
15265 for (i, expr) in &parsed {
15266 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15267 EngineError::Unsupported(alloc::format!(
15268 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15269 ))
15270 })?;
15271 if matches!(v, spg_storage::Value::Bool(false)) {
15273 return Err(EngineError::Unsupported(alloc::format!(
15274 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15275 schema.checks[*i]
15276 )));
15277 }
15278 }
15279 for (col_idx, checks) in &domain_checks_per_col {
15285 let cell = row_values
15286 .get(*col_idx)
15287 .cloned()
15288 .unwrap_or(spg_storage::Value::Null);
15289 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15290 "value",
15291 schema.columns[*col_idx].ty,
15292 schema.columns[*col_idx].nullable,
15293 )];
15294 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15295 let synth_row = spg_storage::Row {
15296 values: alloc::vec![cell],
15297 };
15298 for (ci, expr) in checks.iter().enumerate() {
15299 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15300 EngineError::Unsupported(alloc::format!(
15301 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15302 schema.columns[*col_idx].name
15303 ))
15304 })?;
15305 if matches!(v, spg_storage::Value::Bool(false)) {
15306 return Err(EngineError::Unsupported(alloc::format!(
15307 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15308 schema.columns[*col_idx].name
15309 )));
15310 }
15311 }
15312 }
15313 }
15314 Ok(())
15315}
15316
15317fn enforce_fk_inserts(
15318 catalog: &Catalog,
15319 child_table: &str,
15320 fks: &[spg_storage::ForeignKeyConstraint],
15321 rows: &[Vec<Value>],
15322) -> Result<(), EngineError> {
15323 for fk in fks {
15324 let parent_is_self = fk.parent_table == child_table;
15325 let parent = if parent_is_self {
15326 catalog.get(child_table).ok_or_else(|| {
15329 EngineError::Storage(StorageError::TableNotFound {
15330 name: child_table.into(),
15331 })
15332 })?
15333 } else {
15334 catalog.get(&fk.parent_table).ok_or_else(|| {
15335 EngineError::Storage(StorageError::TableNotFound {
15336 name: fk.parent_table.clone(),
15337 })
15338 })?
15339 };
15340 for (batch_idx, row_values) in rows.iter().enumerate() {
15341 if fk.local_columns.len() == 1 {
15345 let v = &row_values[fk.local_columns[0]];
15346 if matches!(v, Value::Null) {
15347 continue;
15348 }
15349 let parent_col = fk.parent_columns[0];
15350 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15351 EngineError::Unsupported(alloc::format!(
15352 "FOREIGN KEY column value of type {:?} is not index-eligible",
15353 v.data_type()
15354 ))
15355 })?;
15356 let present_committed = parent.indices().iter().any(|idx| {
15357 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15358 && idx.column_position == parent_col
15359 && idx.partial_predicate.is_none()
15360 && !idx.lookup_eq(&key).is_empty()
15361 });
15362 let present_in_batch = parent_is_self
15366 && rows[..batch_idx]
15367 .iter()
15368 .any(|earlier| earlier.get(parent_col) == Some(v));
15369 if !(present_committed || present_in_batch) {
15370 return Err(EngineError::Unsupported(alloc::format!(
15371 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15372 fk.parent_table,
15373 parent
15374 .schema()
15375 .columns
15376 .get(parent_col)
15377 .map_or("?", |c| c.name.as_str()),
15378 v,
15379 )));
15380 }
15381 } else {
15382 if fk
15386 .local_columns
15387 .iter()
15388 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15389 {
15390 continue;
15391 }
15392 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15393 let parent_match_committed = parent.rows().iter().any(|prow| {
15394 fk.parent_columns
15395 .iter()
15396 .enumerate()
15397 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15398 });
15399 let parent_match_in_batch = parent_is_self
15400 && rows[..batch_idx].iter().any(|earlier| {
15401 fk.parent_columns
15402 .iter()
15403 .enumerate()
15404 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15405 });
15406 if !(parent_match_committed || parent_match_in_batch) {
15407 return Err(EngineError::Unsupported(alloc::format!(
15408 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15409 fk.parent_table,
15410 )));
15411 }
15412 }
15413 }
15414 }
15415 Ok(())
15416}
15417
15418#[derive(Debug, Clone)]
15422struct FkChildStep {
15423 child_table: String,
15424 action: FkChildAction,
15425}
15426
15427#[derive(Debug, Clone)]
15428enum FkChildAction {
15429 Delete { positions: Vec<usize> },
15431 SetNull {
15435 positions: Vec<usize>,
15436 columns: Vec<usize>,
15437 },
15438 SetDefault {
15442 positions: Vec<usize>,
15443 columns: Vec<usize>,
15444 defaults: Vec<Value>,
15445 },
15446}
15447
15448fn plan_fk_parent_deletions(
15464 catalog: &Catalog,
15465 parent_table_name: &str,
15466 to_delete_positions: &[usize],
15467 to_delete_rows: &[Vec<Value>],
15468) -> Result<Vec<FkChildStep>, EngineError> {
15469 use alloc::collections::{BTreeMap, BTreeSet};
15470 if to_delete_rows.is_empty() {
15471 return Ok(Vec::new());
15472 }
15473 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15474 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15476 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15477 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15478 for &p in to_delete_positions {
15479 visited.insert((parent_table_name.to_string(), p));
15480 }
15481 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15482 .iter()
15483 .map(|r| (parent_table_name.to_string(), r.clone()))
15484 .collect();
15485 while let Some((cur_parent, parent_row)) = work.pop() {
15486 for child_name in catalog.table_names() {
15487 let child = catalog
15488 .get(&child_name)
15489 .expect("table_names → catalog.get round-trip is total");
15490 for fk in &child.schema().foreign_keys {
15491 if fk.parent_table != cur_parent {
15492 continue;
15493 }
15494 let parent_key: Vec<&Value> = fk
15495 .parent_columns
15496 .iter()
15497 .map(|&pi| &parent_row[pi])
15498 .collect();
15499 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15500 continue;
15501 }
15502 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15503 if child_name == cur_parent
15504 && visited.contains(&(child_name.clone(), child_row_idx))
15505 {
15506 continue;
15507 }
15508 let matches_key = fk
15509 .local_columns
15510 .iter()
15511 .enumerate()
15512 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15513 if !matches_key {
15514 continue;
15515 }
15516 match fk.on_delete {
15517 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15518 return Err(EngineError::Unsupported(alloc::format!(
15519 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15520 restricted by FK from {child_name:?}.{:?}",
15521 fk.local_columns,
15522 )));
15523 }
15524 spg_storage::FkAction::Cascade => {
15525 if visited.insert((child_name.clone(), child_row_idx)) {
15526 delete_plan
15527 .entry(child_name.clone())
15528 .or_default()
15529 .insert(child_row_idx);
15530 work.push((child_name.clone(), child_row.values.clone()));
15531 }
15532 }
15533 spg_storage::FkAction::SetNull => {
15534 for &li in &fk.local_columns {
15536 let col = child.schema().columns.get(li).ok_or_else(|| {
15537 EngineError::Unsupported(alloc::format!(
15538 "FK local column {li} missing in {child_name:?}"
15539 ))
15540 })?;
15541 if !col.nullable {
15542 return Err(EngineError::Unsupported(alloc::format!(
15543 "FOREIGN KEY ON DELETE SET NULL: column \
15544 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15545 col.name,
15546 )));
15547 }
15548 }
15549 let entry = setnull_plan.entry(child_name.clone()).or_default();
15550 for &li in &fk.local_columns {
15551 entry.insert((child_row_idx, li));
15552 }
15553 }
15554 spg_storage::FkAction::SetDefault => {
15555 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15557 for &li in &fk.local_columns {
15558 let col = child.schema().columns.get(li).ok_or_else(|| {
15559 EngineError::Unsupported(alloc::format!(
15560 "FK local column {li} missing in {child_name:?}"
15561 ))
15562 })?;
15563 let default = col.default.clone().ok_or_else(|| {
15564 EngineError::Unsupported(alloc::format!(
15565 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15566 {child_name:?}.{:?} has no DEFAULT declared",
15567 col.name,
15568 ))
15569 })?;
15570 entry.insert((child_row_idx, li), default);
15571 }
15572 }
15573 }
15574 }
15575 }
15576 }
15577 }
15578 let mut steps: Vec<FkChildStep> = Vec::new();
15586 for (child_table, entries) in setnull_plan {
15587 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15588 steps.push(FkChildStep {
15589 child_table,
15590 action: FkChildAction::SetNull { positions, columns },
15591 });
15592 }
15593 for (child_table, entries) in setdefault_plan {
15594 let mut positions = Vec::with_capacity(entries.len());
15595 let mut columns = Vec::with_capacity(entries.len());
15596 let mut defaults = Vec::with_capacity(entries.len());
15597 for ((p, c), v) in entries {
15598 positions.push(p);
15599 columns.push(c);
15600 defaults.push(v);
15601 }
15602 steps.push(FkChildStep {
15603 child_table,
15604 action: FkChildAction::SetDefault {
15605 positions,
15606 columns,
15607 defaults,
15608 },
15609 });
15610 }
15611 for (child_table, positions) in delete_plan {
15612 steps.push(FkChildStep {
15613 child_table,
15614 action: FkChildAction::Delete {
15615 positions: positions.into_iter().collect(),
15616 },
15617 });
15618 }
15619 Ok(steps)
15620}
15621
15622fn plan_fk_parent_updates(
15639 catalog: &Catalog,
15640 parent_table_name: &str,
15641 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15642) -> Result<Vec<FkChildStep>, EngineError> {
15643 use alloc::collections::BTreeMap;
15644 if plan_with_old.is_empty() {
15645 return Ok(Vec::new());
15646 }
15647 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15652 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15653 BTreeMap::new();
15654 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15655 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15657
15658 for child_name in catalog.table_names() {
15659 let child = catalog
15660 .get(&child_name)
15661 .expect("table_names → catalog.get total");
15662 for fk in &child.schema().foreign_keys {
15663 if fk.parent_table != parent_table_name {
15664 continue;
15665 }
15666 for (_pos, old_row, new_row) in plan_with_old {
15667 let key_changed = fk
15669 .parent_columns
15670 .iter()
15671 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15672 if !key_changed {
15673 continue;
15674 }
15675 let old_key: Vec<&Value> =
15677 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15678 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15679 continue;
15681 }
15682 let new_key: Vec<&Value> =
15683 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15684 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15685 if child_name == parent_table_name
15688 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15689 {
15690 continue;
15691 }
15692 let matches_key = fk
15693 .local_columns
15694 .iter()
15695 .enumerate()
15696 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15697 if !matches_key {
15698 continue;
15699 }
15700 match fk.on_update {
15701 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15702 return Err(EngineError::Unsupported(alloc::format!(
15703 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15704 restricted by FK from {child_name:?}.{:?}",
15705 fk.local_columns,
15706 )));
15707 }
15708 spg_storage::FkAction::Cascade => {
15709 let entry = cascade_plan.entry(child_name.clone()).or_default();
15711 for (i, &li) in fk.local_columns.iter().enumerate() {
15712 entry.insert((child_row_idx, li), new_key[i].clone());
15713 }
15714 }
15715 spg_storage::FkAction::SetNull => {
15716 for &li in &fk.local_columns {
15717 let col = child.schema().columns.get(li).ok_or_else(|| {
15718 EngineError::Unsupported(alloc::format!(
15719 "FK local column {li} missing in {child_name:?}"
15720 ))
15721 })?;
15722 if !col.nullable {
15723 return Err(EngineError::Unsupported(alloc::format!(
15724 "FOREIGN KEY ON UPDATE SET NULL: column \
15725 {child_name:?}.{:?} is NOT NULL",
15726 col.name,
15727 )));
15728 }
15729 }
15730 let entry = setnull_plan.entry(child_name.clone()).or_default();
15731 for &li in &fk.local_columns {
15732 entry.insert((child_row_idx, li));
15733 }
15734 }
15735 spg_storage::FkAction::SetDefault => {
15736 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15737 for &li in &fk.local_columns {
15738 let col = child.schema().columns.get(li).ok_or_else(|| {
15739 EngineError::Unsupported(alloc::format!(
15740 "FK local column {li} missing in {child_name:?}"
15741 ))
15742 })?;
15743 let default = col.default.clone().ok_or_else(|| {
15744 EngineError::Unsupported(alloc::format!(
15745 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15746 {child_name:?}.{:?} has no DEFAULT",
15747 col.name,
15748 ))
15749 })?;
15750 entry.insert((child_row_idx, li), default);
15751 }
15752 }
15753 }
15754 }
15755 }
15756 }
15757 }
15758 let mut steps: Vec<FkChildStep> = Vec::new();
15761 for (child_table, entries) in cascade_plan {
15762 let mut positions = Vec::with_capacity(entries.len());
15763 let mut columns = Vec::with_capacity(entries.len());
15764 let mut defaults = Vec::with_capacity(entries.len());
15765 for ((p, c), v) in entries {
15766 positions.push(p);
15767 columns.push(c);
15768 defaults.push(v);
15769 }
15770 steps.push(FkChildStep {
15775 child_table,
15776 action: FkChildAction::SetDefault {
15777 positions,
15778 columns,
15779 defaults,
15780 },
15781 });
15782 }
15783 for (child_table, entries) in setnull_plan {
15784 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15785 steps.push(FkChildStep {
15786 child_table,
15787 action: FkChildAction::SetNull { positions, columns },
15788 });
15789 }
15790 for (child_table, entries) in setdefault_plan {
15791 let mut positions = Vec::with_capacity(entries.len());
15792 let mut columns = Vec::with_capacity(entries.len());
15793 let mut defaults = Vec::with_capacity(entries.len());
15794 for ((p, c), v) in entries {
15795 positions.push(p);
15796 columns.push(c);
15797 defaults.push(v);
15798 }
15799 steps.push(FkChildStep {
15800 child_table,
15801 action: FkChildAction::SetDefault {
15802 positions,
15803 columns,
15804 defaults,
15805 },
15806 });
15807 }
15808 let _ = delete_plan; Ok(steps)
15810}
15811
15812fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15816 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15817 EngineError::Storage(StorageError::TableNotFound {
15818 name: step.child_table.clone(),
15819 })
15820 })?;
15821 match &step.action {
15822 FkChildAction::Delete { positions } => {
15823 let _ = child.delete_rows(positions);
15824 }
15825 FkChildAction::SetNull { positions, columns } => {
15826 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15827 }
15828 FkChildAction::SetDefault {
15829 positions,
15830 columns,
15831 defaults,
15832 } => {
15833 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15834 }
15835 }
15836 Ok(())
15837}
15838
15839fn apply_per_cell_writes(
15845 child: &mut spg_storage::Table,
15846 positions: &[usize],
15847 columns: &[usize],
15848 mut value_for: impl FnMut(usize) -> Value,
15849) -> Result<(), EngineError> {
15850 use alloc::collections::BTreeMap;
15851 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15852 for i in 0..positions.len() {
15853 by_row
15854 .entry(positions[i])
15855 .or_default()
15856 .push((columns[i], value_for(i)));
15857 }
15858 for (pos, mutations) in by_row {
15859 let mut new_values = child.rows()[pos].values.clone();
15860 for (col, v) in mutations {
15861 if let Some(slot) = new_values.get_mut(col) {
15862 *slot = v;
15863 }
15864 }
15865 child
15866 .update_row(pos, new_values)
15867 .map_err(EngineError::Storage)?;
15868 }
15869 Ok(())
15870}
15871
15872fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15873 match a {
15874 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15875 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15876 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15877 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15878 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15879 }
15880}
15881
15882fn resolve_column_default_free(
15888 col: &ColumnSchema,
15889 clock_fn: Option<ClockFn>,
15890) -> Result<Value, EngineError> {
15891 if let Some(rt) = &col.runtime_default {
15892 return eval_runtime_default_free(rt, col.ty, clock_fn);
15893 }
15894 Ok(col.default.clone().unwrap_or(Value::Null))
15895}
15896
15897fn eval_runtime_default_free(
15898 rt: &str,
15899 ty: DataType,
15900 clock_fn: Option<ClockFn>,
15901) -> Result<Value, EngineError> {
15902 let s = rt.trim().to_ascii_lowercase();
15903 let with_no_parens = s.trim_end_matches("()");
15909 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15910 if with_no_parens.ends_with(')') {
15911 &with_no_parens[..open_idx]
15912 } else {
15913 with_no_parens
15914 }
15915 } else {
15916 with_no_parens
15917 };
15918 let now_us = match clock_fn {
15919 Some(f) => f(),
15920 None => 0,
15921 };
15922 let v = match canonical {
15923 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15924 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15925 "current_time" | "localtime" => Value::Timestamp(now_us),
15926 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15932 other => {
15933 return Err(EngineError::Unsupported(alloc::format!(
15934 "runtime DEFAULT expression {other:?} not supported \
15935 (v7.17.0 whitelist: now() / current_timestamp / \
15936 current_date / current_time / localtimestamp / \
15937 localtime / gen_random_uuid() / \
15938 uuid_generate_v4())"
15939 )));
15940 }
15941 };
15942 coerce_value(v, ty, "DEFAULT", 0)
15943}
15944
15945fn is_runtime_default_expr(expr: &Expr) -> bool {
15951 match expr {
15952 Expr::FunctionCall { .. } => true,
15953 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15954 _ => false,
15955 }
15956}
15957
15958fn canonicalize_set_value(
15971 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15972 col_idx: usize,
15973 col_name: &str,
15974 value: Value,
15975) -> Result<Value, EngineError> {
15976 let Some(variants) = lookup.get(&col_idx) else {
15977 return Ok(value);
15978 };
15979 match value {
15980 Value::Null => Ok(Value::Null),
15981 Value::Text(s) => {
15982 if s.is_empty() {
15983 return Ok(Value::Text(alloc::string::String::new()));
15984 }
15985 let mut present = alloc::vec![false; variants.len()];
15988 for raw in s.split(',') {
15989 let tok = raw.trim();
15990 if tok.is_empty() {
15991 continue;
15992 }
15993 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15994 EngineError::Unsupported(alloc::format!(
15995 "column {col_name:?}: invalid SET token {tok:?}; \
15996 allowed: {variants:?}"
15997 ))
15998 })?;
15999 present[idx] = true;
16000 }
16001 let mut out = alloc::string::String::new();
16003 let mut first = true;
16004 for (i, keep) in present.iter().enumerate() {
16005 if !keep {
16006 continue;
16007 }
16008 if !first {
16009 out.push(',');
16010 }
16011 first = false;
16012 out.push_str(&variants[i]);
16013 }
16014 Ok(Value::Text(out))
16015 }
16016 other => Err(EngineError::Unsupported(alloc::format!(
16017 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16018 other.data_type()
16019 ))),
16020 }
16021}
16022
16023fn enforce_enum_label(
16024 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16025 col_idx: usize,
16026 col_name: &str,
16027 value: &Value,
16028) -> Result<(), EngineError> {
16029 if let Some(labels) = lookup.get(&col_idx) {
16030 match value {
16031 Value::Null => Ok(()),
16032 Value::Text(s) => {
16033 if labels.iter().any(|l| l == s) {
16034 Ok(())
16035 } else {
16036 Err(EngineError::Unsupported(alloc::format!(
16037 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16038 )))
16039 }
16040 }
16041 other => Err(EngineError::Unsupported(alloc::format!(
16042 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16043 other.data_type()
16044 ))),
16045 }
16046 } else {
16047 Ok(())
16048 }
16049}
16050
16051fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16052 let ty = column_type_to_data_type(c.ty);
16053 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16054 if let Some(name) = c.user_type_ref {
16061 schema.user_enum_type = Some(name);
16062 }
16063 if let Some(expr) = c.on_update_runtime {
16066 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16067 }
16068 schema.collation = match c.collation {
16072 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16073 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16074 };
16075 schema.is_unsigned = c.is_unsigned;
16078 schema.inline_enum_variants = c.inline_enum_variants;
16082 schema.inline_set_variants = c.inline_set_variants;
16086 if let Some(default_expr) = c.default {
16087 if is_runtime_default_expr(&default_expr) {
16093 let display = alloc::format!("{default_expr}");
16094 schema = schema.with_runtime_default(display);
16095 } else {
16096 let raw = literal_expr_to_value(default_expr)?;
16097 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16098 schema = schema.with_default(coerced);
16099 }
16100 }
16101 if c.auto_increment {
16102 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16104 return Err(EngineError::Unsupported(alloc::format!(
16105 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16106 )));
16107 }
16108 schema = schema.with_auto_increment();
16109 }
16110 Ok(schema)
16111}
16112
16113fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16118 let s = s.trim();
16119 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16120 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16122 if cleaned.len() % 2 != 0 {
16123 return Err("odd-length hex literal");
16124 }
16125 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16126 let cleaned_bytes = cleaned.as_bytes();
16127 for i in (0..cleaned_bytes.len()).step_by(2) {
16128 let hi = hex_nibble(cleaned_bytes[i])?;
16129 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16130 out.push((hi << 4) | lo);
16131 }
16132 return Ok(out);
16133 }
16134 let bytes = s.as_bytes();
16137 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16138 let mut i = 0;
16139 while i < bytes.len() {
16140 let b = bytes[i];
16141 if b == b'\\' && i + 1 < bytes.len() {
16142 let n = bytes[i + 1];
16143 if n == b'\\' {
16144 out.push(b'\\');
16145 i += 2;
16146 continue;
16147 }
16148 if n.is_ascii_digit()
16149 && i + 3 < bytes.len()
16150 && bytes[i + 2].is_ascii_digit()
16151 && bytes[i + 3].is_ascii_digit()
16152 {
16153 let oct = |x: u8| (x - b'0') as u32;
16154 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16155 if v <= 0xFF {
16156 out.push(v as u8);
16157 i += 4;
16158 continue;
16159 }
16160 }
16161 }
16162 out.push(b);
16163 i += 1;
16164 }
16165 Ok(out)
16166}
16167
16168fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16169 match b {
16170 b'0'..=b'9' => Ok(b - b'0'),
16171 b'a'..=b'f' => Ok(b - b'a' + 10),
16172 b'A'..=b'F' => Ok(b - b'A' + 10),
16173 _ => Err("invalid hex digit"),
16174 }
16175}
16176
16177fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16191 let mut has_text = false;
16192 let mut has_bigint = false;
16193 let mut has_int = false;
16194 for v in &items {
16195 match v {
16196 Value::Null => {}
16197 Value::Text(_) | Value::Json(_) => has_text = true,
16198 Value::BigInt(_) => has_bigint = true,
16199 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16200 _ => has_text = true,
16201 }
16202 }
16203 if has_text || (!has_bigint && !has_int) {
16204 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16205 .into_iter()
16206 .map(|v| match v {
16207 Value::Null => None,
16208 Value::Text(s) | Value::Json(s) => Some(s),
16209 other => Some(alloc::format!("{other:?}")),
16210 })
16211 .collect();
16212 return Value::TextArray(out);
16213 }
16214 if has_bigint {
16215 let out: alloc::vec::Vec<Option<i64>> = items
16216 .into_iter()
16217 .map(|v| match v {
16218 Value::Null => None,
16219 Value::Int(n) => Some(i64::from(n)),
16220 Value::SmallInt(n) => Some(i64::from(n)),
16221 Value::BigInt(n) => Some(n),
16222 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16223 })
16224 .collect();
16225 return Value::BigIntArray(out);
16226 }
16227 let out: alloc::vec::Vec<Option<i32>> = items
16228 .into_iter()
16229 .map(|v| match v {
16230 Value::Null => None,
16231 Value::Int(n) => Some(n),
16232 Value::SmallInt(n) => Some(i32::from(n)),
16233 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16234 })
16235 .collect();
16236 Value::IntArray(out)
16237}
16238
16239fn decode_text_array_literal(
16240 s: &str,
16241) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16242 let trimmed = s.trim();
16243 let inner = trimmed
16244 .strip_prefix('{')
16245 .and_then(|x| x.strip_suffix('}'))
16246 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16247 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16248 if inner.trim().is_empty() {
16249 return Ok(out);
16250 }
16251 let bytes = inner.as_bytes();
16252 let mut i = 0;
16253 while i <= bytes.len() {
16254 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16256 i += 1;
16257 }
16258 if i < bytes.len() && bytes[i] == b'"' {
16260 i += 1; let mut buf = alloc::string::String::new();
16262 while i < bytes.len() && bytes[i] != b'"' {
16263 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16264 buf.push(bytes[i + 1] as char);
16265 i += 2;
16266 } else {
16267 buf.push(bytes[i] as char);
16268 i += 1;
16269 }
16270 }
16271 if i >= bytes.len() {
16272 return Err("unterminated quoted element");
16273 }
16274 i += 1; out.push(Some(buf));
16276 } else {
16277 let start = i;
16279 while i < bytes.len() && bytes[i] != b',' {
16280 i += 1;
16281 }
16282 let raw = inner[start..i].trim();
16283 if raw.eq_ignore_ascii_case("NULL") {
16284 out.push(None);
16285 } else {
16286 out.push(Some(alloc::string::ToString::to_string(raw)));
16287 }
16288 }
16289 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16291 i += 1;
16292 }
16293 if i >= bytes.len() {
16294 break;
16295 }
16296 if bytes[i] != b',' {
16297 return Err("expected ',' between TEXT[] elements");
16298 }
16299 i += 1;
16300 }
16301 Ok(out)
16302}
16303
16304fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16309 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16310 out.push('{');
16311 for (i, item) in items.iter().enumerate() {
16312 if i > 0 {
16313 out.push(',');
16314 }
16315 match item {
16316 None => out.push_str("NULL"),
16317 Some(s) => {
16318 let needs_quote = s.is_empty()
16319 || s.eq_ignore_ascii_case("NULL")
16320 || s.chars()
16321 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16322 if needs_quote {
16323 out.push('"');
16324 for c in s.chars() {
16325 if c == '"' || c == '\\' {
16326 out.push('\\');
16327 }
16328 out.push(c);
16329 }
16330 out.push('"');
16331 } else {
16332 out.push_str(s);
16333 }
16334 }
16335 }
16336 }
16337 out.push('}');
16338 out
16339}
16340
16341fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16345 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16346 out.push_str("\\x");
16347 for byte in b {
16348 let hi = byte >> 4;
16349 let lo = byte & 0x0F;
16350 out.push(hex_digit(hi));
16351 out.push(hex_digit(lo));
16352 }
16353 out
16354}
16355
16356const fn hex_digit(n: u8) -> char {
16357 match n {
16358 0..=9 => (b'0' + n) as char,
16359 10..=15 => (b'a' + n - 10) as char,
16360 _ => '?',
16361 }
16362}
16363
16364fn parse_hstore_str(
16376 s: &str,
16377) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16378 let bytes = s.as_bytes();
16379 let mut i = 0;
16380 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16381 let skip_ws = |bytes: &[u8], i: &mut usize| {
16382 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16383 *i += 1;
16384 }
16385 };
16386 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16387 if *i >= bytes.len() {
16388 return None;
16389 }
16390 if bytes[*i] == b'"' {
16391 *i += 1;
16392 let mut out = alloc::string::String::new();
16393 while *i < bytes.len() {
16394 match bytes[*i] {
16395 b'"' => {
16396 *i += 1;
16397 return Some(out);
16398 }
16399 b'\\' if *i + 1 < bytes.len() => {
16400 out.push(bytes[*i + 1] as char);
16401 *i += 2;
16402 }
16403 c => {
16404 out.push(c as char);
16405 *i += 1;
16406 }
16407 }
16408 }
16409 None
16410 } else {
16411 let start = *i;
16412 while *i < bytes.len()
16413 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16414 {
16415 *i += 1;
16416 }
16417 if *i == start {
16418 return None;
16419 }
16420 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16421 }
16422 };
16423 skip_ws(bytes, &mut i);
16424 while i < bytes.len() {
16425 let key = parse_token(bytes, &mut i)?;
16426 skip_ws(bytes, &mut i);
16427 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16428 return None;
16429 }
16430 i += 2;
16431 skip_ws(bytes, &mut i);
16432 let val_token = if i + 4 <= bytes.len()
16434 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16435 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16436 {
16437 i += 4;
16438 None
16439 } else {
16440 Some(parse_token(bytes, &mut i)?)
16441 };
16442 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16444 out[pos] = (key, val_token);
16445 } else {
16446 out.push((key, val_token));
16447 }
16448 skip_ws(bytes, &mut i);
16449 if i >= bytes.len() {
16450 break;
16451 }
16452 if bytes[i] == b',' {
16453 i += 1;
16454 skip_ws(bytes, &mut i);
16455 continue;
16456 }
16457 return None;
16458 }
16459 Some(out)
16460}
16461
16462fn format_hstore_str(
16466 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16467) -> alloc::string::String {
16468 let mut out = alloc::string::String::new();
16469 for (i, (k, v)) in pairs.iter().enumerate() {
16470 if i > 0 {
16471 out.push_str(", ");
16472 }
16473 out.push('"');
16474 out.push_str(k);
16475 out.push_str("\"=>");
16476 match v {
16477 None => out.push_str("NULL"),
16478 Some(val) => {
16479 out.push('"');
16480 out.push_str(val);
16481 out.push('"');
16482 }
16483 }
16484 }
16485 out
16486}
16487
16488pub fn format_hstore_text(
16491 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16492) -> alloc::string::String {
16493 format_hstore_str(pairs)
16494}
16495
16496fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16501 let s = s.trim();
16502 let outer = s
16503 .strip_prefix('{')
16504 .and_then(|x| x.strip_suffix('}'))
16505 .ok_or("missing outer '{...}' braces")?;
16506 let trimmed = outer.trim();
16507 if trimmed.is_empty() {
16508 return Ok(Vec::new());
16509 }
16510 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16511 let mut i = 0;
16512 let bytes = trimmed.as_bytes();
16513 while i < bytes.len() {
16514 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16515 i += 1;
16516 }
16517 if i >= bytes.len() {
16518 break;
16519 }
16520 if bytes[i] != b'{' {
16521 return Err("expected '{' opening a row");
16522 }
16523 i += 1;
16524 let row_start = i;
16525 let mut depth = 1;
16526 while i < bytes.len() && depth > 0 {
16527 match bytes[i] {
16528 b'{' => depth += 1,
16529 b'}' => depth -= 1,
16530 _ => {}
16531 }
16532 if depth > 0 {
16533 i += 1;
16534 }
16535 }
16536 if depth != 0 {
16537 return Err("unbalanced '{...}' in row");
16538 }
16539 let row_text = &trimmed[row_start..i];
16540 i += 1;
16541 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16542 Vec::new()
16543 } else {
16544 row_text.split(',').map(|t| t.trim().to_string()).collect()
16545 };
16546 rows.push(cells);
16547 }
16548 if let Some(first) = rows.first() {
16549 let cols = first.len();
16550 for r in &rows {
16551 if r.len() != cols {
16552 return Err("ragged 2D array (rows have different column counts)");
16553 }
16554 }
16555 }
16556 Ok(rows)
16557}
16558
16559fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16560 let raw = split_2d_literal(s)?;
16561 raw.into_iter()
16562 .map(|row| {
16563 row.into_iter()
16564 .map(|cell| {
16565 if cell.eq_ignore_ascii_case("NULL") {
16566 Ok(None)
16567 } else {
16568 cell.parse::<i32>()
16569 .map(Some)
16570 .map_err(|_| "invalid int element")
16571 }
16572 })
16573 .collect()
16574 })
16575 .collect()
16576}
16577
16578fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16579 let raw = split_2d_literal(s)?;
16580 raw.into_iter()
16581 .map(|row| {
16582 row.into_iter()
16583 .map(|cell| {
16584 if cell.eq_ignore_ascii_case("NULL") {
16585 Ok(None)
16586 } else {
16587 cell.parse::<i64>()
16588 .map(Some)
16589 .map_err(|_| "invalid bigint element")
16590 }
16591 })
16592 .collect()
16593 })
16594 .collect()
16595}
16596
16597fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16598 let raw = split_2d_literal(s)?;
16599 Ok(raw
16600 .into_iter()
16601 .map(|row| {
16602 row.into_iter()
16603 .map(|cell| {
16604 if cell.eq_ignore_ascii_case("NULL") {
16605 None
16606 } else {
16607 Some(cell.trim_matches('"').to_string())
16608 }
16609 })
16610 .collect()
16611 })
16612 .collect())
16613}
16614
16615fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16616 let mut out = alloc::string::String::from("{");
16617 for (i, row) in rows.iter().enumerate() {
16618 if i > 0 {
16619 out.push(',');
16620 }
16621 out.push('{');
16622 for (j, cell) in row.iter().enumerate() {
16623 if j > 0 {
16624 out.push(',');
16625 }
16626 match cell {
16627 None => out.push_str("NULL"),
16628 Some(n) => out.push_str(&alloc::format!("{n}")),
16629 }
16630 }
16631 out.push('}');
16632 }
16633 out.push('}');
16634 out
16635}
16636
16637fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16638 let mut out = alloc::string::String::from("{");
16639 for (i, row) in rows.iter().enumerate() {
16640 if i > 0 {
16641 out.push(',');
16642 }
16643 out.push('{');
16644 for (j, cell) in row.iter().enumerate() {
16645 if j > 0 {
16646 out.push(',');
16647 }
16648 match cell {
16649 None => out.push_str("NULL"),
16650 Some(n) => out.push_str(&alloc::format!("{n}")),
16651 }
16652 }
16653 out.push('}');
16654 }
16655 out.push('}');
16656 out
16657}
16658
16659fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16660 let mut out = alloc::string::String::from("{");
16661 for (i, row) in rows.iter().enumerate() {
16662 if i > 0 {
16663 out.push(',');
16664 }
16665 out.push('{');
16666 for (j, cell) in row.iter().enumerate() {
16667 if j > 0 {
16668 out.push(',');
16669 }
16670 match cell {
16671 None => out.push_str("NULL"),
16672 Some(s) => out.push_str(s),
16673 }
16674 }
16675 out.push('}');
16676 }
16677 out.push('}');
16678 out
16679}
16680
16681pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16684 format_int_2d_text(rows)
16685}
16686pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16687 format_bigint_2d_text(rows)
16688}
16689pub fn format_text_2d_text_pub(
16690 rows: &[Vec<Option<alloc::string::String>>],
16691) -> alloc::string::String {
16692 format_text_2d_text(rows)
16693}
16694
16695fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16700 let s = s.trim();
16701 if s.eq_ignore_ascii_case("empty") {
16702 return Some(Value::Range {
16703 kind,
16704 lower: None,
16705 upper: None,
16706 lower_inc: false,
16707 upper_inc: false,
16708 empty: true,
16709 });
16710 }
16711 let bytes = s.as_bytes();
16712 if bytes.len() < 3 {
16713 return None;
16714 }
16715 let lower_inc = match bytes[0] {
16716 b'[' => true,
16717 b'(' => false,
16718 _ => return None,
16719 };
16720 let upper_inc = match bytes[bytes.len() - 1] {
16721 b']' => true,
16722 b')' => false,
16723 _ => return None,
16724 };
16725 let inner = &s[1..s.len() - 1];
16726 let (lo_text, up_text) = inner.split_once(',')?;
16727 let lower = if lo_text.is_empty() {
16728 None
16729 } else {
16730 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16731 };
16732 let upper = if up_text.is_empty() {
16733 None
16734 } else {
16735 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16736 };
16737 Some(Value::Range {
16738 kind,
16739 lower,
16740 upper,
16741 lower_inc,
16742 upper_inc,
16743 empty: false,
16744 })
16745}
16746
16747fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16750 let text = text.trim().trim_matches('"');
16751 use spg_storage::RangeKind as K;
16752 match kind {
16753 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16754 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16755 K::Num => {
16756 let dot = text.find('.');
16759 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16760 let digits: alloc::string::String = text
16761 .chars()
16762 .filter(|c| *c == '-' || c.is_ascii_digit())
16763 .collect();
16764 let scaled: i128 = digits.parse().ok()?;
16765 Some(Value::Numeric { scaled, scale })
16766 }
16767 K::Ts | K::TsTz => {
16768 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16773 }
16774 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16775 }
16776}
16777
16778pub fn format_range_text(v: &Value) -> alloc::string::String {
16782 format_range_str(v)
16783}
16784
16785fn format_range_str(v: &Value) -> alloc::string::String {
16786 let Value::Range {
16787 lower,
16788 upper,
16789 lower_inc,
16790 upper_inc,
16791 empty,
16792 ..
16793 } = v
16794 else {
16795 return alloc::string::String::new();
16796 };
16797 if *empty {
16798 return "empty".into();
16799 }
16800 let mut out = alloc::string::String::new();
16801 out.push(if *lower_inc { '[' } else { '(' });
16802 if let Some(l) = lower {
16803 out.push_str(&format_range_element(l));
16804 }
16805 out.push(',');
16806 if let Some(u) = upper {
16807 out.push_str(&format_range_element(u));
16808 }
16809 out.push(if *upper_inc { ']' } else { ')' });
16810 out
16811}
16812
16813fn format_range_element(v: &Value) -> alloc::string::String {
16814 match v {
16815 Value::Int(n) => alloc::format!("{n}"),
16816 Value::BigInt(n) => alloc::format!("{n}"),
16817 Value::Date(d) => crate::eval::format_date(*d),
16818 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16819 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16820 other => alloc::format!("{other:?}"),
16821 }
16822}
16823
16824fn parse_money_str(s: &str) -> Option<i64> {
16835 let s = s.trim();
16836 let (neg, rest) = match s.strip_prefix('-') {
16837 Some(r) => (true, r.trim_start()),
16838 None => (false, s),
16839 };
16840 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16841 let (int_part, frac_part) = match rest.split_once('.') {
16842 Some((i, f)) => (i, Some(f)),
16843 None => (rest, None),
16844 };
16845 if int_part.is_empty() {
16846 return None;
16847 }
16848 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16850 for b in int_part.bytes() {
16851 match b {
16852 b',' => {}
16853 b'0'..=b'9' => int_digits.push(b as char),
16854 _ => return None,
16855 }
16856 }
16857 if int_digits.is_empty() {
16858 return None;
16859 }
16860 let dollars: i64 = int_digits.parse().ok()?;
16861 let cents: i64 = match frac_part {
16862 None => 0,
16863 Some(f) => {
16864 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16865 return None;
16866 }
16867 let padded = if f.len() == 1 {
16868 alloc::format!("{f}0")
16869 } else {
16870 f.to_string()
16871 };
16872 padded.parse().ok()?
16873 }
16874 };
16875 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16876 Some(if neg { -total } else { total })
16877}
16878
16879fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16890 let s = s.trim();
16891 let bytes = s.as_bytes();
16895 let sign_pos = bytes
16896 .iter()
16897 .enumerate()
16898 .rev()
16899 .find(|&(_, &b)| b == b'+' || b == b'-')
16900 .map(|(i, _)| i)?;
16901 if sign_pos == 0 {
16902 return None; }
16904 let time_part = &s[..sign_pos];
16905 let offset_part = &s[sign_pos..];
16906 let us = parse_time_str(time_part)?;
16907 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16908 let offset_body = &offset_part[1..];
16909 let (hh_str, mm_str) = match offset_body.split_once(':') {
16910 Some((h, m)) => (h, m),
16911 None => (offset_body, "0"),
16912 };
16913 let hh: i32 = hh_str.parse().ok()?;
16914 let mm: i32 = mm_str.parse().ok()?;
16915 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16916 return None;
16917 }
16918 let total = sign * (hh * 3600 + mm * 60);
16919 if total.abs() > 50_400 {
16920 return None;
16921 }
16922 Some((us, total))
16923}
16924
16925fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16930 if n == 0 || (1901..=2155).contains(&n) {
16931 return Ok(Value::Year(n as u16));
16934 }
16935 Err(EngineError::Eval(EvalError::TypeMismatch {
16936 detail: alloc::format!(
16937 "year value out of range: {n} (column `{col_name}`; \
16938 MySQL accepts 0 or 1901..=2155)"
16939 ),
16940 }))
16941}
16942
16943fn parse_time_str(s: &str) -> Option<i64> {
16955 let s = s.trim();
16956 let (hms, frac) = match s.split_once('.') {
16957 Some((h, f)) => (h, Some(f)),
16958 None => (s, None),
16959 };
16960 let mut parts = hms.split(':');
16961 let hh: u32 = parts.next()?.parse().ok()?;
16962 let mm: u32 = parts.next()?.parse().ok()?;
16963 let ss: u32 = parts.next()?.parse().ok()?;
16964 if parts.next().is_some() {
16965 return None;
16966 }
16967 if hh > 23 || mm > 59 || ss > 59 {
16968 return None;
16969 }
16970 let frac_us: i64 = match frac {
16971 None => 0,
16972 Some(f) => {
16973 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16974 return None;
16975 }
16976 let mut padded = alloc::string::String::with_capacity(6);
16978 padded.push_str(f);
16979 while padded.len() < 6 {
16980 padded.push('0');
16981 }
16982 padded.parse().ok()?
16983 }
16984 };
16985 Some(
16986 i64::from(hh) * 3_600_000_000
16987 + i64::from(mm) * 60_000_000
16988 + i64::from(ss) * 1_000_000
16989 + frac_us,
16990 )
16991}
16992
16993const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16994 match t {
16995 ColumnTypeName::SmallInt => DataType::SmallInt,
16996 ColumnTypeName::Int => DataType::Int,
16997 ColumnTypeName::BigInt => DataType::BigInt,
16998 ColumnTypeName::Float => DataType::Float,
16999 ColumnTypeName::Text => DataType::Text,
17000 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17001 ColumnTypeName::Char(n) => DataType::Char(n),
17002 ColumnTypeName::Bool => DataType::Bool,
17003 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17004 dim,
17005 encoding: match encoding {
17006 SqlVecEncoding::F32 => VecEncoding::F32,
17007 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17008 SqlVecEncoding::F16 => VecEncoding::F16,
17009 },
17010 },
17011 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17012 ColumnTypeName::Date => DataType::Date,
17013 ColumnTypeName::Timestamp => DataType::Timestamp,
17014 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17015 ColumnTypeName::Json => DataType::Json,
17016 ColumnTypeName::Jsonb => DataType::Jsonb,
17017 ColumnTypeName::Bytes => DataType::Bytes,
17018 ColumnTypeName::TextArray => DataType::TextArray,
17019 ColumnTypeName::IntArray => DataType::IntArray,
17020 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17021 ColumnTypeName::TsVector => DataType::TsVector,
17022 ColumnTypeName::TsQuery => DataType::TsQuery,
17023 ColumnTypeName::Uuid => DataType::Uuid,
17024 ColumnTypeName::Time => DataType::Time,
17025 ColumnTypeName::Year => DataType::Year,
17026 ColumnTypeName::TimeTz => DataType::TimeTz,
17027 ColumnTypeName::Money => DataType::Money,
17028 ColumnTypeName::Range(k) => DataType::Range(match k {
17029 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17030 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17031 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17032 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17033 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17034 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17035 }),
17036 ColumnTypeName::Hstore => DataType::Hstore,
17037 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17038 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17039 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17040 }
17041}
17042
17043fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17047 match expr {
17048 Expr::Literal(l) => Ok(literal_to_value(l)),
17049 Expr::Cast { expr, target } => {
17050 let inner_value = literal_expr_to_value(*expr)?;
17051 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17052 }
17053 Expr::Unary {
17054 op: UnOp::Neg,
17055 expr,
17056 } => match *expr {
17057 Expr::Literal(Literal::Integer(n)) => {
17058 let neg = n.checked_neg().ok_or_else(|| {
17061 EngineError::Unsupported("integer literal overflow on negation".into())
17062 })?;
17063 Ok(int_value_for(neg))
17064 }
17065 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17066 other => Err(EngineError::Unsupported(alloc::format!(
17067 "unary minus over non-literal expression: {other:?}"
17068 ))),
17069 },
17070 Expr::Array(items) => {
17078 let mut materialised: alloc::vec::Vec<Value> =
17079 alloc::vec::Vec::with_capacity(items.len());
17080 for elem in items {
17081 materialised.push(literal_expr_to_value(elem)?);
17082 }
17083 Ok(array_literal_widen(materialised))
17084 }
17085 other => {
17098 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17099 let ctx = EvalContext::new(&empty_schema, None);
17100 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17101 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17102 }
17103 }
17104}
17105
17106fn literal_to_value(l: Literal) -> Value {
17107 match l {
17108 Literal::Integer(n) => int_value_for(n),
17109 Literal::Float(x) => Value::Float(x),
17110 Literal::String(s) => Value::Text(s),
17111 Literal::Bool(b) => Value::Bool(b),
17112 Literal::Null => Value::Null,
17113 Literal::Vector(v) => Value::Vector(v),
17114 Literal::TextArray(items) => Value::TextArray(items),
17115 Literal::IntArray(items) => Value::IntArray(items),
17116 Literal::BigIntArray(items) => Value::BigIntArray(items),
17117 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17118 }
17119}
17120
17121fn int_value_for(n: i64) -> Value {
17125 if let Ok(small) = i32::try_from(n) {
17126 Value::Int(small)
17127 } else {
17128 Value::BigInt(n)
17129 }
17130}
17131
17132#[allow(clippy::too_many_lines)]
17138fn check_unsigned_range(
17143 v: &Value,
17144 schema: &ColumnSchema,
17145 position: usize,
17146) -> Result<(), EngineError> {
17147 if !schema.is_unsigned {
17148 return Ok(());
17149 }
17150 let n = match v {
17151 Value::SmallInt(x) => i64::from(*x),
17152 Value::Int(x) => i64::from(*x),
17153 Value::BigInt(x) => *x,
17154 _ => return Ok(()), };
17156 if n < 0 {
17157 return Err(EngineError::Unsupported(alloc::format!(
17158 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17159 schema.name
17160 )));
17161 }
17162 Ok(())
17163}
17164
17165fn coerce_value(
17166 v: Value,
17167 expected: DataType,
17168 col_name: &str,
17169 position: usize,
17170) -> Result<Value, EngineError> {
17171 if v.is_null() {
17172 return Ok(Value::Null);
17173 }
17174 let actual = v.data_type().expect("non-null");
17175 if actual == expected {
17176 return Ok(v);
17177 }
17178 let coerced = match (v, expected) {
17179 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17180 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17181 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17182 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17183 i128::from(n),
17184 precision,
17185 scale,
17186 col_name,
17187 )?),
17188 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17189 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17190 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17191 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17192 i128::from(n),
17193 precision,
17194 scale,
17195 col_name,
17196 )?),
17197 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17198 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17199 #[allow(clippy::cast_precision_loss)]
17200 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17201 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17202 i128::from(n),
17203 precision,
17204 scale,
17205 col_name,
17206 )?),
17207 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17208 Some(numeric_from_float(x, precision, scale, col_name)?)
17209 }
17210 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17221 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17222 return Err(EngineError::Eval(EvalError::TypeMismatch {
17223 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17224 }));
17225 };
17226 Some(numeric_rescale(
17227 mantissa, src_scale, precision, scale, col_name,
17228 )?)
17229 }
17230 (Value::Text(s), DataType::Date) => {
17232 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17233 EngineError::Eval(EvalError::TypeMismatch {
17234 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17235 })
17236 })?;
17237 Some(Value::Date(d))
17238 }
17239 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17246 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17247 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17248 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17249 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17250 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17251 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17252 _ => None,
17253 },
17254 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17263 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17264 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17265 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17269 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17270 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17278 (Value::Text(s), DataType::Bytes) => {
17285 let bytes = decode_bytea_literal(&s).map_err(|e| {
17286 EngineError::Eval(EvalError::TypeMismatch {
17287 detail: alloc::format!(
17288 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17289 ),
17290 })
17291 })?;
17292 Some(Value::Bytes(bytes))
17293 }
17294 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17298 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17306 Some(b) => Some(Value::Uuid(b)),
17307 None => {
17308 return Err(EngineError::Eval(EvalError::TypeMismatch {
17309 detail: alloc::format!(
17310 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17311 ),
17312 }));
17313 }
17314 },
17315 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17320 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17326 Some(us) => Some(Value::Time(us)),
17327 None => {
17328 return Err(EngineError::Eval(EvalError::TypeMismatch {
17329 detail: alloc::format!(
17330 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17331 ),
17332 }));
17333 }
17334 },
17335 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17337 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17342 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17343 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17344 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17348 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17349 Err(_) => {
17350 return Err(EngineError::Eval(EvalError::TypeMismatch {
17351 detail: alloc::format!(
17352 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17353 ),
17354 }));
17355 }
17356 },
17357 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17359 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17363 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17364 None => {
17365 return Err(EngineError::Eval(EvalError::TypeMismatch {
17366 detail: alloc::format!(
17367 "invalid input syntax for type time with time zone: \
17368 {s:?} (column `{col_name}`)"
17369 ),
17370 }));
17371 }
17372 },
17373 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17375 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17376 }
17377 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17381 Some(c) => Some(Value::Money(c)),
17382 None => {
17383 return Err(EngineError::Eval(EvalError::TypeMismatch {
17384 detail: alloc::format!(
17385 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17386 ),
17387 }));
17388 }
17389 },
17390 (Value::SmallInt(n), DataType::Money) => {
17394 Some(Value::Money(i64::from(n).saturating_mul(100)))
17395 }
17396 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17397 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17398 (Value::Float(x), DataType::Money) => {
17399 let scaled = x * 100.0;
17402 let cents = if scaled >= 0.0 {
17403 (scaled + 0.5) as i64
17404 } else {
17405 (scaled - 0.5) as i64
17406 };
17407 Some(Value::Money(cents))
17408 }
17409 (Value::Numeric { scaled, scale }, DataType::Money) => {
17410 let cents = if scale == 2 {
17413 scaled
17414 } else if scale < 2 {
17415 let mult = 10_i128.pow(u32::from(2 - scale));
17416 scaled.saturating_mul(mult)
17417 } else {
17418 let div = 10_i128.pow(u32::from(scale - 2));
17419 let half = div / 2;
17420 let bias = if scaled >= 0 { half } else { -half };
17421 (scaled + bias) / div
17422 };
17423 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17424 }
17425 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17427 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17431 Some(v) => Some(v),
17432 None => {
17433 return Err(EngineError::Eval(EvalError::TypeMismatch {
17434 detail: alloc::format!(
17435 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17436 ),
17437 }));
17438 }
17439 },
17440 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17442 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17444 Some(pairs) => Some(Value::Hstore(pairs)),
17445 None => {
17446 return Err(EngineError::Eval(EvalError::TypeMismatch {
17447 detail: alloc::format!(
17448 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17449 ),
17450 }));
17451 }
17452 },
17453 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17455 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17458 Ok(m) => Some(Value::IntArray2D(m)),
17459 Err(e) => {
17460 return Err(EngineError::Eval(EvalError::TypeMismatch {
17461 detail: alloc::format!(
17462 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17463 ),
17464 }));
17465 }
17466 },
17467 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17468 Ok(m) => Some(Value::BigIntArray2D(m)),
17469 Err(e) => {
17470 return Err(EngineError::Eval(EvalError::TypeMismatch {
17471 detail: alloc::format!(
17472 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17473 ),
17474 }));
17475 }
17476 },
17477 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17478 Ok(m) => Some(Value::TextArray2D(m)),
17479 Err(e) => {
17480 return Err(EngineError::Eval(EvalError::TypeMismatch {
17481 detail: alloc::format!(
17482 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17483 ),
17484 }));
17485 }
17486 },
17487 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17489 (Value::BigIntArray2D(rows), DataType::Text) => {
17490 Some(Value::Text(format_bigint_2d_text(&rows)))
17491 }
17492 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17493 (Value::Text(s), DataType::TextArray) => {
17498 let arr = decode_text_array_literal(&s).map_err(|e| {
17499 EngineError::Eval(EvalError::TypeMismatch {
17500 detail: alloc::format!(
17501 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17502 ),
17503 })
17504 })?;
17505 Some(Value::TextArray(arr))
17506 }
17507 (Value::Text(s), DataType::IntArray) => {
17513 let arr = decode_text_array_literal(&s).map_err(|e| {
17514 EngineError::Eval(EvalError::TypeMismatch {
17515 detail: alloc::format!(
17516 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17517 ),
17518 })
17519 })?;
17520 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17521 for elem in arr {
17522 match elem {
17523 None => out.push(None),
17524 Some(t) => {
17525 let n: i32 = t.parse().map_err(|_| {
17526 EngineError::Eval(EvalError::TypeMismatch {
17527 detail: alloc::format!(
17528 "cannot parse {t:?} as INT element for `{col_name}`"
17529 ),
17530 })
17531 })?;
17532 out.push(Some(n));
17533 }
17534 }
17535 }
17536 Some(Value::IntArray(out))
17537 }
17538 (Value::Text(s), DataType::BigIntArray) => {
17539 let arr = decode_text_array_literal(&s).map_err(|e| {
17540 EngineError::Eval(EvalError::TypeMismatch {
17541 detail: alloc::format!(
17542 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17543 ),
17544 })
17545 })?;
17546 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17547 for elem in arr {
17548 match elem {
17549 None => out.push(None),
17550 Some(t) => {
17551 let n: i64 = t.parse().map_err(|_| {
17552 EngineError::Eval(EvalError::TypeMismatch {
17553 detail: alloc::format!(
17554 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17555 ),
17556 })
17557 })?;
17558 out.push(Some(n));
17559 }
17560 }
17561 }
17562 Some(Value::BigIntArray(out))
17563 }
17564 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17568 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17577 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17578 EngineError::Eval(EvalError::TypeMismatch {
17579 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17580 })
17581 })?;
17582 if parsed.len() != dim as usize {
17583 return Err(EngineError::Eval(EvalError::TypeMismatch {
17584 detail: alloc::format!(
17585 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17586 parsed.len()
17587 ),
17588 }));
17589 }
17590 Some(match encoding {
17591 VecEncoding::F32 => Value::Vector(parsed),
17592 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17593 VecEncoding::F16 => {
17594 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17595 }
17596 })
17597 }
17598 (Value::Text(s), DataType::TsVector) => {
17608 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17609 EngineError::Eval(EvalError::TypeMismatch {
17610 detail: alloc::format!(
17611 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17612 ),
17613 })
17614 })?;
17615 Some(Value::TsVector(lexs))
17616 }
17617 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17618 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17619 EngineError::Eval(EvalError::TypeMismatch {
17620 detail: alloc::format!(
17621 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17622 ),
17623 })
17624 })?;
17625 Some(Value::Timestamp(t))
17626 }
17627 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17630 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17631 }
17632 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17636 (Value::Timestamp(t), DataType::Date) => {
17637 let days = t.div_euclid(86_400_000_000);
17638 i32::try_from(days).ok().map(Value::Date)
17639 }
17640 (
17641 Value::Numeric {
17642 scaled,
17643 scale: src_scale,
17644 },
17645 DataType::Numeric { precision, scale },
17646 ) => Some(numeric_rescale(
17647 scaled, src_scale, precision, scale, col_name,
17648 )?),
17649 #[allow(clippy::cast_precision_loss)]
17650 (Value::Numeric { scaled, scale }, DataType::Float) => {
17651 let mut div = 1.0_f64;
17652 for _ in 0..scale {
17653 div *= 10.0;
17654 }
17655 Some(Value::Float((scaled as f64) / div))
17656 }
17657 (Value::Numeric { scaled, scale }, DataType::Int) => {
17658 let truncated = numeric_truncate_to_integer(scaled, scale);
17659 i32::try_from(truncated).ok().map(Value::Int)
17660 }
17661 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17662 let truncated = numeric_truncate_to_integer(scaled, scale);
17663 i64::try_from(truncated).ok().map(Value::BigInt)
17664 }
17665 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17666 let truncated = numeric_truncate_to_integer(scaled, scale);
17667 i16::try_from(truncated).ok().map(Value::SmallInt)
17668 }
17669 (Value::Text(s), DataType::Varchar(max)) => {
17671 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17672 Some(Value::Text(s))
17673 } else {
17674 return Err(EngineError::Unsupported(alloc::format!(
17675 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17676 {} chars",
17677 s.chars().count()
17678 )));
17679 }
17680 }
17681 (
17689 Value::Vector(v),
17690 DataType::Vector {
17691 dim,
17692 encoding: VecEncoding::Sq8,
17693 },
17694 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17695 (
17700 Value::Vector(v),
17701 DataType::Vector {
17702 dim,
17703 encoding: VecEncoding::F16,
17704 },
17705 ) if v.len() == dim as usize => Some(Value::HalfVector(
17706 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17707 )),
17708 (Value::Text(s), DataType::Char(size)) => {
17712 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17713 if len > size {
17714 return Err(EngineError::Unsupported(alloc::format!(
17715 "value for CHAR({size}) column `{col_name}` exceeds length: \
17716 {len} chars"
17717 )));
17718 }
17719 let need = (size - len) as usize;
17720 let mut padded = s;
17721 padded.reserve(need);
17722 for _ in 0..need {
17723 padded.push(' ');
17724 }
17725 Some(Value::Text(padded))
17726 }
17727 _ => None,
17728 };
17729 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17730 column: col_name.into(),
17731 expected,
17732 actual,
17733 position,
17734 }))
17735}
17736
17737fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17743 use core::fmt::Write;
17744 let mut out = alloc::string::String::from("(");
17745 for (i, a) in args.iter().enumerate() {
17746 if i > 0 {
17747 out.push_str(", ");
17748 }
17749 match a.mode {
17750 spg_sql::ast::FunctionArgMode::In => {}
17751 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17752 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17753 }
17754 if let Some(n) = &a.name {
17755 out.push_str(n);
17756 out.push(' ');
17757 }
17758 match &a.ty {
17759 spg_sql::ast::FunctionArgType::Typed(t) => {
17760 let _ = write!(out, "{t}");
17761 }
17762 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17763 }
17764 }
17765 out.push(')');
17766 out
17767}
17768
17769fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
17778 match expr {
17779 spg_sql::ast::Expr::FunctionCall { name, args } => {
17780 name.eq_ignore_ascii_case("unnest") && args.len() == 1
17781 }
17782 _ => false,
17783 }
17784}
17785
17786fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
17790 match expr {
17791 spg_sql::ast::Expr::FunctionCall { name, args }
17792 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
17793 {
17794 Some(&args[0])
17795 }
17796 _ => None,
17797 }
17798}
17799
17800fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
17805 match v {
17806 Value::Null => Ok(Vec::new()),
17807 Value::TextArray(items) => Ok(items
17808 .iter()
17809 .map(|opt| {
17810 opt.as_ref()
17811 .map(|s| Value::Text(s.clone()))
17812 .unwrap_or(Value::Null)
17813 })
17814 .collect()),
17815 Value::IntArray(items) => Ok(items
17816 .iter()
17817 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
17818 .collect()),
17819 Value::BigIntArray(items) => Ok(items
17820 .iter()
17821 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
17822 .collect()),
17823 other => Err(EngineError::Eval(EvalError::TypeMismatch {
17824 detail: alloc::format!(
17825 "unnest() expects an array argument, got {:?}",
17826 other.data_type()
17827 ),
17828 })),
17829 }
17830}
17831
17832#[cfg(test)]
17833mod tests {
17834 use super::*;
17835 use alloc::vec;
17836
17837 fn unwrap_command_ok(r: &QueryResult) -> usize {
17838 match r {
17839 QueryResult::CommandOk { affected, .. } => *affected,
17840 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17841 }
17842 }
17843
17844 #[test]
17845 fn update_seek_positions_engages_on_indexed_eq() {
17846 let mut e = Engine::new();
17847 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
17848 .unwrap();
17849 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
17850 for i in 0..100 {
17851 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
17852 .unwrap();
17853 }
17854 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
17855 .expect("parse");
17856 let Statement::Update(u) = stmt else {
17857 panic!("expected Update, got {stmt:?}");
17858 };
17859 let w = u.where_.as_ref().expect("where");
17860 let table = e.catalog().get("b").unwrap();
17861 let schema_cols = table.schema().columns.clone();
17862 let Expr::Binary { lhs, op, rhs } = w else {
17864 panic!("WHERE not Binary: {w:?}");
17865 };
17866 assert_eq!(*op, BinOp::Eq, "op not Eq");
17867 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
17868 assert!(
17869 pair.is_some(),
17870 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
17871 );
17872 let (col_pos, value) = pair.unwrap();
17873 assert!(
17874 table.index_on(col_pos).is_some(),
17875 "no index on col {col_pos}"
17876 );
17877 assert!(
17878 IndexKey::from_value(&value).is_some(),
17879 "IndexKey::from_value None for {value:?}"
17880 );
17881 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
17882 assert_eq!(positions, Some(vec![42]), "seek did not engage");
17883 }
17884
17885 #[test]
17886 fn create_table_registers_schema() {
17887 let mut e = Engine::new();
17888 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17889 .unwrap();
17890 assert_eq!(e.catalog().table_count(), 1);
17891 let t = e.catalog().get("foo").unwrap();
17892 assert_eq!(t.schema().columns.len(), 2);
17893 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17894 assert!(!t.schema().columns[0].nullable);
17895 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17896 }
17897
17898 #[test]
17899 fn create_table_vector_default_is_f32_encoded() {
17900 let mut e = Engine::new();
17901 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17902 let t = e.catalog().get("t").unwrap();
17903 assert_eq!(
17904 t.schema().columns[0].ty,
17905 DataType::Vector {
17906 dim: 8,
17907 encoding: VecEncoding::F32,
17908 },
17909 );
17910 }
17911
17912 #[test]
17913 fn create_table_vector_using_sq8_succeeds() {
17914 let mut e = Engine::new();
17918 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17919 let t = e.catalog().get("t").unwrap();
17920 assert_eq!(
17921 t.schema().columns[0].ty,
17922 DataType::Vector {
17923 dim: 8,
17924 encoding: VecEncoding::Sq8,
17925 },
17926 );
17927 }
17928
17929 #[test]
17930 fn insert_into_sq8_column_quantises_f32_payload() {
17931 let mut e = Engine::new();
17938 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17939 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17940 .unwrap();
17941 let t = e.catalog().get("t").unwrap();
17942 assert_eq!(t.rows().len(), 1);
17943 match &t.rows()[0].values[0] {
17944 Value::Sq8Vector(q) => {
17945 assert_eq!(q.bytes.len(), 4);
17946 assert!((q.min - 0.0).abs() < 1e-6);
17948 assert!((q.max - 1.0).abs() < 1e-6);
17949 }
17950 other => panic!("expected Sq8Vector cell, got {other:?}"),
17951 }
17952 }
17953
17954 #[test]
17955 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17956 let mut e = Engine::new();
17963 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17964 .unwrap();
17965 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17966 .unwrap();
17967 let t = e.catalog().get("t").unwrap();
17968 assert_eq!(t.rows().len(), 1);
17969 match &t.rows()[0].values[0] {
17970 Value::HalfVector(h) => {
17971 assert_eq!(h.dim(), 4);
17972 let back = h.to_f32_vec();
17973 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17974 for (g, e) in back.iter().zip(expected.iter()) {
17975 assert!(
17976 (g - e).abs() < 1e-6,
17977 "{g} vs {e} should be exact on f16 grid"
17978 );
17979 }
17980 }
17981 other => panic!("expected HalfVector cell, got {other:?}"),
17982 }
17983 }
17984
17985 #[test]
17986 fn alter_index_rebuild_in_place_succeeds() {
17987 let mut e = Engine::new();
17992 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17993 .unwrap();
17994 for i in 0..8_i32 {
17995 #[allow(clippy::cast_precision_loss)]
17996 let base = (i as f32) * 0.1;
17997 e.execute(&alloc::format!(
17998 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
17999 b1 = base + 0.01,
18000 b2 = base + 0.02,
18001 ))
18002 .unwrap();
18003 }
18004 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18005 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18006 assert_eq!(
18008 e.catalog().get("t").unwrap().schema().columns[1].ty,
18009 DataType::Vector {
18010 dim: 3,
18011 encoding: VecEncoding::F32,
18012 },
18013 );
18014 }
18015
18016 #[test]
18017 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18018 let mut e = Engine::new();
18023 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18024 .unwrap();
18025 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18026 .unwrap();
18027 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18028 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18029 .unwrap();
18030 let t = e.catalog().get("t").unwrap();
18031 assert_eq!(
18032 t.schema().columns[1].ty,
18033 DataType::Vector {
18034 dim: 4,
18035 encoding: VecEncoding::Sq8,
18036 },
18037 );
18038 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18039 }
18040
18041 #[test]
18042 fn alter_index_rebuild_unknown_index_errors() {
18043 let mut e = Engine::new();
18044 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18045 assert!(
18046 matches!(
18047 &err,
18048 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18049 ),
18050 "got: {err}"
18051 );
18052 }
18053
18054 #[test]
18055 fn alter_index_rebuild_on_btree_index_errors() {
18056 let mut e = Engine::new();
18059 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18060 e.execute("INSERT INTO t VALUES (1)").unwrap();
18061 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18062 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18063 assert!(
18064 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18065 "got: {err}"
18066 );
18067 }
18068
18069 #[test]
18070 fn prepared_insert_substitutes_placeholders() {
18071 let mut e = Engine::new();
18077 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18078 .unwrap();
18079 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18080 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18081 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18082 .unwrap();
18083 }
18084 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18086 let QueryResult::Rows { rows, .. } = rows_result else {
18087 panic!("expected Rows")
18088 };
18089 assert_eq!(rows.len(), 3);
18090 }
18091
18092 #[test]
18093 fn prepared_select_with_placeholder_filters_rows() {
18094 let mut e = Engine::new();
18095 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18096 .unwrap();
18097 for i in 0..10_i32 {
18098 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18099 .unwrap();
18100 }
18101 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18102 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18103 else {
18104 panic!("expected Rows")
18105 };
18106 assert_eq!(rows.len(), 1);
18108 assert_eq!(rows[0].values[0], Value::Int(5));
18109 }
18110
18111 #[test]
18112 fn prepared_too_few_params_errors() {
18113 let mut e = Engine::new();
18114 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18115 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18116 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18117 assert!(
18118 matches!(
18119 &err,
18120 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18121 ),
18122 "got: {err}"
18123 );
18124 }
18125
18126 #[test]
18127 fn bytea_cast_round_trips_text_input() {
18128 let e = Engine::new();
18131 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18132 let QueryResult::Rows { rows, .. } = r else {
18133 panic!("expected Rows")
18134 };
18135 assert_eq!(rows.len(), 1);
18136 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18137 }
18138
18139 #[test]
18140 fn bytea_cast_pg_escape_hex_form() {
18141 let e = Engine::new();
18145 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18146 let QueryResult::Rows { rows, .. } = r else {
18147 panic!("expected Rows")
18148 };
18149 assert_eq!(
18150 rows[0].values[0],
18151 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18152 );
18153 }
18154
18155 #[test]
18156 fn bytea_cast_chains_through_octet_length() {
18157 let e = Engine::new();
18161 let r = e
18162 .execute_readonly("SELECT octet_length('hello'::bytea)")
18163 .unwrap();
18164 let QueryResult::Rows { rows, .. } = r else {
18165 panic!("expected Rows")
18166 };
18167 match &rows[0].values[0] {
18168 Value::Int(n) => assert_eq!(*n, 5),
18169 Value::BigInt(n) => assert_eq!(*n, 5),
18170 other => panic!("expected integer length, got {other:?}"),
18171 }
18172 }
18173
18174 #[test]
18175 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18176 let mut e = Engine::new();
18182 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18183 .unwrap();
18184 for i in 0..10_i32 {
18185 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18186 .unwrap();
18187 }
18188 let snapshot = e.clone_snapshot();
18189 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18190 let QueryResult::Rows { rows, .. } =
18191 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18192 .unwrap()
18193 else {
18194 panic!("expected Rows")
18195 };
18196 assert_eq!(rows.len(), 1);
18197 assert_eq!(rows[0].values[0], Value::Int(5));
18198 }
18199
18200 #[test]
18201 fn readonly_prepared_on_snapshot_rejects_writes() {
18202 let mut e = Engine::new();
18206 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18207 let snapshot = e.clone_snapshot();
18208 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18209 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18210 .unwrap_err();
18211 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18212 }
18213
18214 #[test]
18215 fn readonly_prepared_on_snapshot_frozen_view() {
18216 let mut e = Engine::new();
18222 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18223 e.execute("INSERT INTO t VALUES (1)").unwrap();
18224 let snapshot = e.clone_snapshot();
18225 e.execute("INSERT INTO t VALUES (2)").unwrap();
18226 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18227 let QueryResult::Rows { rows, .. } =
18228 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18229 .unwrap()
18230 else {
18231 panic!("expected Rows")
18232 };
18233 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18234 }
18235
18236 #[test]
18237 fn describe_prepared_on_snapshot_resolves_columns() {
18238 let mut e = Engine::new();
18243 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18244 .unwrap();
18245 let snapshot = e.clone_snapshot();
18246 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18247 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18248 assert_eq!(cols.len(), 2);
18249 assert_eq!(cols[0].name, "id");
18250 assert_eq!(cols[0].ty, DataType::Int);
18251 assert_eq!(cols[1].name, "name");
18252 assert_eq!(cols[1].ty, DataType::Text);
18253 }
18254
18255 #[test]
18256 fn insert_into_half_column_dim_mismatch_errors() {
18257 let mut e = Engine::new();
18258 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18259 .unwrap();
18260 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18261 assert!(matches!(
18262 &err,
18263 EngineError::Storage(StorageError::TypeMismatch { .. })
18264 ));
18265 }
18266
18267 #[test]
18268 fn insert_into_sq8_column_dim_mismatch_errors() {
18269 let mut e = Engine::new();
18274 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18275 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18276 assert!(
18277 matches!(
18278 &err,
18279 EngineError::Storage(StorageError::TypeMismatch { .. })
18280 ),
18281 "got: {err}",
18282 );
18283 }
18284
18285 #[test]
18286 fn create_table_duplicate_errors() {
18287 let mut e = Engine::new();
18288 e.execute("CREATE TABLE foo (a INT)").unwrap();
18289 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18290 assert!(matches!(
18291 err,
18292 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18293 ));
18294 }
18295
18296 #[test]
18297 fn insert_into_unknown_table_errors() {
18298 let mut e = Engine::new();
18299 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18300 assert!(matches!(
18301 err,
18302 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18303 ));
18304 }
18305
18306 #[test]
18307 fn insert_happy_path_reports_one_affected() {
18308 let mut e = Engine::new();
18309 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18310 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18311 assert_eq!(unwrap_command_ok(&r), 1);
18312 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18313 }
18314
18315 #[test]
18316 fn insert_arity_mismatch_propagates() {
18317 let mut e = Engine::new();
18318 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18319 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18320 assert!(matches!(
18321 err,
18322 EngineError::Storage(StorageError::ArityMismatch { .. })
18323 ));
18324 }
18325
18326 #[test]
18327 fn insert_negative_integer_via_unary_minus() {
18328 let mut e = Engine::new();
18329 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18330 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18331 let rows = e.catalog().get("foo").unwrap().rows();
18332 assert_eq!(rows[0].values[0], Value::Int(-7));
18333 }
18334
18335 #[test]
18336 fn insert_expression_evaluated_against_empty_context() {
18337 let mut e = Engine::new();
18342 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18343 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18344 let rows = e.catalog().get("foo").unwrap().rows();
18345 assert_eq!(rows[0].values[0], Value::Int(3));
18346 }
18347
18348 #[test]
18349 fn select_star_returns_all_rows_in_insertion_order() {
18350 let mut e = Engine::new();
18351 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18352 .unwrap();
18353 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18354 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18355 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18356
18357 let r = e.execute("SELECT * FROM foo").unwrap();
18358 let QueryResult::Rows { columns, rows } = r else {
18359 panic!("expected Rows")
18360 };
18361 assert_eq!(columns.len(), 2);
18362 assert_eq!(columns[0].name, "a");
18363 assert_eq!(rows.len(), 3);
18364 assert_eq!(
18365 rows[1].values,
18366 vec![Value::Int(2), Value::Text("two".into())]
18367 );
18368 }
18369
18370 #[test]
18371 fn select_star_on_empty_table_returns_zero_rows() {
18372 let mut e = Engine::new();
18373 e.execute("CREATE TABLE foo (a INT)").unwrap();
18374 let r = e.execute("SELECT * FROM foo").unwrap();
18375 match r {
18376 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18377 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18378 }
18379 }
18380
18381 fn make_three_row_users(e: &mut Engine) {
18384 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18385 .unwrap();
18386 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18387 .unwrap();
18388 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18389 .unwrap();
18390 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18391 .unwrap();
18392 }
18393
18394 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18395 match r {
18396 QueryResult::Rows { columns, rows } => (columns, rows),
18397 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18398 }
18399 }
18400
18401 #[test]
18402 fn where_filter_passes_only_true_rows() {
18403 let mut e = Engine::new();
18404 make_three_row_users(&mut e);
18405 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18406 let (_, rows) = unwrap_rows(r);
18407 assert_eq!(rows.len(), 2);
18408 assert_eq!(rows[0].values[0], Value::Int(2));
18409 assert_eq!(rows[1].values[0], Value::Int(3));
18410 }
18411
18412 #[test]
18413 fn where_with_null_result_filters_out_row() {
18414 let mut e = Engine::new();
18415 make_three_row_users(&mut e);
18416 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18418 let (_, rows) = unwrap_rows(r);
18419 assert_eq!(rows.len(), 1);
18420 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18421 }
18422
18423 #[test]
18424 fn projection_named_columns() {
18425 let mut e = Engine::new();
18426 make_three_row_users(&mut e);
18427 let r = e.execute("SELECT name, score FROM users").unwrap();
18428 let (cols, rows) = unwrap_rows(r);
18429 assert_eq!(cols.len(), 2);
18430 assert_eq!(cols[0].name, "name");
18431 assert_eq!(cols[1].name, "score");
18432 assert_eq!(rows.len(), 3);
18433 assert_eq!(
18434 rows[0].values,
18435 vec![Value::Text("alice".into()), Value::Int(90)]
18436 );
18437 }
18438
18439 #[test]
18440 fn projection_with_column_alias() {
18441 let mut e = Engine::new();
18442 make_three_row_users(&mut e);
18443 let r = e
18444 .execute("SELECT name AS who FROM users WHERE id = 1")
18445 .unwrap();
18446 let (cols, rows) = unwrap_rows(r);
18447 assert_eq!(cols[0].name, "who");
18448 assert_eq!(rows.len(), 1);
18449 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18450 }
18451
18452 #[test]
18453 fn qualified_column_with_table_alias_resolves() {
18454 let mut e = Engine::new();
18455 make_three_row_users(&mut e);
18456 let r = e
18457 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18458 .unwrap();
18459 let (cols, rows) = unwrap_rows(r);
18460 assert_eq!(cols.len(), 2);
18461 assert_eq!(rows.len(), 2);
18462 }
18463
18464 #[test]
18465 fn qualified_column_with_wrong_alias_errors() {
18466 let mut e = Engine::new();
18467 make_three_row_users(&mut e);
18468 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18469 assert!(matches!(
18470 err,
18471 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18472 ));
18473 }
18474
18475 #[test]
18476 fn select_unknown_column_errors_in_projection() {
18477 let mut e = Engine::new();
18478 make_three_row_users(&mut e);
18479 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18480 assert!(matches!(
18481 err,
18482 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18483 ));
18484 }
18485
18486 #[test]
18487 fn where_unknown_column_errors() {
18488 let mut e = Engine::new();
18489 make_three_row_users(&mut e);
18490 let err = e
18491 .execute("SELECT * FROM users WHERE ghost = 1")
18492 .unwrap_err();
18493 assert!(matches!(
18494 err,
18495 EngineError::Eval(EvalError::ColumnNotFound { .. })
18496 ));
18497 }
18498
18499 #[test]
18500 fn expression_projection_evaluates_and_renders() {
18501 let mut e = Engine::new();
18504 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18505 e.execute("INSERT INTO t VALUES (3)").unwrap();
18506 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18507 assert_eq!(rows.len(), 1);
18508 assert_eq!(rows[0].values[0], Value::Int(3));
18511 }
18512
18513 #[test]
18514 fn select_unknown_table_errors() {
18515 let mut e = Engine::new();
18516 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18517 assert!(matches!(
18518 err,
18519 EngineError::Storage(StorageError::TableNotFound { .. })
18520 ));
18521 }
18522
18523 #[test]
18524 fn invalid_sql_returns_parse_error() {
18525 let mut e = Engine::new();
18528 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18529 assert!(matches!(err, EngineError::Parse(_)));
18530 }
18531
18532 #[test]
18535 fn create_index_registers_on_table() {
18536 let mut e = Engine::new();
18537 make_three_row_users(&mut e);
18538 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18539 let t = e.catalog().get("users").unwrap();
18540 assert_eq!(t.indices().len(), 1);
18541 assert_eq!(t.indices()[0].name, "by_name");
18542 }
18543
18544 #[test]
18545 fn create_index_on_unknown_table_errors() {
18546 let mut e = Engine::new();
18547 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18548 assert!(matches!(
18549 err,
18550 EngineError::Storage(StorageError::TableNotFound { .. })
18551 ));
18552 }
18553
18554 #[test]
18555 fn create_index_on_unknown_column_errors() {
18556 let mut e = Engine::new();
18557 make_three_row_users(&mut e);
18558 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18559 assert!(matches!(
18560 err,
18561 EngineError::Storage(StorageError::ColumnNotFound { .. })
18562 ));
18563 }
18564
18565 #[test]
18566 fn select_eq_uses_index_returns_same_rows_as_scan() {
18567 let mut without = Engine::new();
18571 make_three_row_users(&mut without);
18572 let mut with = Engine::new();
18573 make_three_row_users(&mut with);
18574 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18575
18576 let q = "SELECT * FROM users WHERE id = 2";
18577 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18578 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18579 assert_eq!(no_idx_rows, idx_rows);
18580 assert_eq!(idx_rows.len(), 1);
18581 }
18582
18583 #[test]
18584 fn select_eq_with_no_matching_index_value_returns_empty() {
18585 let mut e = Engine::new();
18586 make_three_row_users(&mut e);
18587 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18588 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18589 assert_eq!(rows.len(), 0);
18590 }
18591
18592 #[test]
18595 fn begin_sets_in_transaction_flag() {
18596 let mut e = Engine::new();
18597 assert!(!e.in_transaction());
18598 e.execute("BEGIN").unwrap();
18599 assert!(e.in_transaction());
18600 }
18601
18602 #[test]
18603 fn double_begin_errors() {
18604 let mut e = Engine::new();
18605 e.execute("BEGIN").unwrap();
18606 let err = e.execute("BEGIN").unwrap_err();
18607 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18608 }
18609
18610 #[test]
18611 fn commit_without_begin_errors() {
18612 let mut e = Engine::new();
18613 let err = e.execute("COMMIT").unwrap_err();
18614 assert_eq!(err, EngineError::NoActiveTransaction);
18615 }
18616
18617 #[test]
18618 fn rollback_without_begin_errors() {
18619 let mut e = Engine::new();
18620 let err = e.execute("ROLLBACK").unwrap_err();
18621 assert_eq!(err, EngineError::NoActiveTransaction);
18622 }
18623
18624 #[test]
18625 fn commit_applies_shadow_to_committed_catalog() {
18626 let mut e = Engine::new();
18627 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18628 e.execute("BEGIN").unwrap();
18629 e.execute("INSERT INTO t VALUES (1)").unwrap();
18630 e.execute("INSERT INTO t VALUES (2)").unwrap();
18631 e.execute("COMMIT").unwrap();
18632 assert!(!e.in_transaction());
18633 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18634 }
18635
18636 #[test]
18637 fn rollback_discards_shadow() {
18638 let mut e = Engine::new();
18639 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18640 e.execute("BEGIN").unwrap();
18641 e.execute("INSERT INTO t VALUES (1)").unwrap();
18642 e.execute("INSERT INTO t VALUES (2)").unwrap();
18643 e.execute("ROLLBACK").unwrap();
18644 assert!(!e.in_transaction());
18645 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18646 }
18647
18648 #[test]
18649 fn select_during_tx_sees_uncommitted_writes_own_session() {
18650 let mut e = Engine::new();
18653 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18654 e.execute("BEGIN").unwrap();
18655 e.execute("INSERT INTO t VALUES (42)").unwrap();
18656 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18657 assert_eq!(rows.len(), 1);
18658 assert_eq!(rows[0].values[0], Value::Int(42));
18659 }
18660
18661 #[test]
18662 fn snapshot_with_no_users_is_bare_catalog_format() {
18663 let mut e = Engine::new();
18664 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18665 let bytes = e.snapshot();
18666 assert_eq!(
18667 &bytes[..8],
18668 b"SPGDB001",
18669 "must be the bare v3.x catalog magic"
18670 );
18671 let e2 = Engine::restore_envelope(&bytes).unwrap();
18672 assert!(e2.users().is_empty());
18673 assert_eq!(e2.catalog().table_count(), 1);
18674 }
18675
18676 #[test]
18677 fn snapshot_with_users_round_trips_both_via_envelope() {
18678 let mut e = Engine::new();
18679 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18680 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18681 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18682 .unwrap();
18683 let bytes = e.snapshot();
18684 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18685 let e2 = Engine::restore_envelope(&bytes).unwrap();
18686 assert_eq!(e2.users().len(), 2);
18687 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18688 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18689 assert_eq!(e2.verify_user("alice", "wrong"), None);
18690 assert_eq!(e2.catalog().table_count(), 1);
18691 }
18692
18693 #[test]
18694 fn ddl_inside_tx_also_rolled_back() {
18695 let mut e = Engine::new();
18696 e.execute("BEGIN").unwrap();
18697 e.execute("CREATE TABLE t (v INT)").unwrap();
18698 e.execute("SELECT * FROM t").unwrap();
18700 e.execute("ROLLBACK").unwrap();
18701 let err = e.execute("SELECT * FROM t").unwrap_err();
18703 assert!(matches!(
18704 err,
18705 EngineError::Storage(StorageError::TableNotFound { .. })
18706 ));
18707 }
18708
18709 #[test]
18712 fn create_publication_lands_in_catalog() {
18713 let mut e = Engine::new();
18714 assert!(e.publications().is_empty());
18715 e.execute("CREATE PUBLICATION pub_a").unwrap();
18716 assert_eq!(e.publications().len(), 1);
18717 assert!(e.publications().contains("pub_a"));
18718 }
18719
18720 #[test]
18721 fn create_publication_duplicate_errors() {
18722 let mut e = Engine::new();
18723 e.execute("CREATE PUBLICATION pub_a").unwrap();
18724 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18725 assert!(
18726 alloc::format!("{err:?}").contains("DuplicateName"),
18727 "got {err:?}"
18728 );
18729 }
18730
18731 #[test]
18732 fn drop_publication_silent_when_absent() {
18733 let mut e = Engine::new();
18734 let r = e.execute("DROP PUBLICATION nope").unwrap();
18737 match r {
18738 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18739 other => panic!("expected CommandOk, got {other:?}"),
18740 }
18741 }
18742
18743 #[test]
18744 fn drop_publication_present_reports_one_affected() {
18745 let mut e = Engine::new();
18746 e.execute("CREATE PUBLICATION pub_a").unwrap();
18747 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18748 match r {
18749 QueryResult::CommandOk {
18750 affected,
18751 modified_catalog,
18752 } => {
18753 assert_eq!(affected, 1);
18754 assert!(modified_catalog);
18755 }
18756 other => panic!("expected CommandOk, got {other:?}"),
18757 }
18758 assert!(e.publications().is_empty());
18759 }
18760
18761 #[test]
18762 fn publications_persist_across_snapshot_restore() {
18763 let mut e = Engine::new();
18768 e.execute("CREATE PUBLICATION pub_a").unwrap();
18769 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18770 .unwrap();
18771 let snap = e.snapshot();
18772 let e2 = Engine::restore_envelope(&snap).unwrap();
18773 assert_eq!(e2.publications().len(), 2);
18774 assert!(e2.publications().contains("pub_a"));
18775 assert!(e2.publications().contains("pub_b"));
18776 }
18777
18778 #[test]
18779 fn create_publication_allowed_inside_transaction() {
18780 let mut e = Engine::new();
18784 e.execute("BEGIN").unwrap();
18785 e.execute("CREATE PUBLICATION pub_a").unwrap();
18786 e.execute("COMMIT").unwrap();
18787 assert!(e.publications().contains("pub_a"));
18788 }
18789
18790 #[test]
18793 fn create_publication_for_table_list_lands_with_scope() {
18794 let mut e = Engine::new();
18795 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18796 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18797 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18798 .unwrap();
18799 let scope = e.publications().get("pub_a").cloned();
18800 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18801 panic!("expected ForTables scope, got {scope:?}")
18802 };
18803 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18804 }
18805
18806 #[test]
18807 fn create_publication_all_tables_except_lands_with_scope() {
18808 let mut e = Engine::new();
18809 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18810 .unwrap();
18811 let scope = e.publications().get("pub_a").cloned();
18812 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18813 panic!("expected AllTablesExcept scope, got {scope:?}")
18814 };
18815 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18816 }
18817
18818 #[test]
18819 fn show_publications_empty_returns_zero_rows() {
18820 let e = Engine::new();
18821 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18822 let QueryResult::Rows { rows, columns } = r else {
18823 panic!()
18824 };
18825 assert!(rows.is_empty());
18826 assert_eq!(columns.len(), 3);
18827 assert_eq!(columns[0].name, "name");
18828 assert_eq!(columns[1].name, "scope");
18829 assert_eq!(columns[2].name, "table_count");
18830 }
18831
18832 #[test]
18833 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18834 let mut e = Engine::new();
18835 e.execute("CREATE PUBLICATION z_pub").unwrap();
18836 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18837 .unwrap();
18838 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18839 .unwrap();
18840 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18841 let QueryResult::Rows { rows, .. } = r else {
18842 panic!()
18843 };
18844 assert_eq!(rows.len(), 3);
18845 let names: Vec<&str> = rows
18847 .iter()
18848 .map(|r| {
18849 if let Value::Text(s) = &r.values[0] {
18850 s.as_str()
18851 } else {
18852 panic!()
18853 }
18854 })
18855 .collect();
18856 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18857 match &rows[0].values[1] {
18859 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18860 other => panic!("expected Text, got {other:?}"),
18861 }
18862 assert_eq!(rows[0].values[2], Value::Int(2));
18863 match &rows[1].values[1] {
18865 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18866 other => panic!("expected Text, got {other:?}"),
18867 }
18868 assert_eq!(rows[1].values[2], Value::Int(1));
18869 match &rows[2].values[1] {
18871 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18872 other => panic!("expected Text, got {other:?}"),
18873 }
18874 assert_eq!(rows[2].values[2], Value::Null);
18875 }
18876
18877 #[test]
18878 fn for_list_scopes_persist_across_snapshot() {
18879 let mut e = Engine::new();
18882 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18883 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18884 .unwrap();
18885 let snap = e.snapshot();
18886 let e2 = Engine::restore_envelope(&snap).unwrap();
18887 assert_eq!(e2.publications().len(), 2);
18888 let p1 = e2.publications().get("p1").cloned();
18889 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18890 panic!("p1 scope lost: {p1:?}")
18891 };
18892 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18893 let p2 = e2.publications().get("p2").cloned();
18894 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18895 panic!("p2 scope lost: {p2:?}")
18896 };
18897 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18898 }
18899
18900 #[test]
18903 fn create_subscription_lands_in_catalog_with_defaults() {
18904 let mut e = Engine::new();
18905 e.execute(
18906 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18907 )
18908 .unwrap();
18909 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18910 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18911 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18912 assert!(s.enabled);
18913 assert_eq!(s.last_received_pos, 0);
18914 }
18915
18916 #[test]
18917 fn create_subscription_duplicate_name_errors() {
18918 let mut e = Engine::new();
18919 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18920 .unwrap();
18921 let err = e
18922 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18923 .unwrap_err();
18924 assert!(
18925 alloc::format!("{err:?}").contains("DuplicateName"),
18926 "got {err:?}"
18927 );
18928 }
18929
18930 #[test]
18931 fn drop_subscription_silent_when_absent() {
18932 let mut e = Engine::new();
18933 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18934 match r {
18935 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18936 other => panic!("expected CommandOk, got {other:?}"),
18937 }
18938 }
18939
18940 #[test]
18941 fn subscription_advance_updates_last_pos_monotone() {
18942 let mut e = Engine::new();
18943 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18944 .unwrap();
18945 assert!(e.subscription_advance("s", 100));
18946 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18947 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18949 assert!(e.subscription_advance("s", 200));
18950 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18951 assert!(!e.subscription_advance("missing", 1));
18952 }
18953
18954 #[test]
18955 fn show_subscriptions_returns_rows_ordered_by_name() {
18956 let mut e = Engine::new();
18957 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18958 .unwrap();
18959 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18960 .unwrap();
18961 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18962 let QueryResult::Rows { rows, columns } = r else {
18963 panic!()
18964 };
18965 assert_eq!(rows.len(), 2);
18966 assert_eq!(columns.len(), 5);
18967 assert_eq!(columns[0].name, "name");
18968 assert_eq!(columns[4].name, "last_received_pos");
18969 let names: Vec<&str> = rows
18971 .iter()
18972 .map(|r| {
18973 if let Value::Text(s) = &r.values[0] {
18974 s.as_str()
18975 } else {
18976 panic!()
18977 }
18978 })
18979 .collect();
18980 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18981 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18983 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18984 assert_eq!(rows[0].values[3], Value::Bool(true));
18985 assert_eq!(rows[0].values[4], Value::BigInt(0));
18986 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18988 }
18989
18990 #[test]
18991 fn subscriptions_persist_across_snapshot_envelope_v4() {
18992 let mut e = Engine::new();
18993 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18994 .unwrap();
18995 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18996 .unwrap();
18997 e.subscription_advance("s2", 42);
18998 let snap = e.snapshot();
18999 let e2 = Engine::restore_envelope(&snap).unwrap();
19000 assert_eq!(e2.subscriptions().len(), 2);
19001 let s1 = e2.subscriptions().get("s1").unwrap();
19002 assert_eq!(s1.conn_str, "h=A");
19003 assert_eq!(
19004 s1.publications,
19005 alloc::vec!["p1".to_string(), "p2".to_string()]
19006 );
19007 assert_eq!(s1.last_received_pos, 0);
19008 let s2 = e2.subscriptions().get("s2").unwrap();
19009 assert_eq!(s2.last_received_pos, 42);
19010 }
19011
19012 #[test]
19013 fn v3_envelope_loads_with_empty_subscriptions() {
19014 let mut e = Engine::new();
19018 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19019 let catalog = e.catalog.serialize();
19020 let users = crate::users::serialize_users(&e.users);
19021 let pubs = e.publications.serialize();
19022 let mut buf = Vec::new();
19023 buf.extend_from_slice(b"SPGENV01");
19024 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19026 buf.extend_from_slice(&catalog);
19027 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19028 buf.extend_from_slice(&users);
19029 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19030 buf.extend_from_slice(&pubs);
19031 let crc = spg_crypto::crc32::crc32(&buf);
19032 buf.extend_from_slice(&crc.to_le_bytes());
19033
19034 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19035 assert!(e2.subscriptions().is_empty());
19036 assert!(e2.publications().contains("pub_legacy"));
19037 }
19038
19039 #[test]
19040 fn create_subscription_allowed_inside_transaction() {
19041 let mut e = Engine::new();
19042 e.execute("BEGIN").unwrap();
19043 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19044 .unwrap();
19045 e.execute("COMMIT").unwrap();
19046 assert!(e.subscriptions().contains("s"));
19047 }
19048
19049 #[test]
19051 fn analyze_populates_histogram_bounds() {
19052 let mut e = Engine::new();
19053 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19054 .unwrap();
19055 for i in 0..50 {
19056 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19057 .unwrap();
19058 }
19059 e.execute("ANALYZE t").unwrap();
19060 let stats = e.statistics();
19061 let id_stats = stats.get("t", "id").unwrap();
19062 assert!(id_stats.histogram_bounds.len() >= 2);
19063 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19064 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19065 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19066 assert_eq!(id_stats.n_distinct, 50);
19067 }
19068
19069 #[test]
19070 fn reanalyze_overwrites_prior_stats() {
19071 let mut e = Engine::new();
19072 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19073 for i in 0..10 {
19074 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19075 .unwrap();
19076 }
19077 e.execute("ANALYZE t").unwrap();
19078 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19079 assert_eq!(n1, 10);
19080 for i in 10..30 {
19081 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19082 .unwrap();
19083 }
19084 e.execute("ANALYZE t").unwrap();
19085 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19086 assert_eq!(n2, 30);
19087 }
19088
19089 #[test]
19090 fn analyze_unknown_table_errors() {
19091 let mut e = Engine::new();
19092 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19093 assert!(matches!(
19094 err,
19095 EngineError::Storage(StorageError::TableNotFound { .. })
19096 ));
19097 }
19098
19099 #[test]
19100 fn bare_analyze_covers_all_user_tables() {
19101 let mut e = Engine::new();
19102 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19103 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19104 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19105 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19106 let r = e.execute("ANALYZE").unwrap();
19107 match r {
19108 QueryResult::CommandOk {
19109 affected,
19110 modified_catalog,
19111 } => {
19112 assert_eq!(affected, 2);
19113 assert!(modified_catalog);
19114 }
19115 other => panic!("expected CommandOk, got {other:?}"),
19116 }
19117 assert!(e.statistics().get("t1", "id").is_some());
19118 assert!(e.statistics().get("t2", "name").is_some());
19119 }
19120
19121 #[test]
19122 fn select_from_spg_statistic_returns_rows_per_column() {
19123 let mut e = Engine::new();
19124 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19125 .unwrap();
19126 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19127 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19128 e.execute("ANALYZE t").unwrap();
19129 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19130 let QueryResult::Rows { rows, columns } = r else {
19131 panic!()
19132 };
19133 assert_eq!(columns.len(), 6);
19135 assert_eq!(columns[0].name, "table_name");
19136 assert_eq!(columns[4].name, "histogram_bounds");
19137 assert_eq!(columns[5].name, "cold_row_count");
19138 assert_eq!(rows.len(), 2, "one row per column of t");
19139 match (&rows[0].values[0], &rows[0].values[1]) {
19141 (Value::Text(t), Value::Text(c)) => {
19142 assert_eq!(t, "t");
19143 assert_eq!(c, "id");
19145 }
19146 _ => panic!(),
19147 }
19148 }
19149
19150 #[test]
19151 fn analyze_skips_vector_columns() {
19152 let mut e = Engine::new();
19155 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19156 .unwrap();
19157 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19158 e.execute("ANALYZE t").unwrap();
19159 assert!(e.statistics().get("t", "id").is_some());
19160 assert!(e.statistics().get("t", "v").is_none());
19161 }
19162
19163 #[test]
19164 fn statistics_persist_across_envelope_v5_round_trip() {
19165 let mut e = Engine::new();
19166 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19167 for i in 0..20 {
19168 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19169 .unwrap();
19170 }
19171 e.execute("ANALYZE").unwrap();
19172 let snap = e.snapshot();
19173 let e2 = Engine::restore_envelope(&snap).unwrap();
19174 let s = e2.statistics().get("t", "id").unwrap();
19175 assert_eq!(s.n_distinct, 20);
19176 }
19177
19178 #[test]
19181 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19182 let mut e = Engine::new();
19186 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19187 for i in 0..9 {
19188 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19189 .unwrap();
19190 }
19191 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19192 e.execute("INSERT INTO t VALUES (9)").unwrap();
19193 let needs = e.tables_needing_analyze();
19194 assert_eq!(needs, alloc::vec!["t".to_string()]);
19195 }
19196
19197 #[test]
19198 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19199 let mut e = Engine::new();
19205 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19206 for i in 0..1000 {
19207 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19208 .unwrap();
19209 }
19210 e.execute("ANALYZE t").unwrap();
19211 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19212 for i in 1000..1050 {
19213 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19214 .unwrap();
19215 }
19216 assert!(
19217 e.tables_needing_analyze().is_empty(),
19218 "50 inserts < threshold of ~105"
19219 );
19220 for i in 1050..1200 {
19221 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19222 .unwrap();
19223 }
19224 assert_eq!(
19225 e.tables_needing_analyze(),
19226 alloc::vec!["t".to_string()],
19227 "200 inserts > 0.1 × 1200 threshold"
19228 );
19229 }
19230
19231 #[test]
19232 fn auto_analyze_threshold_resets_after_analyze() {
19233 let mut e = Engine::new();
19234 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19235 for i in 0..200 {
19236 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19237 .unwrap();
19238 }
19239 assert!(!e.tables_needing_analyze().is_empty());
19240 e.execute("ANALYZE").unwrap();
19241 assert!(
19242 e.tables_needing_analyze().is_empty(),
19243 "ANALYZE must reset the counter"
19244 );
19245 }
19246
19247 #[test]
19248 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19249 let mut e = Engine::new();
19250 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19251 .unwrap();
19252 for i in 0..50 {
19253 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19254 .unwrap();
19255 }
19256 e.execute("ANALYZE t").unwrap();
19257 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19260 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19261 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19262 }
19263
19264 #[test]
19265 fn v4_envelope_loads_with_empty_statistics() {
19266 let mut e = Engine::new();
19270 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19271 .unwrap();
19272 let catalog = e.catalog.serialize();
19273 let users = crate::users::serialize_users(&e.users);
19274 let pubs = e.publications.serialize();
19275 let subs = e.subscriptions.serialize();
19276 let mut buf = Vec::new();
19277 buf.extend_from_slice(b"SPGENV01");
19278 buf.push(4u8);
19279 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19280 buf.extend_from_slice(&catalog);
19281 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19282 buf.extend_from_slice(&users);
19283 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19284 buf.extend_from_slice(&pubs);
19285 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19286 buf.extend_from_slice(&subs);
19287 let crc = spg_crypto::crc32::crc32(&buf);
19288 buf.extend_from_slice(&crc.to_le_bytes());
19289 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19290 assert!(e2.statistics().is_empty());
19291 }
19292
19293 #[test]
19294 fn v1_v2_envelope_loads_with_empty_publications() {
19295 let mut e = Engine::new();
19302 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19305 .unwrap();
19306
19307 let catalog = e.catalog.serialize();
19309 let users = crate::users::serialize_users(&e.users);
19310 let mut buf = Vec::new();
19311 buf.extend_from_slice(b"SPGENV01");
19312 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19314 buf.extend_from_slice(&catalog);
19315 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19316 buf.extend_from_slice(&users);
19317 let crc = spg_crypto::crc32::crc32(&buf);
19318 buf.extend_from_slice(&crc.to_le_bytes());
19319
19320 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19321 assert!(e2.publications().is_empty());
19322 }
19323}