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, seg.long_strings())
7160 .map_err(EngineError::Storage)?;
7161 if let Some(where_expr) = &stmt.where_ {
7162 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
7163 if !matches!(cond, Value::Bool(true)) {
7164 continue;
7165 }
7166 }
7167 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7169 out_rows.push(projected);
7170 if let Some(rem) = limit_remaining.as_mut() {
7171 if *rem == 0 {
7172 out_rows.pop();
7173 break;
7174 }
7175 *rem -= 1;
7176 }
7177 }
7178 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7180 Ok(QueryResult::Rows {
7181 columns,
7182 rows: out_rows,
7183 })
7184 }
7185
7186 fn eval_expr_simple(
7191 &self,
7192 expr: &Expr,
7193 row: &Row,
7194 ctx: &EvalContext,
7195 ) -> Result<Value, EngineError> {
7196 let cancel = CancelToken::none();
7197 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7198 }
7199
7200 fn build_returning_rows(
7207 &self,
7208 table_name: &str,
7209 items: &[SelectItem],
7210 mutated_rows: Vec<Vec<Value>>,
7211 ) -> Result<QueryResult, EngineError> {
7212 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7213 EngineError::Storage(StorageError::TableNotFound {
7214 name: table_name.into(),
7215 })
7216 })?;
7217 let schema_cols = table.schema().columns.clone();
7218 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7219 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7220 for values in mutated_rows {
7221 let row = Row::new(values);
7222 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7223 out_rows.push(projected);
7224 }
7225 Ok(QueryResult::Rows {
7226 columns,
7227 rows: out_rows,
7228 })
7229 }
7230
7231 fn project_row_simple(
7235 &self,
7236 row: &Row,
7237 items: &[SelectItem],
7238 schema_cols: &[ColumnSchema],
7239 alias: &str,
7240 ) -> Result<Row, EngineError> {
7241 let ctx = EvalContext::new(schema_cols, Some(alias));
7242 let cancel = CancelToken::none();
7243 let mut out_vals = Vec::new();
7244 for item in items {
7245 match item {
7246 SelectItem::Wildcard => {
7247 out_vals.extend(row.values.iter().cloned());
7248 }
7249 SelectItem::Expr { expr, .. } => {
7250 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7251 out_vals.push(v);
7252 }
7253 }
7254 }
7255 Ok(Row::new(out_vals))
7256 }
7257
7258 fn derive_output_columns(
7263 &self,
7264 items: &[SelectItem],
7265 schema_cols: &[ColumnSchema],
7266 _alias: &str,
7267 ) -> Vec<ColumnSchema> {
7268 let mut out = Vec::new();
7269 for item in items {
7270 match item {
7271 SelectItem::Wildcard => {
7272 out.extend(schema_cols.iter().cloned());
7273 }
7274 SelectItem::Expr { expr, alias } => {
7275 if let Expr::Column(col) = expr
7281 && let Some(sc) = schema_cols.iter().find(|c| c.name == col.name)
7282 {
7283 let name = alias.clone().unwrap_or_else(|| sc.name.clone());
7284 out.push(ColumnSchema::new(name, sc.ty, sc.nullable));
7285 continue;
7286 }
7287 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7288 out.push(ColumnSchema::new(name, DataType::Text, true));
7291 }
7292 }
7293 }
7294 out
7295 }
7296
7297 fn exec_select_cancel(
7298 &self,
7299 stmt: &SelectStatement,
7300 cancel: CancelToken<'_>,
7301 ) -> Result<QueryResult, EngineError> {
7302 cancel.check()?;
7303 if !self.active_catalog().views().is_empty() {
7310 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7311 return self.exec_select_cancel(&rewritten, cancel);
7312 }
7313 }
7314 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7323 return self.exec_select_with_meta_views(stmt, cancel);
7324 }
7325 if let Some(from) = &stmt.from
7334 && let Some(seg_id) = from.primary.as_of_segment
7335 {
7336 return self.exec_select_as_of_segment(stmt, from, seg_id);
7337 }
7338 if let Some(from) = &stmt.from
7342 && from.joins.is_empty()
7343 && stmt.where_.is_none()
7344 && stmt.group_by.is_none()
7345 && stmt.having.is_none()
7346 && stmt.unions.is_empty()
7347 && stmt.order_by.is_empty()
7348 && stmt.limit.is_none()
7349 && stmt.offset.is_none()
7350 && !stmt.distinct
7351 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7352 {
7353 let lower = from.primary.name.to_ascii_lowercase();
7354 match lower.as_str() {
7355 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7356 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7358 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7359 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7360 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7361 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7362 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7363 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7364 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7365 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7366 _ => {}
7367 }
7368 }
7369 if !stmt.ctes.is_empty() {
7377 return self.exec_with_ctes(stmt, cancel);
7378 }
7379 let mut stmt_owned;
7386 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7387 stmt_owned = stmt.clone();
7388 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7389 &stmt_owned
7390 } else {
7391 stmt
7392 };
7393 if stmt_ref.unions.is_empty() {
7394 return self.exec_bare_select_cancel(stmt_ref, cancel);
7395 }
7396 let mut head = stmt_ref.clone();
7401 head.unions = Vec::new();
7402 head.order_by = Vec::new();
7403 head.limit = None;
7404 let QueryResult::Rows { columns, mut rows } =
7405 self.exec_bare_select_cancel(&head, cancel)?
7406 else {
7407 unreachable!("bare SELECT cannot return CommandOk")
7408 };
7409 for (kind, peer) in &stmt_ref.unions {
7410 let QueryResult::Rows {
7411 columns: peer_cols,
7412 rows: peer_rows,
7413 } = self.exec_bare_select_cancel(peer, cancel)?
7414 else {
7415 unreachable!("bare SELECT cannot return CommandOk")
7416 };
7417 if peer_cols.len() != columns.len() {
7418 return Err(EngineError::Unsupported(alloc::format!(
7419 "UNION arity mismatch: head has {} columns, peer has {}",
7420 columns.len(),
7421 peer_cols.len()
7422 )));
7423 }
7424 rows.extend(peer_rows);
7425 if matches!(kind, UnionKind::Distinct) {
7426 rows = dedup_rows(rows);
7427 }
7428 }
7429 if !stmt.order_by.is_empty() {
7432 let synth_ctx = EvalContext::new(&columns, None);
7433 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7434 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7435 for r in rows {
7436 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7437 tagged.push((keys, r));
7438 }
7439 sort_by_keys(&mut tagged, &descs);
7440 rows = tagged.into_iter().map(|(_, r)| r).collect();
7441 }
7442 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7443 Ok(QueryResult::Rows { columns, rows })
7444 }
7445
7446 #[allow(clippy::too_many_lines)]
7447 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7455 &self,
7456 stmt: &SelectStatement,
7457 primary: &TableRef,
7458 cancel: CancelToken<'_>,
7459 ) -> Result<QueryResult, EngineError> {
7460 let expr = primary
7461 .unnest_expr
7462 .as_deref()
7463 .expect("caller guards unnest_expr.is_some()");
7464 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7467 let ctx = EvalContext::new(&empty_schema, None);
7468 let dummy_row = Row::new(alloc::vec::Vec::new());
7469 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7472 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7473 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7474 Value::TextArray(items) => {
7475 let rows = items
7476 .into_iter()
7477 .map(|item| {
7478 Row::new(alloc::vec![match item {
7479 Some(s) => Value::Text(s),
7480 None => Value::Null,
7481 }])
7482 })
7483 .collect();
7484 (DataType::Text, rows)
7485 }
7486 Value::IntArray(items) => {
7487 let rows = items
7488 .into_iter()
7489 .map(|item| {
7490 Row::new(alloc::vec![match item {
7491 Some(n) => Value::Int(n),
7492 None => Value::Null,
7493 }])
7494 })
7495 .collect();
7496 (DataType::Int, rows)
7497 }
7498 Value::BigIntArray(items) => {
7499 let rows = items
7500 .into_iter()
7501 .map(|item| {
7502 Row::new(alloc::vec![match item {
7503 Some(n) => Value::BigInt(n),
7504 None => Value::Null,
7505 }])
7506 })
7507 .collect();
7508 (DataType::BigInt, rows)
7509 }
7510 other => {
7511 return Err(EngineError::Unsupported(alloc::format!(
7512 "unnest() expects an array argument, got {:?}",
7513 other.data_type()
7514 )));
7515 }
7516 };
7517 let alias = primary
7518 .alias
7519 .clone()
7520 .unwrap_or_else(|| "unnest".to_string());
7521 let col_name = primary
7527 .unnest_column_aliases
7528 .first()
7529 .cloned()
7530 .unwrap_or_else(|| alias.clone());
7531 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7532 let schema_cols = alloc::vec![col_schema.clone()];
7533 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7534 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7536 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7537 for row in rows {
7538 cancel.check()?;
7539 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7540 if matches!(v, Value::Bool(true)) {
7541 out.push(row);
7542 }
7543 }
7544 out
7545 } else {
7546 rows
7547 };
7548 if aggregate::uses_aggregate(stmt) {
7554 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7555 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7556 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7557 return Ok(QueryResult::Rows {
7558 columns: agg.columns,
7559 rows: agg.rows,
7560 });
7561 }
7562 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7564 let mut projected_rows: alloc::vec::Vec<Row> =
7565 alloc::vec::Vec::with_capacity(filtered.len());
7566 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7575 if let Some(srf_idx) = srf_position {
7576 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7577 .expect("checked by is_top_level_unnest above");
7578 for row in &filtered {
7579 let arr_val =
7580 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7581 let elements = array_value_to_elements(&arr_val)?;
7582 for elem in elements {
7586 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7587 for (i, p) in projection.iter().enumerate() {
7588 if i == srf_idx {
7589 vals.push(elem.clone());
7590 } else {
7591 vals.push(
7592 eval::eval_expr(&p.expr, row, &scan_ctx)
7593 .map_err(EngineError::Eval)?,
7594 );
7595 }
7596 }
7597 projected_rows.push(Row::new(vals));
7598 }
7599 }
7600 } else {
7601 for row in &filtered {
7602 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7603 for p in &projection {
7604 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7605 }
7606 projected_rows.push(Row::new(vals));
7607 }
7608 }
7609 let columns: alloc::vec::Vec<ColumnSchema> = projection
7612 .iter()
7613 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7614 .collect();
7615 if !stmt.order_by.is_empty() {
7618 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7619 .iter()
7620 .enumerate()
7621 .map(|(i, r)| -> Result<_, EngineError> {
7622 let keys: Result<Vec<Value>, EngineError> = stmt
7623 .order_by
7624 .iter()
7625 .map(|ob| {
7626 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7627 })
7628 .collect();
7629 Ok((i, keys?))
7630 })
7631 .collect::<Result<_, _>>()?;
7632 indexed.sort_by(|a, b| {
7633 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7634 let mut cmp = value_cmp(ka, kb);
7635 if stmt.order_by[idx].desc {
7636 cmp = cmp.reverse();
7637 }
7638 if cmp != core::cmp::Ordering::Equal {
7639 return cmp;
7640 }
7641 }
7642 core::cmp::Ordering::Equal
7643 });
7644 projected_rows = indexed
7645 .into_iter()
7646 .map(|(i, _)| projected_rows[i].clone())
7647 .collect();
7648 }
7649 if let Some(offset) = stmt.offset_literal() {
7651 let off = (offset as usize).min(projected_rows.len());
7652 projected_rows.drain(..off);
7653 }
7654 if let Some(limit) = stmt.limit_literal() {
7655 projected_rows.truncate(limit as usize);
7656 }
7657 Ok(QueryResult::Rows {
7658 columns,
7659 rows: projected_rows,
7660 })
7661 }
7662
7663 fn exec_select_generate_series(
7674 &self,
7675 stmt: &SelectStatement,
7676 primary: &TableRef,
7677 cancel: CancelToken<'_>,
7678 ) -> Result<QueryResult, EngineError> {
7679 let args = primary
7680 .generate_series_args
7681 .as_ref()
7682 .expect("caller guards generate_series_args.is_some()");
7683 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7684 let ctx = EvalContext::new(&empty_schema, None);
7685 let dummy_row = Row::new(alloc::vec::Vec::new());
7686 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7687 for a in args {
7688 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7689 }
7690 let (elem_dtype, rows) = match arg_values.as_slice() {
7694 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7695 let interval_step = match step {
7696 Value::Interval { .. } => step.clone(),
7697 other => {
7698 return Err(EngineError::Unsupported(alloc::format!(
7699 "generate_series(timestamp, timestamp, …): \
7700 step must be INTERVAL, got {:?}",
7701 other.data_type()
7702 )));
7703 }
7704 };
7705 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7706 (DataType::Timestamp, rows)
7707 }
7708 [start, stop, step]
7709 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7710 {
7711 let s = value_to_i64(start);
7712 let e = value_to_i64(stop);
7713 let st = value_to_i64(step);
7714 let rows = generate_series_integers(s, e, st, &cancel)?;
7715 (DataType::BigInt, rows)
7716 }
7717 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7718 let s = value_to_i64(start);
7719 let e = value_to_i64(stop);
7720 let rows = generate_series_integers(s, e, 1, &cancel)?;
7721 (DataType::BigInt, rows)
7722 }
7723 _ => {
7724 return Err(EngineError::Unsupported(alloc::format!(
7725 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7726 argument shapes; got {:?}",
7727 arg_values
7728 .iter()
7729 .map(|v| v.data_type())
7730 .collect::<alloc::vec::Vec<_>>()
7731 )));
7732 }
7733 };
7734 let alias = primary
7735 .alias
7736 .clone()
7737 .unwrap_or_else(|| "generate_series".to_string());
7738 let col_name = alias.clone();
7739 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7740 let schema_cols = alloc::vec![col_schema.clone()];
7741 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7742 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7744 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7745 for row in rows {
7746 cancel.check()?;
7747 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7748 if matches!(v, Value::Bool(true)) {
7749 out.push(row);
7750 }
7751 }
7752 out
7753 } else {
7754 rows
7755 };
7756 if aggregate::uses_aggregate(stmt) {
7766 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7767 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7768 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7769 return Ok(QueryResult::Rows {
7770 columns: agg.columns,
7771 rows: agg.rows,
7772 });
7773 }
7774 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7776 let mut projected_rows: alloc::vec::Vec<Row> =
7777 alloc::vec::Vec::with_capacity(filtered.len());
7778 for row in &filtered {
7779 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7780 for p in &projection {
7781 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7782 }
7783 projected_rows.push(Row::new(vals));
7784 }
7785 let columns: alloc::vec::Vec<ColumnSchema> = projection
7786 .iter()
7787 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7788 .collect();
7789 if !stmt.order_by.is_empty() {
7791 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7792 .iter()
7793 .enumerate()
7794 .map(|(i, r)| -> Result<_, EngineError> {
7795 let keys: Result<Vec<Value>, EngineError> = stmt
7796 .order_by
7797 .iter()
7798 .map(|ob| {
7799 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7800 })
7801 .collect();
7802 Ok((i, keys?))
7803 })
7804 .collect::<Result<_, _>>()?;
7805 indexed.sort_by(|a, b| {
7806 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7807 let mut cmp = value_cmp(ka, kb);
7808 if stmt.order_by[idx].desc {
7809 cmp = cmp.reverse();
7810 }
7811 if cmp != core::cmp::Ordering::Equal {
7812 return cmp;
7813 }
7814 }
7815 core::cmp::Ordering::Equal
7816 });
7817 projected_rows = indexed
7818 .into_iter()
7819 .map(|(i, _)| projected_rows[i].clone())
7820 .collect();
7821 }
7822 if let Some(offset) = stmt.offset_literal() {
7823 let off = (offset as usize).min(projected_rows.len());
7824 projected_rows.drain(..off);
7825 }
7826 if let Some(limit) = stmt.limit_literal() {
7827 projected_rows.truncate(limit as usize);
7828 }
7829 Ok(QueryResult::Rows {
7830 columns,
7831 rows: projected_rows,
7832 })
7833 }
7834
7835 fn exec_bare_select_cancel(
7836 &self,
7837 stmt: &SelectStatement,
7838 cancel: CancelToken<'_>,
7839 ) -> Result<QueryResult, EngineError> {
7840 check_with_ties_requires_order_by(stmt)?;
7845 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7853 return self.exec_select_with_meta_views(stmt, cancel);
7854 }
7855 if select_has_window(stmt) {
7860 return self.exec_select_with_window(stmt, cancel);
7861 }
7862 let Some(from) = &stmt.from else {
7867 let empty_schema: Vec<ColumnSchema> = Vec::new();
7868 let ctx = self.ev_ctx(&empty_schema, None);
7869 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7870 let dummy_row = Row::new(Vec::new());
7871 let mut values = Vec::with_capacity(projection.len());
7872 for p in &projection {
7873 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7874 }
7875 let columns: Vec<ColumnSchema> = projection
7876 .into_iter()
7877 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7878 .collect();
7879 return Ok(QueryResult::Rows {
7880 columns,
7881 rows: alloc::vec![Row::new(values)],
7882 });
7883 };
7884 if !from.joins.is_empty() {
7888 return self.exec_joined_select(stmt, from);
7889 }
7890 if from.primary.unnest_expr.is_some() {
7897 return self.exec_select_unnest(stmt, &from.primary, cancel);
7898 }
7899 if from.primary.generate_series_args.is_some() {
7905 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7906 }
7907 let primary = &from.primary;
7908 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7909 StorageError::TableNotFound {
7910 name: primary.name.clone(),
7911 }
7912 })?;
7913 let schema_cols = &table.schema().columns;
7914 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7917 let ctx = self.ev_ctx(schema_cols, Some(alias));
7918
7919 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7924 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7925 }
7926
7927 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7935 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7938 .or_else(|| {
7939 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7945 })
7946 .or_else(|| {
7947 try_trgm_seek(w, schema_cols, table, alias)
7953 })
7954 });
7955
7956 if aggregate::uses_aggregate(stmt) {
7959 let mut filtered: Vec<&Row> = Vec::new();
7960 let mut memo = memoize::MemoizeCache::new();
7964 if let Some(rows) = &indexed_rows {
7965 for cow in rows {
7966 let row = cow.as_ref();
7967 if let Some(where_expr) = &stmt.where_ {
7968 let cond = self.eval_expr_with_correlated(
7969 where_expr,
7970 row,
7971 &ctx,
7972 cancel,
7973 Some(&mut memo),
7974 )?;
7975 if !matches!(cond, Value::Bool(true)) {
7976 continue;
7977 }
7978 }
7979 filtered.push(row);
7980 }
7981 } else {
7982 for i in 0..table.row_count() {
7983 let row = &table.rows()[i];
7984 if let Some(where_expr) = &stmt.where_ {
7985 let cond = self.eval_expr_with_correlated(
7986 where_expr,
7987 row,
7988 &ctx,
7989 cancel,
7990 Some(&mut memo),
7991 )?;
7992 if !matches!(cond, Value::Bool(true)) {
7993 continue;
7994 }
7995 }
7996 filtered.push(row);
7997 }
7998 }
7999 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
8000 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8001 return Ok(QueryResult::Rows {
8002 columns: agg.columns,
8003 rows: agg.rows,
8004 });
8005 }
8006
8007 let projection = build_projection(&stmt.items, schema_cols, alias)?;
8008 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
8016
8017 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8020 let mut memo = memoize::MemoizeCache::new();
8022 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
8025 if loop_idx.is_multiple_of(256) {
8026 cancel.check()?;
8027 }
8028 if let Some(where_expr) = &stmt.where_ {
8029 let cond =
8030 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
8031 if !matches!(cond, Value::Bool(true)) {
8032 return Ok(());
8033 }
8034 }
8035 let order_keys = if stmt.order_by.is_empty() {
8036 Vec::new()
8037 } else {
8038 build_order_keys(&stmt.order_by, row, &ctx)?
8039 };
8040 if let Some(srf_idx) = srf_position {
8041 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
8042 .expect("checked by is_top_level_unnest above");
8043 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
8044 let elements = array_value_to_elements(&arr_val)?;
8045 for elem in elements {
8046 let mut values = Vec::with_capacity(projection.len());
8047 for (i, p) in projection.iter().enumerate() {
8048 if i == srf_idx {
8049 values.push(elem.clone());
8050 } else {
8051 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8052 }
8053 }
8054 tagged.push((order_keys.clone(), Row::new(values)));
8055 }
8056 } else {
8057 let mut values = Vec::with_capacity(projection.len());
8058 for p in &projection {
8059 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8060 }
8061 tagged.push((order_keys, Row::new(values)));
8062 }
8063 Ok(())
8064 };
8065 if let Some(rows) = &indexed_rows {
8066 for (loop_idx, cow) in rows.iter().enumerate() {
8067 process_row(cow.as_ref(), loop_idx)?;
8068 }
8069 } else {
8070 for i in 0..table.row_count() {
8071 process_row(&table.rows()[i], i)?;
8072 }
8073 }
8074
8075 if !stmt.order_by.is_empty() {
8076 let keep = if stmt.distinct || stmt.limit_with_ties {
8084 None
8085 } else {
8086 stmt.limit_literal()
8087 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8088 };
8089 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8090 partial_sort_tagged(&mut tagged, keep, &descs);
8091 }
8092
8093 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
8103 apply_offset_and_limit_tagged(
8104 &mut tagged,
8105 stmt.offset_literal(),
8106 stmt.limit_literal(),
8107 true,
8108 );
8109 tagged.into_iter().map(|(_, r)| r).collect()
8110 } else {
8111 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8112 if stmt.distinct {
8113 output_rows = dedup_rows(output_rows);
8114 }
8115 apply_offset_and_limit(
8116 &mut output_rows,
8117 stmt.offset_literal(),
8118 stmt.limit_literal(),
8119 );
8120 output_rows
8121 };
8122
8123 let columns: Vec<ColumnSchema> = projection
8124 .into_iter()
8125 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8126 .collect();
8127
8128 Ok(QueryResult::Rows {
8129 columns,
8130 rows: output_rows,
8131 })
8132 }
8133
8134 #[allow(clippy::too_many_lines)]
8141 fn materialise_table_ref(
8149 &self,
8150 tref: &TableRef,
8151 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8152 if let Some(expr) = tref.unnest_expr.as_deref() {
8153 let empty_schema: Vec<ColumnSchema> = Vec::new();
8154 let ctx = EvalContext::new(&empty_schema, None);
8155 let dummy_row = Row::new(Vec::new());
8156 let (elem_dtype, rows) =
8157 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8158 Value::Null => (DataType::Text, Vec::new()),
8159 Value::TextArray(items) => (
8160 DataType::Text,
8161 items
8162 .into_iter()
8163 .map(|item| {
8164 Row::new(alloc::vec![match item {
8165 Some(s) => Value::Text(s),
8166 None => Value::Null,
8167 }])
8168 })
8169 .collect(),
8170 ),
8171 Value::IntArray(items) => (
8172 DataType::Int,
8173 items
8174 .into_iter()
8175 .map(|item| {
8176 Row::new(alloc::vec![match item {
8177 Some(n) => Value::Int(n),
8178 None => Value::Null,
8179 }])
8180 })
8181 .collect(),
8182 ),
8183 Value::BigIntArray(items) => (
8184 DataType::BigInt,
8185 items
8186 .into_iter()
8187 .map(|item| {
8188 Row::new(alloc::vec![match item {
8189 Some(n) => Value::BigInt(n),
8190 None => Value::Null,
8191 }])
8192 })
8193 .collect(),
8194 ),
8195 other => {
8196 return Err(EngineError::Unsupported(alloc::format!(
8197 "unnest() expects an array argument, got {:?}",
8198 other.data_type()
8199 )));
8200 }
8201 };
8202 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8203 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8204 return Ok((
8205 rows,
8206 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8207 ));
8208 }
8209 let table =
8210 self.active_catalog()
8211 .get(&tref.name)
8212 .ok_or_else(|| StorageError::TableNotFound {
8213 name: tref.name.clone(),
8214 })?;
8215 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8216 let cols = table.schema().columns.clone();
8217 Ok((rows, cols))
8218 }
8219
8220 fn build_joined_filtered_rows(
8232 &self,
8233 from: &FromClause,
8234 where_: Option<&Expr>,
8235 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8236 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8237 let primary_alias = from
8238 .primary
8239 .alias
8240 .as_deref()
8241 .unwrap_or(from.primary.name.as_str())
8242 .to_string();
8243 #[allow(clippy::type_complexity)]
8250 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8251 for j in &from.joins {
8252 let a = j
8253 .table
8254 .alias
8255 .as_deref()
8256 .unwrap_or(j.table.name.as_str())
8257 .to_string();
8258 if let Some(inner_box) = &j.table.lateral_subquery {
8259 let schema = self.lateral_probe_schema(inner_box)?;
8264 joined.push(JoinedPeer {
8265 eager_rows: None,
8266 cols: schema,
8267 alias: a,
8268 kind: j.kind,
8269 on: j.on.as_ref(),
8270 lateral: Some(inner_box.as_ref()),
8271 });
8272 } else {
8273 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8274 joined.push(JoinedPeer {
8275 eager_rows: Some(rows),
8276 cols,
8277 alias: a,
8278 kind: j.kind,
8279 on: j.on.as_ref(),
8280 lateral: None,
8281 });
8282 }
8283 }
8284 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8285 for col in &primary_cols {
8286 combined_schema.push(ColumnSchema::new(
8287 alloc::format!("{primary_alias}.{}", col.name),
8288 col.ty,
8289 col.nullable,
8290 ));
8291 }
8292 for peer in &joined {
8293 for col in &peer.cols {
8294 combined_schema.push(ColumnSchema::new(
8295 alloc::format!("{}.{}", peer.alias, col.name),
8296 col.ty,
8297 col.nullable,
8298 ));
8299 }
8300 }
8301 let ctx = EvalContext::new(&combined_schema, None);
8302 let mut working: Vec<Row> = primary_rows;
8303 let mut consumed_cols = primary_cols.len();
8306 for peer in &joined {
8307 let right_arity = peer.cols.len();
8308 let mut next: Vec<Row> = Vec::new();
8309 for left in &working {
8310 let mut left_matched = false;
8311 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8312 Some(inner) => {
8313 let outer_schema = &combined_schema[..consumed_cols];
8317 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8318 alloc::borrow::Cow::Owned(rows)
8319 }
8320 None => {
8321 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8322 alloc::borrow::Cow::Borrowed(r.as_slice())
8323 }
8324 };
8325 for right in per_left_rrows.as_ref() {
8326 let mut combined_vals = left.values.clone();
8327 combined_vals.extend(right.values.iter().cloned());
8328 let combined = Row::new(combined_vals);
8329 let keep = if let Some(on_expr) = peer.on {
8330 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
8331 matches!(cond, Value::Bool(true))
8332 } else {
8333 true
8334 };
8335 if keep {
8336 next.push(combined);
8337 left_matched = true;
8338 }
8339 }
8340 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8341 let mut combined_vals = left.values.clone();
8342 for _ in 0..right_arity {
8343 combined_vals.push(Value::Null);
8344 }
8345 next.push(Row::new(combined_vals));
8346 }
8347 }
8348 working = next;
8349 consumed_cols += right_arity;
8350 debug_assert!(consumed_cols <= combined_schema.len());
8351 }
8352 let mut filtered: Vec<Row> = Vec::new();
8353 for row in working {
8354 if let Some(where_expr) = where_ {
8355 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
8356 if !matches!(cond, Value::Bool(true)) {
8357 continue;
8358 }
8359 }
8360 filtered.push(row);
8361 }
8362 Ok((combined_schema, filtered))
8363 }
8364
8365 fn lateral_probe_schema(
8371 &self,
8372 inner: &SelectStatement,
8373 ) -> Result<Vec<ColumnSchema>, EngineError> {
8374 match self.execute_readonly_select_for_lateral_probe(inner) {
8384 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8385 _ => {
8391 let mut out: Vec<ColumnSchema> = Vec::new();
8392 for (i, item) in inner.items.iter().enumerate() {
8393 let name = match item {
8394 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8395 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8396 SelectItem::Wildcard => alloc::format!("col{i}"),
8397 };
8398 out.push(ColumnSchema::new(name, DataType::Text, true));
8399 }
8400 Ok(out)
8401 }
8402 }
8403 }
8404
8405 fn execute_readonly_select_for_lateral_probe(
8411 &self,
8412 inner: &SelectStatement,
8413 ) -> Result<QueryResult, EngineError> {
8414 self.exec_bare_select_cancel(inner, CancelToken::none())
8415 }
8416
8417 fn materialise_lateral_for_outer(
8423 &self,
8424 inner: &SelectStatement,
8425 outer_schema: &[ColumnSchema],
8426 outer_row: &Row,
8427 ) -> Result<Vec<Row>, EngineError> {
8428 let mut substituted = inner.clone();
8429 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8430 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8431 match result {
8432 QueryResult::Rows { rows, .. } => Ok(rows),
8433 _ => Err(EngineError::Unsupported(
8434 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8435 )),
8436 }
8437 }
8438
8439 fn exec_joined_select(
8440 &self,
8441 stmt: &SelectStatement,
8442 from: &FromClause,
8443 ) -> Result<QueryResult, EngineError> {
8444 let (combined_schema, filtered) =
8452 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8453 let ctx = EvalContext::new(&combined_schema, None);
8454 if aggregate::uses_aggregate(stmt) {
8457 let refs: Vec<&Row> = filtered.iter().collect();
8458 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8459 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8460 return Ok(QueryResult::Rows {
8461 columns: agg.columns,
8462 rows: agg.rows,
8463 });
8464 }
8465
8466 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8467 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8468 for row in &filtered {
8469 let mut values = Vec::with_capacity(projection.len());
8470 for p in &projection {
8471 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8472 }
8473 let order_keys = if stmt.order_by.is_empty() {
8474 Vec::new()
8475 } else {
8476 build_order_keys(&stmt.order_by, row, &ctx)?
8477 };
8478 tagged.push((order_keys, Row::new(values)));
8479 }
8480 if !stmt.order_by.is_empty() {
8481 let keep = if stmt.distinct {
8482 None
8483 } else {
8484 stmt.limit_literal()
8485 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8486 };
8487 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8488 partial_sort_tagged(&mut tagged, keep, &descs);
8489 }
8490 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8491 if stmt.distinct {
8492 output_rows = dedup_rows(output_rows);
8493 }
8494 apply_offset_and_limit(
8495 &mut output_rows,
8496 stmt.offset_literal(),
8497 stmt.limit_literal(),
8498 );
8499 let columns: Vec<ColumnSchema> = projection
8500 .into_iter()
8501 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8502 .collect();
8503 Ok(QueryResult::Rows {
8504 columns,
8505 rows: output_rows,
8506 })
8507 }
8508}
8509
8510#[derive(Debug, Clone)]
8513struct ProjectedItem {
8514 expr: Expr,
8515 output_name: String,
8516 ty: DataType,
8517 nullable: bool,
8518}
8519
8520fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8526 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8527 for r in rows {
8528 if !out.iter().any(|seen| seen == &r) {
8529 out.push(r);
8530 }
8531 }
8532 out
8533}
8534
8535fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8539 match v {
8540 Value::Null => Ok(f64::INFINITY),
8541 Value::SmallInt(n) => Ok(f64::from(*n)),
8542 Value::Int(n) => Ok(f64::from(*n)),
8543 Value::Date(d) => Ok(f64::from(*d)),
8544 #[allow(clippy::cast_precision_loss)]
8545 Value::Timestamp(t) => Ok(*t as f64),
8546 #[allow(clippy::cast_precision_loss)]
8549 Value::Time(us) => Ok(*us as f64),
8550 Value::Year(y) => Ok(f64::from(*y)),
8554 #[allow(clippy::cast_precision_loss)]
8559 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8560 #[allow(clippy::cast_precision_loss)]
8562 Value::Money(c) => Ok(*c as f64),
8563 Value::Range { .. } => Err(EngineError::Unsupported(
8566 "ORDER BY of a range value is not supported in v7.17.0".into(),
8567 )),
8568 Value::Hstore(_) => Err(EngineError::Unsupported(
8570 "ORDER BY of a hstore value is not supported".into(),
8571 )),
8572 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8574 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8575 ),
8576 #[allow(clippy::cast_precision_loss)]
8577 Value::Numeric { scaled, scale } => {
8578 let mut divisor = 1.0_f64;
8584 for _ in 0..*scale {
8585 divisor *= 10.0;
8586 }
8587 Ok((*scaled as f64) / divisor)
8588 }
8589 #[allow(clippy::cast_precision_loss)]
8590 Value::BigInt(n) => Ok(*n as f64),
8591 Value::Float(x) => Ok(*x),
8592 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8593 Value::Text(s) => {
8594 let mut key: u64 = 0;
8598 for &b in s.as_bytes().iter().take(8) {
8599 key = (key << 8) | u64::from(b);
8600 }
8601 #[allow(clippy::cast_precision_loss)]
8602 Ok(key as f64)
8603 }
8604 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8605 Err(EngineError::Unsupported(
8606 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8607 ))
8608 }
8609 Value::Interval { .. } => Err(EngineError::Unsupported(
8610 "ORDER BY of an INTERVAL is not supported in v2.11 \
8611 (months vs micros has no single canonical ordering)"
8612 .into(),
8613 )),
8614 Value::Json(_) => Err(EngineError::Unsupported(
8615 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8616 )),
8617 _ => Err(EngineError::Unsupported(
8621 "ORDER BY of this value type is not supported".into(),
8622 )),
8623 }
8624}
8625
8626fn try_nsw_knn(
8640 stmt: &SelectStatement,
8641 table: &Table,
8642 schema_cols: &[ColumnSchema],
8643 table_alias: &str,
8644) -> Option<Vec<usize>> {
8645 if stmt.distinct {
8646 return None;
8647 }
8648 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8649 if limit == 0 {
8650 return None;
8651 }
8652 if stmt.order_by.len() != 1 {
8656 return None;
8657 }
8658 let order = &stmt.order_by[0];
8659 if order.desc {
8663 return None;
8664 }
8665 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8666 return None;
8667 };
8668 let metric = match op {
8669 BinOp::L2Distance => spg_storage::NswMetric::L2,
8670 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8671 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8672 _ => return None,
8673 };
8674 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8676 (lhs.as_ref(), rhs.as_ref())
8677 else {
8678 return None;
8679 };
8680 if let Some(q) = &col.qualifier
8681 && q != table_alias
8682 {
8683 return None;
8684 }
8685 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8686 let query = literal_to_vector(literal)?;
8687 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8688 if let Some(where_expr) = &stmt.where_ {
8689 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8693 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8694 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8695 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8696 for i in candidates {
8697 let row = &table.rows()[i];
8698 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8699 if matches!(cond, Value::Bool(true)) {
8700 kept.push(i);
8701 if kept.len() >= limit {
8702 break;
8703 }
8704 }
8705 }
8706 Some(kept)
8707 } else {
8708 Some(spg_storage::nsw_query(
8709 table, &idx.name, &query, limit, metric,
8710 ))
8711 }
8712}
8713
8714const NSW_OVER_FETCH_FLOOR: usize = 32;
8718
8719fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8722 match e {
8723 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8724 Expr::Cast { expr, .. } => literal_to_vector(expr),
8725 _ => None,
8726 }
8727}
8728
8729fn materialise_in_order(
8733 stmt: &SelectStatement,
8734 table: &Table,
8735 schema_cols: &[ColumnSchema],
8736 table_alias: &str,
8737 ordered_rows: &[usize],
8738) -> Result<QueryResult, EngineError> {
8739 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8740 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8741 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8742 for &i in ordered_rows {
8743 let row = &table.rows()[i];
8744 let mut values = Vec::with_capacity(projection.len());
8745 for p in &projection {
8746 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8747 }
8748 output_rows.push(Row::new(values));
8749 }
8750 apply_offset_and_limit(
8751 &mut output_rows,
8752 stmt.offset_literal(),
8753 stmt.limit_literal(),
8754 );
8755 let columns: Vec<ColumnSchema> = projection
8756 .into_iter()
8757 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8758 .collect();
8759 Ok(QueryResult::Rows {
8760 columns,
8761 rows: output_rows,
8762 })
8763}
8764
8765fn try_index_seek_positions(
8778 where_expr: &Expr,
8779 schema_cols: &[ColumnSchema],
8780 table: &Table,
8781 table_alias: &str,
8782) -> Option<Vec<usize>> {
8783 if let Expr::Binary {
8784 lhs,
8785 op: BinOp::And,
8786 rhs,
8787 } = where_expr
8788 {
8789 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8790 return Some(p);
8791 }
8792 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8793 }
8794 let Expr::Binary {
8795 lhs,
8796 op: BinOp::Eq,
8797 rhs,
8798 } = where_expr
8799 else {
8800 return None;
8801 };
8802 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8803 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8804 let idx = table.index_on(col_pos)?;
8805 let key = IndexKey::from_value(&value)?;
8806 let locators = idx.lookup_eq(&key);
8807 let mut out = Vec::with_capacity(locators.len());
8808 for loc in locators {
8809 match *loc {
8810 spg_storage::RowLocator::Hot(i) => out.push(i),
8811 spg_storage::RowLocator::Cold { .. } => return None,
8812 }
8813 }
8814 Some(out)
8815}
8816
8817fn try_index_seek<'a>(
8818 where_expr: &Expr,
8819 schema_cols: &[ColumnSchema],
8820 catalog: &'a Catalog,
8821 table: &'a Table,
8822 table_alias: &str,
8823) -> Option<Vec<Cow<'a, Row>>> {
8824 if let Expr::Binary {
8831 lhs,
8832 op: BinOp::And,
8833 rhs,
8834 } = where_expr
8835 {
8836 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8839 return Some(rows);
8840 }
8841 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8842 }
8843 let Expr::Binary {
8844 lhs,
8845 op: BinOp::Eq,
8846 rhs,
8847 } = where_expr
8848 else {
8849 return None;
8850 };
8851 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8852 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8853 let idx = table.index_on(col_pos)?;
8854 let key = IndexKey::from_value(&value)?;
8855 let locators = idx.lookup_eq(&key);
8856 let table_name = table.schema().name.as_str();
8857 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8865 for loc in locators {
8866 match *loc {
8867 spg_storage::RowLocator::Hot(i) => {
8868 if let Some(row) = table.rows().get(i) {
8869 out.push(Cow::Borrowed(row));
8870 }
8871 }
8872 spg_storage::RowLocator::Cold { segment_id, .. } => {
8873 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8874 out.push(Cow::Owned(row));
8875 }
8876 }
8877 }
8878 }
8879 Some(out)
8880}
8881
8882fn try_gin_seek<'a>(
8901 where_expr: &Expr,
8902 schema_cols: &[ColumnSchema],
8903 catalog: &'a Catalog,
8904 table: &'a Table,
8905 table_alias: &str,
8906 ctx: &eval::EvalContext<'_>,
8907) -> Option<Vec<Cow<'a, Row>>> {
8908 if let Expr::Binary {
8909 lhs,
8910 op: BinOp::And,
8911 rhs,
8912 } = where_expr
8913 {
8914 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8915 return Some(rows);
8916 }
8917 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8918 }
8919 if let Expr::Binary {
8928 lhs,
8929 op: BinOp::Or,
8930 rhs,
8931 } = where_expr
8932 {
8933 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8934 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8935 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8936 out.extend(left);
8937 out.extend(right);
8938 return Some(out);
8939 }
8940 let Expr::Binary {
8941 lhs,
8942 op: BinOp::TsMatch,
8943 rhs,
8944 } = where_expr
8945 else {
8946 return None;
8947 };
8948 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8953 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8954 let idx = table
8961 .indices()
8962 .iter()
8963 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8964 let candidates = gin_query_candidates(idx, &query)?;
8965 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8967 for loc in candidates {
8968 match loc {
8969 spg_storage::RowLocator::Hot(i) => {
8970 if let Some(row) = table.rows().get(i) {
8971 out.push(Cow::Borrowed(row));
8972 }
8973 }
8974 spg_storage::RowLocator::Cold { .. } => {}
8981 }
8982 }
8983 Some(out)
8984}
8985
8986fn try_trgm_seek<'a>(
9002 where_expr: &Expr,
9003 schema_cols: &[ColumnSchema],
9004 table: &'a Table,
9005 table_alias: &str,
9006) -> Option<Vec<Cow<'a, Row>>> {
9007 if let Expr::Binary {
9008 lhs,
9009 op: BinOp::And,
9010 rhs,
9011 } = where_expr
9012 {
9013 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
9014 return Some(rows);
9015 }
9016 return try_trgm_seek(rhs, schema_cols, table, table_alias);
9017 }
9018 let Expr::Like { expr, pattern, .. } = where_expr else {
9024 return None;
9025 };
9026 let Expr::Column(c) = expr.as_ref() else {
9028 return None;
9029 };
9030 if let Some(q) = &c.qualifier
9031 && q != table_alias
9032 {
9033 return None;
9034 }
9035 let col_pos = schema_cols
9036 .iter()
9037 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
9038 let idx = table
9040 .indices()
9041 .iter()
9042 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
9043 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
9047 return None;
9048 };
9049 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
9050 let mut iter = trigrams.iter();
9053 let first = iter.next()?;
9054 let mut acc: Vec<spg_storage::RowLocator> = {
9055 let mut v = idx.gin_trgm_lookup(first).to_vec();
9056 v.sort_by_key(locator_sort_key);
9057 v.dedup_by_key(|l| locator_sort_key(l));
9058 v
9059 };
9060 for tri in iter {
9061 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
9062 next.sort_by_key(locator_sort_key);
9063 next.dedup_by_key(|l| locator_sort_key(l));
9064 let mut merged: Vec<spg_storage::RowLocator> =
9066 Vec::with_capacity(acc.len().min(next.len()));
9067 let (mut i, mut j) = (0usize, 0usize);
9068 while i < acc.len() && j < next.len() {
9069 let lk = locator_sort_key(&acc[i]);
9070 let rk = locator_sort_key(&next[j]);
9071 match lk.cmp(&rk) {
9072 core::cmp::Ordering::Less => i += 1,
9073 core::cmp::Ordering::Greater => j += 1,
9074 core::cmp::Ordering::Equal => {
9075 merged.push(acc[i]);
9076 i += 1;
9077 j += 1;
9078 }
9079 }
9080 }
9081 acc = merged;
9082 if acc.is_empty() {
9083 break;
9084 }
9085 }
9086 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
9087 for loc in acc {
9088 if let spg_storage::RowLocator::Hot(i) = loc
9089 && let Some(row) = table.rows().get(i)
9090 {
9091 out.push(Cow::Borrowed(row));
9092 }
9093 }
9095 Some(out)
9096}
9097
9098fn resolve_gin_col_query(
9104 col_side: &Expr,
9105 query_side: &Expr,
9106 schema_cols: &[ColumnSchema],
9107 table_alias: &str,
9108 ctx: &eval::EvalContext<'_>,
9109) -> Option<(usize, spg_storage::TsQueryAst)> {
9110 let column = match col_side {
9115 Expr::Column(c) => c,
9116 Expr::FunctionCall { name, args }
9117 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9118 {
9119 if let Expr::Column(c) = args.last().unwrap() {
9123 c
9124 } else {
9125 return None;
9126 }
9127 }
9128 _ => return None,
9129 };
9130 let c = column;
9131 if let Some(q) = &c.qualifier
9132 && q != table_alias
9133 {
9134 return None;
9135 }
9136 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9137 let empty_row = Row::new(Vec::new());
9141 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9142 let Value::TsQuery(q) = v else { return None };
9143 Some((pos, q))
9144}
9145
9146fn gin_query_candidates(
9157 idx: &spg_storage::Index,
9158 query: &spg_storage::TsQueryAst,
9159) -> Option<Vec<spg_storage::RowLocator>> {
9160 use spg_storage::TsQueryAst;
9161 match query {
9162 TsQueryAst::Term { word, .. } => {
9163 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9164 v.sort_by_key(locator_sort_key);
9165 v.dedup_by_key(|l| locator_sort_key(l));
9166 Some(v)
9167 }
9168 TsQueryAst::And(l, r) => {
9169 let mut left = gin_query_candidates(idx, l)?;
9170 let mut right = gin_query_candidates(idx, r)?;
9171 left.sort_by_key(locator_sort_key);
9172 right.sort_by_key(locator_sort_key);
9173 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9175 let (mut i, mut j) = (0usize, 0usize);
9176 while i < left.len() && j < right.len() {
9177 let lk = locator_sort_key(&left[i]);
9178 let rk = locator_sort_key(&right[j]);
9179 match lk.cmp(&rk) {
9180 core::cmp::Ordering::Less => i += 1,
9181 core::cmp::Ordering::Greater => j += 1,
9182 core::cmp::Ordering::Equal => {
9183 out.push(left[i]);
9184 i += 1;
9185 j += 1;
9186 }
9187 }
9188 }
9189 Some(out)
9190 }
9191 TsQueryAst::Or(l, r) => {
9192 let mut out = gin_query_candidates(idx, l)?;
9193 out.extend(gin_query_candidates(idx, r)?);
9194 out.sort_by_key(locator_sort_key);
9195 out.dedup_by_key(|l| locator_sort_key(l));
9196 Some(out)
9197 }
9198 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9203 }
9204}
9205
9206fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9211 match *l {
9212 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9213 spg_storage::RowLocator::Cold {
9214 segment_id,
9215 page_offset,
9216 } => (1, u64::from(segment_id), u64::from(page_offset)),
9217 }
9218}
9219
9220fn try_pk_predicate(
9232 where_expr: &Expr,
9233 schema_cols: &[ColumnSchema],
9234 table_alias: &str,
9235) -> Option<(usize, IndexKey)> {
9236 let Expr::Binary {
9237 lhs,
9238 op: BinOp::Eq,
9239 rhs,
9240 } = where_expr
9241 else {
9242 return None;
9243 };
9244 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9245 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9246 let key = IndexKey::from_value(&value)?;
9247 Some((col_pos, key))
9248}
9249
9250fn resolve_col_literal_pair(
9251 col_side: &Expr,
9252 lit_side: &Expr,
9253 schema_cols: &[ColumnSchema],
9254 table_alias: &str,
9255) -> Option<(usize, Value)> {
9256 let Expr::Column(c) = col_side else {
9257 return None;
9258 };
9259 if let Some(q) = &c.qualifier
9260 && q != table_alias
9261 {
9262 return None;
9263 }
9264 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9265 let Expr::Literal(l) = lit_side else {
9266 return None;
9267 };
9268 let v = match l {
9269 Literal::Integer(n) => {
9270 if let Ok(small) = i32::try_from(*n) {
9271 Value::Int(small)
9272 } else {
9273 Value::BigInt(*n)
9274 }
9275 }
9276 Literal::Float(x) => Value::Float(*x),
9277 Literal::String(s) => Value::Text(s.clone()),
9278 Literal::Bool(b) => Value::Bool(*b),
9279 Literal::Null => Value::Null,
9280 Literal::Vector(_)
9283 | Literal::Interval { .. }
9284 | Literal::TextArray(_)
9285 | Literal::IntArray(_)
9286 | Literal::BigIntArray(_) => return None,
9287 };
9288 Some((pos, v))
9289}
9290
9291fn resolve_projection_column<'a>(
9296 c: &ColumnName,
9297 schema_cols: &'a [ColumnSchema],
9298 table_alias: &str,
9299) -> Result<&'a ColumnSchema, EngineError> {
9300 if let Some(q) = &c.qualifier {
9301 let composite = alloc::format!("{q}.{name}", name = c.name);
9302 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9303 return Ok(s);
9304 }
9305 if q == table_alias
9308 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9309 {
9310 return Ok(s);
9311 }
9312 let prefix = alloc::format!("{q}.");
9316 let qualifier_known =
9317 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9318 if !qualifier_known {
9319 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9320 qualifier: q.clone(),
9321 }));
9322 }
9323 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9324 name: c.name.clone(),
9325 }));
9326 }
9327 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9328 return Ok(s);
9329 }
9330 let suffix = alloc::format!(".{name}", name = c.name);
9331 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9332 let first = matches.next();
9333 let extra = matches.next();
9334 match (first, extra) {
9335 (Some(s), None) => Ok(s),
9336 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9337 detail: alloc::format!("ambiguous column reference: {}", c.name),
9338 })),
9339 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9340 name: c.name.clone(),
9341 })),
9342 }
9343}
9344
9345fn build_projection(
9346 items: &[SelectItem],
9347 schema_cols: &[ColumnSchema],
9348 table_alias: &str,
9349) -> Result<Vec<ProjectedItem>, EngineError> {
9350 let mut out = Vec::new();
9351 for item in items {
9352 match item {
9353 SelectItem::Wildcard => {
9354 for col in schema_cols {
9355 out.push(ProjectedItem {
9356 expr: Expr::Column(ColumnName {
9357 qualifier: None,
9358 name: col.name.clone(),
9359 }),
9360 output_name: col.name.clone(),
9361 ty: col.ty,
9362 nullable: col.nullable,
9363 });
9364 }
9365 }
9366 SelectItem::Expr { expr, alias } => {
9367 if let Expr::Column(c) = expr {
9374 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9375 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9376 out.push(ProjectedItem {
9377 expr: expr.clone(),
9378 output_name,
9379 ty: sch.ty,
9380 nullable: sch.nullable,
9381 });
9382 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9383 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9384 out.push(ProjectedItem {
9385 expr: expr.clone(),
9386 output_name,
9387 ty: shape.ty,
9388 nullable: shape.nullable,
9389 });
9390 } else {
9391 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9392 out.push(ProjectedItem {
9393 expr: expr.clone(),
9394 output_name,
9395 ty: DataType::Text,
9396 nullable: true,
9397 });
9398 }
9399 }
9400 }
9401 }
9402 Ok(out)
9403}
9404
9405fn numeric_from_integer(
9409 n: i128,
9410 precision: u8,
9411 scale: u8,
9412 col_name: &str,
9413) -> Result<Value, EngineError> {
9414 let factor = pow10_i128(scale);
9415 let scaled = n.checked_mul(factor).ok_or_else(|| {
9416 EngineError::Unsupported(alloc::format!(
9417 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9418 ))
9419 })?;
9420 check_precision(scaled, precision, col_name)?;
9421 Ok(Value::Numeric { scaled, scale })
9422}
9423
9424#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9427fn numeric_from_float(
9428 x: f64,
9429 precision: u8,
9430 scale: u8,
9431 col_name: &str,
9432) -> Result<Value, EngineError> {
9433 if !x.is_finite() {
9434 return Err(EngineError::Unsupported(alloc::format!(
9435 "cannot store non-finite float in NUMERIC column `{col_name}`"
9436 )));
9437 }
9438 let mut factor = 1.0_f64;
9439 for _ in 0..scale {
9440 factor *= 10.0;
9441 }
9442 let shifted = x * factor;
9447 let biased = if shifted >= 0.0 {
9448 shifted + 0.5
9449 } else {
9450 shifted - 0.5
9451 };
9452 if !(-1e38..=1e38).contains(&biased) {
9455 return Err(EngineError::Unsupported(alloc::format!(
9456 "value {x} overflows NUMERIC range for column `{col_name}`"
9457 )));
9458 }
9459 let scaled = biased as i128;
9460 check_precision(scaled, precision, col_name)?;
9461 Ok(Value::Numeric { scaled, scale })
9462}
9463
9464fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9471 let s = s.trim();
9472 if s.is_empty() {
9473 return None;
9474 }
9475 let (negative, rest) = match s.as_bytes()[0] {
9476 b'-' => (true, &s[1..]),
9477 b'+' => (false, &s[1..]),
9478 _ => (false, s),
9479 };
9480 if rest.is_empty() {
9481 return None;
9482 }
9483 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9487 return None;
9488 }
9489 let (int_part, frac_part) = match rest.find('.') {
9490 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9491 None => (rest, ""),
9492 };
9493 if int_part.is_empty() && frac_part.is_empty() {
9494 return None;
9495 }
9496 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9497 return None;
9498 }
9499 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9500 return None;
9501 }
9502 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9503 if scale_u32 > u32::from(u8::MAX) {
9504 return None;
9505 }
9506 let scale = scale_u32 as u8;
9507 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9508 if negative {
9509 digits.push('-');
9510 }
9511 digits.push_str(int_part);
9512 digits.push_str(frac_part);
9513 let digits = if digits == "-" {
9515 return None;
9516 } else if digits.is_empty() {
9517 "0"
9518 } else {
9519 digits.as_str()
9520 };
9521 let mantissa: i128 = digits.parse().ok()?;
9522 Some((mantissa, scale))
9523}
9524
9525fn numeric_rescale(
9528 scaled: i128,
9529 src_scale: u8,
9530 precision: u8,
9531 dst_scale: u8,
9532 col_name: &str,
9533) -> Result<Value, EngineError> {
9534 let new_scaled = if dst_scale >= src_scale {
9535 let bump = pow10_i128(dst_scale - src_scale);
9536 scaled.checked_mul(bump).ok_or_else(|| {
9537 EngineError::Unsupported(alloc::format!(
9538 "overflow rescaling NUMERIC for column `{col_name}`"
9539 ))
9540 })?
9541 } else {
9542 let drop = pow10_i128(src_scale - dst_scale);
9543 let half = drop / 2;
9544 if scaled >= 0 {
9545 (scaled + half) / drop
9546 } else {
9547 (scaled - half) / drop
9548 }
9549 };
9550 check_precision(new_scaled, precision, col_name)?;
9551 Ok(Value::Numeric {
9552 scaled: new_scaled,
9553 scale: dst_scale,
9554 })
9555}
9556
9557const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9560 if scale == 0 {
9561 return scaled;
9562 }
9563 let factor = pow10_i128_const(scale);
9564 scaled / factor
9565}
9566
9567fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9571 if precision == 0 {
9572 return Ok(());
9573 }
9574 let limit = pow10_i128(precision);
9575 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9576 return Err(EngineError::Unsupported(alloc::format!(
9577 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9578 )));
9579 }
9580 Ok(())
9581}
9582
9583const fn pow10_i128_const(p: u8) -> i128 {
9584 let mut acc: i128 = 1;
9585 let mut i = 0;
9586 while i < p {
9587 acc *= 10;
9588 i += 1;
9589 }
9590 acc
9591}
9592
9593fn pow10_i128(p: u8) -> i128 {
9594 pow10_i128_const(p)
9595}
9596
9597impl Engine {
9612 #[allow(
9623 clippy::too_many_lines,
9624 clippy::type_complexity,
9625 clippy::needless_range_loop
9626 )] fn exec_select_with_window(
9628 &self,
9629 stmt: &SelectStatement,
9630 cancel: CancelToken<'_>,
9631 ) -> Result<QueryResult, EngineError> {
9632 let from = stmt.from.as_ref().ok_or_else(|| {
9633 EngineError::Unsupported("window functions require a FROM clause".into())
9634 })?;
9635 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9644 let filtered: Vec<Row>;
9645 if from.joins.is_empty() {
9646 let primary = &from.primary;
9647 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9648 StorageError::TableNotFound {
9649 name: primary.name.clone(),
9650 }
9651 })?;
9652 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9653 schema_cols_owned = table.schema().columns.clone();
9654 alias_opt = Some(alias);
9655 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9660 let mut owned: Vec<Row> = Vec::new();
9661 for (i, row) in table.rows().iter().enumerate() {
9662 if i.is_multiple_of(256) {
9663 cancel.check()?;
9664 }
9665 if let Some(w) = &stmt.where_ {
9666 let cond = eval::eval_expr(w, row, &ctx)?;
9667 if !matches!(cond, Value::Bool(true)) {
9668 continue;
9669 }
9670 }
9671 owned.push(row.clone());
9672 }
9673 filtered = owned;
9674 } else {
9675 let (combined_schema, rows) =
9676 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9677 schema_cols_owned = combined_schema;
9678 alias_opt = None;
9679 filtered = rows;
9680 }
9681 let schema_cols = &schema_cols_owned;
9682 let ctx = self.ev_ctx(schema_cols, alias_opt);
9683 let alias = alias_opt.unwrap_or("");
9684 let n_rows = filtered.len();
9685 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9689
9690 let mut window_nodes: Vec<Expr> = Vec::new();
9692 for item in &stmt.items {
9693 if let SelectItem::Expr { expr, .. } = item {
9694 collect_window_nodes(expr, &mut window_nodes);
9695 }
9696 }
9697
9698 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9701 for wnode in &window_nodes {
9702 let Expr::WindowFunction {
9703 name,
9704 args,
9705 partition_by,
9706 order_by,
9707 frame,
9708 null_treatment,
9709 } = wnode
9710 else {
9711 unreachable!("collect_window_nodes pushes only WindowFunction");
9712 };
9713 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9715 Vec::with_capacity(n_rows);
9716 for (i, row) in filtered.iter().enumerate() {
9717 let pkey: Vec<Value> = partition_by
9718 .iter()
9719 .map(|p| eval::eval_expr(p, row, &ctx))
9720 .collect::<Result<_, _>>()?;
9721 let okey: Vec<(Value, bool)> = order_by
9722 .iter()
9723 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9724 .collect::<Result<_, _>>()?;
9725 indexed.push((pkey, okey, i));
9726 }
9727 indexed.sort_by(|a, b| {
9730 let p_cmp = partition_key_cmp(&a.0, &b.0);
9731 if p_cmp != core::cmp::Ordering::Equal {
9732 return p_cmp;
9733 }
9734 order_key_cmp(&a.1, &b.1)
9735 });
9736 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9738 let mut p_start = 0;
9739 while p_start < indexed.len() {
9740 let mut p_end = p_start + 1;
9741 while p_end < indexed.len()
9742 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9743 == core::cmp::Ordering::Equal
9744 {
9745 p_end += 1;
9746 }
9747 compute_window_partition(
9749 name,
9750 args,
9751 !order_by.is_empty(),
9752 frame.as_ref(),
9753 *null_treatment,
9754 &indexed[p_start..p_end],
9755 &filtered_refs,
9756 &ctx,
9757 &mut out_vals,
9758 )?;
9759 p_start = p_end;
9760 }
9761 win_vals.push(out_vals);
9762 }
9763
9764 let mut ext_cols = schema_cols.clone();
9766 for i in 0..window_nodes.len() {
9767 ext_cols.push(ColumnSchema::new(
9768 alloc::format!("__win_{i}"),
9769 DataType::Text, true,
9771 ));
9772 }
9773 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9775 for i in 0..n_rows {
9776 let mut values = filtered[i].values.clone();
9777 for w in 0..window_nodes.len() {
9778 values.push(win_vals[w][i].clone());
9779 }
9780 ext_rows.push(Row::new(values));
9781 }
9782 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9784 for item in &stmt.items {
9785 let new_item = match item {
9786 SelectItem::Wildcard => SelectItem::Wildcard,
9787 SelectItem::Expr { expr, alias } => {
9788 let mut e = expr.clone();
9789 rewrite_window_to_columns(&mut e, &window_nodes);
9790 SelectItem::Expr {
9791 expr: e,
9792 alias: alias.clone(),
9793 }
9794 }
9795 };
9796 rewritten_items.push(new_item);
9797 }
9798
9799 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9805 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9806 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9807 for (i, row) in ext_rows.iter().enumerate() {
9808 if i.is_multiple_of(256) {
9809 cancel.check()?;
9810 }
9811 let mut values = Vec::with_capacity(projection.len());
9812 for p in &projection {
9813 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9814 }
9815 let order_keys = if stmt.order_by.is_empty() {
9816 Vec::new()
9817 } else {
9818 let mut keys = Vec::with_capacity(stmt.order_by.len());
9819 for o in &stmt.order_by {
9820 let mut e = o.expr.clone();
9821 rewrite_window_to_columns(&mut e, &window_nodes);
9822 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9823 keys.push(value_to_order_key(&key)?);
9824 }
9825 keys
9826 };
9827 tagged.push((order_keys, Row::new(values)));
9828 }
9829 if !stmt.order_by.is_empty() {
9831 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9832 sort_by_keys(&mut tagged, &descs);
9833 }
9834 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9835 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9836 let final_cols: Vec<ColumnSchema> = projection
9837 .into_iter()
9838 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9839 .collect();
9840 Ok(QueryResult::Rows {
9841 columns: final_cols,
9842 rows: out_rows,
9843 })
9844 }
9845
9846 fn exec_select_with_meta_views(
9863 &self,
9864 stmt: &SelectStatement,
9865 cancel: CancelToken<'_>,
9866 ) -> Result<QueryResult, EngineError> {
9867 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9868 collect_meta_view_names(stmt, &mut needed);
9869 let mut catalog = self.active_catalog().clone();
9870 for view in &needed {
9871 if catalog.get(view).is_some() {
9872 continue;
9873 }
9874 match view.as_str() {
9875 "__spg_info_columns" => {
9876 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9877 materialise_meta_view(&mut catalog, view, schema, rows)?;
9878 }
9879 "__spg_info_tables" => {
9880 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9881 materialise_meta_view(&mut catalog, view, schema, rows)?;
9882 }
9883 "__spg_pg_class" => {
9884 let (schema, rows) = synth_pg_class(self.active_catalog());
9885 materialise_meta_view(&mut catalog, view, schema, rows)?;
9886 }
9887 "__spg_pg_attribute" => {
9888 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9889 materialise_meta_view(&mut catalog, view, schema, rows)?;
9890 }
9891 "__spg_pg_type" => {
9894 let (schema, rows) = synth_pg_type(self.active_catalog());
9895 materialise_meta_view(&mut catalog, view, schema, rows)?;
9896 }
9897 "__spg_pg_proc" => {
9900 let (schema, rows) = synth_pg_proc(self.active_catalog());
9901 materialise_meta_view(&mut catalog, view, schema, rows)?;
9902 }
9903 "__spg_pg_namespace" => {
9906 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9907 materialise_meta_view(&mut catalog, view, schema, rows)?;
9908 }
9909 "__spg_pg_indexes" => {
9912 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9913 materialise_meta_view(&mut catalog, view, schema, rows)?;
9914 }
9915 "__spg_pg_index" => {
9918 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9919 materialise_meta_view(&mut catalog, view, schema, rows)?;
9920 }
9921 "__spg_pg_constraint" => {
9924 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9925 materialise_meta_view(&mut catalog, view, schema, rows)?;
9926 }
9927 "__spg_pg_database" => {
9932 let (schema, rows) = synth_pg_database(self.active_catalog());
9933 materialise_meta_view(&mut catalog, view, schema, rows)?;
9934 }
9935 "__spg_pg_roles" | "__spg_pg_user" => {
9936 let (schema, rows) = synth_pg_roles(self);
9937 materialise_meta_view(&mut catalog, view, schema, rows)?;
9938 }
9939 "__spg_pg_views" => {
9943 let (schema, rows) = synth_pg_views(self.active_catalog());
9944 materialise_meta_view(&mut catalog, view, schema, rows)?;
9945 }
9946 "__spg_pg_matviews" => {
9950 let (schema, _) = synth_pg_views(self.active_catalog());
9951 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9952 }
9953 "__spg_pg_extension" => {
9956 let (schema, rows) = synth_pg_extension();
9957 materialise_meta_view(&mut catalog, view, schema, rows)?;
9958 }
9959 "__spg_pg_settings" => {
9961 let (schema, rows) = synth_pg_settings(self);
9962 materialise_meta_view(&mut catalog, view, schema, rows)?;
9963 }
9964 "__spg_info_key_column_usage" => {
9966 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9967 materialise_meta_view(&mut catalog, view, schema, rows)?;
9968 }
9969 "__spg_info_referential_constraints" => {
9971 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9972 materialise_meta_view(&mut catalog, view, schema, rows)?;
9973 }
9974 "__spg_info_statistics" => {
9976 let (schema, rows) = synth_info_statistics(self.active_catalog());
9977 materialise_meta_view(&mut catalog, view, schema, rows)?;
9978 }
9979 "__spg_info_routines" => {
9981 let (schema, rows) = synth_info_routines();
9982 materialise_meta_view(&mut catalog, view, schema, rows)?;
9983 }
9984 "__spg_mysql_user" => {
9986 let (schema, rows) = synth_mysql_user(self);
9987 materialise_meta_view(&mut catalog, view, schema, rows)?;
9988 }
9989 "__spg_mysql_db" => {
9990 let (schema, rows) = synth_mysql_db();
9991 materialise_meta_view(&mut catalog, view, schema, rows)?;
9992 }
9993 _ => {
9994 return Err(EngineError::Unsupported(alloc::format!(
9995 "meta view {view:?} is not yet materialisable; \
9996 v7.16.2 covers information_schema.columns / .tables \
9997 and pg_catalog.pg_class / pg_attribute; \
9998 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9999 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
10000 pg_user / pg_views / pg_matviews / pg_settings"
10001 )));
10002 }
10003 }
10004 }
10005 let mut temp = Engine::restore(catalog);
10006 if let Some(c) = self.clock {
10007 temp = temp.with_clock(c);
10008 }
10009 if let Some(f) = self.salt_fn {
10010 temp = temp.with_salt_fn(f);
10011 }
10012 temp.meta_views_materialised = true;
10013 temp.exec_select_cancel(stmt, cancel)
10014 }
10015
10016 fn exec_with_ctes(
10017 &self,
10018 stmt: &SelectStatement,
10019 cancel: CancelToken<'_>,
10020 ) -> Result<QueryResult, EngineError> {
10021 cancel.check()?;
10022 let mut catalog = self.active_catalog().clone();
10023 for cte in &stmt.ctes {
10024 if catalog.get(&cte.name).is_some() {
10025 return Err(EngineError::Unsupported(alloc::format!(
10026 "CTE name {:?} shadows an existing table; rename the CTE",
10027 cte.name
10028 )));
10029 }
10030 let (columns, rows) = if cte.recursive {
10031 self.materialise_recursive_cte(cte, &catalog, cancel)?
10032 } else {
10033 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
10034 let QueryResult::Rows { columns, rows } = body_result else {
10035 return Err(EngineError::Unsupported(alloc::format!(
10036 "CTE {:?} body did not return rows",
10037 cte.name
10038 )));
10039 };
10040 (columns, rows)
10041 };
10042 let inferred = infer_column_types(&columns, &rows);
10047 let mut columns = inferred;
10048 if !cte.column_overrides.is_empty() {
10050 if cte.column_overrides.len() != columns.len() {
10051 return Err(EngineError::Unsupported(alloc::format!(
10052 "CTE {:?} column list has {} names but body returns {} columns",
10053 cte.name,
10054 cte.column_overrides.len(),
10055 columns.len()
10056 )));
10057 }
10058 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10059 col.name.clone_from(name);
10060 }
10061 }
10062 let schema = TableSchema::new(cte.name.clone(), columns);
10063 catalog.create_table(schema).map_err(EngineError::Storage)?;
10064 let table = catalog
10065 .get_mut(&cte.name)
10066 .expect("just-created CTE table must exist");
10067 for row in rows {
10068 table.insert(row).map_err(EngineError::Storage)?;
10069 }
10070 }
10071 let mut body = stmt.clone();
10074 body.ctes = Vec::new();
10075 let mut temp = Engine::restore(catalog);
10076 if let Some(c) = self.clock {
10077 temp = temp.with_clock(c);
10078 }
10079 if let Some(f) = self.salt_fn {
10080 temp = temp.with_salt_fn(f);
10081 }
10082 temp.exec_select_cancel(&body, cancel)
10083 }
10084
10085 #[allow(clippy::too_many_lines)]
10095 fn materialise_recursive_cte(
10096 &self,
10097 cte: &spg_sql::ast::Cte,
10098 base_catalog: &Catalog,
10099 cancel: CancelToken<'_>,
10100 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10101 const MAX_TOTAL_ROWS: usize = 1_000_000;
10102 const MAX_ITERATIONS: usize = 100_000;
10103 cancel.check()?;
10104 if cte.body.unions.is_empty() {
10105 return Err(EngineError::Unsupported(alloc::format!(
10106 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10107 cte.name
10108 )));
10109 }
10110 let mut anchor = cte.body.clone();
10112 let union_terms = core::mem::take(&mut anchor.unions);
10113 anchor.ctes = Vec::new();
10114 if select_refers_to(&anchor, &cte.name) {
10116 return Err(EngineError::Unsupported(alloc::format!(
10117 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10118 cte.name
10119 )));
10120 }
10121 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10122 let QueryResult::Rows {
10123 columns: anchor_cols,
10124 rows: anchor_rows,
10125 } = anchor_result
10126 else {
10127 return Err(EngineError::Unsupported(alloc::format!(
10128 "WITH RECURSIVE {:?}: anchor did not return rows",
10129 cte.name
10130 )));
10131 };
10132 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10136 if !cte.column_overrides.is_empty() {
10137 if cte.column_overrides.len() != columns.len() {
10138 return Err(EngineError::Unsupported(alloc::format!(
10139 "CTE {:?} column list has {} names but anchor returns {} columns",
10140 cte.name,
10141 cte.column_overrides.len(),
10142 columns.len()
10143 )));
10144 }
10145 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10146 col.name.clone_from(name);
10147 }
10148 }
10149 let mut all_rows: Vec<Row> = anchor_rows.clone();
10150 let mut working_set: Vec<Row> = anchor_rows;
10151 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10152 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10155 if !all_union_all {
10156 for r in &all_rows {
10157 seen.insert(encode_row_key(r));
10158 }
10159 }
10160 for iter in 0..MAX_ITERATIONS {
10161 cancel.check()?;
10162 if working_set.is_empty() {
10163 break;
10164 }
10165 let mut iter_catalog = base_catalog.clone();
10167 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10168 iter_catalog
10169 .create_table(schema)
10170 .map_err(EngineError::Storage)?;
10171 {
10172 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10173 for row in &working_set {
10174 table.insert(row.clone()).map_err(EngineError::Storage)?;
10175 }
10176 }
10177 let mut iter_engine = Engine::restore(iter_catalog);
10178 if let Some(c) = self.clock {
10179 iter_engine = iter_engine.with_clock(c);
10180 }
10181 if let Some(f) = self.salt_fn {
10182 iter_engine = iter_engine.with_salt_fn(f);
10183 }
10184 let mut next_set: Vec<Row> = Vec::new();
10186 for (_, term) in &union_terms {
10187 let mut term = term.clone();
10188 term.ctes = Vec::new();
10189 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10190 let QueryResult::Rows {
10191 columns: rc,
10192 rows: rs,
10193 } = r
10194 else {
10195 return Err(EngineError::Unsupported(alloc::format!(
10196 "WITH RECURSIVE {:?}: recursive term did not return rows",
10197 cte.name
10198 )));
10199 };
10200 if rc.len() != columns.len() {
10201 return Err(EngineError::Unsupported(alloc::format!(
10202 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10203 cte.name,
10204 rc.len(),
10205 columns.len()
10206 )));
10207 }
10208 for row in rs {
10209 if !all_union_all {
10210 let key = encode_row_key(&row);
10211 if !seen.insert(key) {
10212 continue;
10213 }
10214 }
10215 next_set.push(row);
10216 }
10217 }
10218 if next_set.is_empty() {
10219 break;
10220 }
10221 all_rows.extend(next_set.iter().cloned());
10222 working_set = next_set;
10223 if all_rows.len() > MAX_TOTAL_ROWS {
10224 return Err(EngineError::Unsupported(alloc::format!(
10225 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10226 cte.name
10227 )));
10228 }
10229 if iter + 1 == MAX_ITERATIONS {
10230 return Err(EngineError::Unsupported(alloc::format!(
10231 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10232 cte.name
10233 )));
10234 }
10235 }
10236 Ok((columns, all_rows))
10237 }
10238
10239 fn resolve_select_subqueries(
10240 &self,
10241 stmt: &mut SelectStatement,
10242 cancel: CancelToken<'_>,
10243 ) -> Result<(), EngineError> {
10244 for item in &mut stmt.items {
10245 if let SelectItem::Expr { expr, .. } = item {
10246 self.resolve_expr_subqueries(expr, cancel)?;
10247 }
10248 }
10249 if let Some(w) = &mut stmt.where_ {
10250 self.resolve_expr_subqueries(w, cancel)?;
10251 }
10252 if let Some(gs) = &mut stmt.group_by {
10253 for g in gs {
10254 self.resolve_expr_subqueries(g, cancel)?;
10255 }
10256 }
10257 if let Some(h) = &mut stmt.having {
10258 self.resolve_expr_subqueries(h, cancel)?;
10259 }
10260 for o in &mut stmt.order_by {
10261 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10262 }
10263 for (_, peer) in &mut stmt.unions {
10264 self.resolve_select_subqueries(peer, cancel)?;
10265 }
10266 Ok(())
10267 }
10268
10269 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10271 &self,
10272 e: &mut Expr,
10273 cancel: CancelToken<'_>,
10274 ) -> Result<(), EngineError> {
10275 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10277 *e = replacement;
10278 return Ok(());
10279 }
10280 match e {
10281 Expr::Binary { lhs, rhs, .. } => {
10282 self.resolve_expr_subqueries(lhs, cancel)?;
10283 self.resolve_expr_subqueries(rhs, cancel)?;
10284 }
10285 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10286 self.resolve_expr_subqueries(expr, cancel)?;
10287 }
10288 Expr::FunctionCall { args, .. } => {
10289 for a in args {
10290 self.resolve_expr_subqueries(a, cancel)?;
10291 }
10292 }
10293 Expr::Like { expr, pattern, .. } => {
10294 self.resolve_expr_subqueries(expr, cancel)?;
10295 self.resolve_expr_subqueries(pattern, cancel)?;
10296 }
10297 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10298 Expr::WindowFunction {
10301 args,
10302 partition_by,
10303 order_by,
10304 ..
10305 } => {
10306 for a in args {
10307 self.resolve_expr_subqueries(a, cancel)?;
10308 }
10309 for p in partition_by {
10310 self.resolve_expr_subqueries(p, cancel)?;
10311 }
10312 for (e, _) in order_by {
10313 self.resolve_expr_subqueries(e, cancel)?;
10314 }
10315 }
10316 Expr::ScalarSubquery(_)
10320 | Expr::Exists { .. }
10321 | Expr::InSubquery { .. }
10322 | Expr::Literal(_)
10323 | Expr::Placeholder(_)
10324 | Expr::Column(_) => {}
10325 Expr::Array(items) => {
10327 for elem in items {
10328 self.resolve_expr_subqueries(elem, cancel)?;
10329 }
10330 }
10331 Expr::ArraySubscript { target, index } => {
10332 self.resolve_expr_subqueries(target, cancel)?;
10333 self.resolve_expr_subqueries(index, cancel)?;
10334 }
10335 Expr::AnyAll { expr, array, .. } => {
10336 self.resolve_expr_subqueries(expr, cancel)?;
10337 self.resolve_expr_subqueries(array, cancel)?;
10338 }
10339 Expr::Case {
10340 operand,
10341 branches,
10342 else_branch,
10343 } => {
10344 if let Some(o) = operand {
10345 self.resolve_expr_subqueries(o, cancel)?;
10346 }
10347 for (w, t) in branches {
10348 self.resolve_expr_subqueries(w, cancel)?;
10349 self.resolve_expr_subqueries(t, cancel)?;
10350 }
10351 if let Some(e) = else_branch {
10352 self.resolve_expr_subqueries(e, cancel)?;
10353 }
10354 }
10355 }
10356 Ok(())
10357 }
10358
10359 fn eval_expr_with_correlated(
10367 &self,
10368 expr: &Expr,
10369 row: &Row,
10370 ctx: &EvalContext<'_>,
10371 cancel: CancelToken<'_>,
10372 memo: Option<&mut memoize::MemoizeCache>,
10373 ) -> Result<Value, EngineError> {
10374 if !expr_has_subquery(expr) {
10375 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10376 }
10377 let mut e = expr.clone();
10378 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10379 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10380 }
10381
10382 fn resolve_correlated_in_expr(
10383 &self,
10384 e: &mut Expr,
10385 row: &Row,
10386 ctx: &EvalContext<'_>,
10387 cancel: CancelToken<'_>,
10388 mut memo: Option<&mut memoize::MemoizeCache>,
10389 ) -> Result<(), EngineError> {
10390 match e {
10391 Expr::ScalarSubquery(inner) => {
10392 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10397 subquery_repr: alloc::format!("{}", **inner),
10398 outer_values: row.values.clone(),
10399 });
10400 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10401 && let Some(cached) = cache.get(k)
10402 {
10403 *e = value_to_literal_expr(cached)?;
10404 return Ok(());
10405 }
10406 let mut s = (**inner).clone();
10407 substitute_outer_columns(&mut s, row, ctx);
10408 let r = self.exec_select_cancel(&s, cancel)?;
10409 let QueryResult::Rows { rows, .. } = r else {
10410 return Err(EngineError::Unsupported(
10411 "scalar subquery: inner did not return rows".into(),
10412 ));
10413 };
10414 let value = match rows.as_slice() {
10415 [] => Value::Null,
10416 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10417 _ => {
10418 return Err(EngineError::Unsupported(alloc::format!(
10419 "scalar subquery returned {} rows; expected 0 or 1",
10420 rows.len()
10421 )));
10422 }
10423 };
10424 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10425 cache.insert(k, value.clone());
10426 }
10427 *e = value_to_literal_expr(value)?;
10428 }
10429 Expr::Exists { subquery, negated } => {
10430 let mut s = (**subquery).clone();
10431 substitute_outer_columns(&mut s, row, ctx);
10432 let r = self.exec_select_cancel(&s, cancel)?;
10433 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10434 let bit = if *negated { !exists } else { exists };
10435 *e = Expr::Literal(Literal::Bool(bit));
10436 }
10437 Expr::InSubquery {
10438 expr: lhs,
10439 subquery,
10440 negated,
10441 } => {
10442 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10443 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10444 let mut s = (**subquery).clone();
10445 substitute_outer_columns(&mut s, row, ctx);
10446 let r = self.exec_select_cancel(&s, cancel)?;
10447 let QueryResult::Rows { columns, rows, .. } = r else {
10448 return Err(EngineError::Unsupported(
10449 "IN-subquery: inner did not return rows".into(),
10450 ));
10451 };
10452 if columns.len() != 1 {
10453 return Err(EngineError::Unsupported(alloc::format!(
10454 "IN-subquery must project exactly one column; got {}",
10455 columns.len()
10456 )));
10457 }
10458 let mut found = false;
10459 let mut any_null = false;
10460 for r0 in rows {
10461 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10462 if v.is_null() {
10463 any_null = true;
10464 continue;
10465 }
10466 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10467 found = true;
10468 break;
10469 }
10470 }
10471 let bit = if found {
10472 !*negated
10473 } else if any_null {
10474 return Err(EngineError::Unsupported(
10475 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10476 ));
10477 } else {
10478 *negated
10479 };
10480 *e = Expr::Literal(Literal::Bool(bit));
10481 }
10482 Expr::Binary { lhs, rhs, .. } => {
10483 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10484 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10485 }
10486 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10487 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10488 }
10489 Expr::Like { expr, pattern, .. } => {
10490 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10491 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10492 }
10493 Expr::FunctionCall { args, .. } => {
10494 for a in args {
10495 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10496 }
10497 }
10498 Expr::Extract { source, .. } => {
10499 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10500 }
10501 Expr::WindowFunction { .. }
10502 | Expr::Literal(_)
10503 | Expr::Placeholder(_)
10504 | Expr::Column(_) => {}
10505 Expr::Array(items) => {
10507 for elem in items {
10508 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10509 }
10510 }
10511 Expr::ArraySubscript { target, index } => {
10512 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10513 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10514 }
10515 Expr::AnyAll { expr, array, .. } => {
10516 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10517 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10518 }
10519 Expr::Case {
10520 operand,
10521 branches,
10522 else_branch,
10523 } => {
10524 if let Some(o) = operand {
10525 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10526 }
10527 for (w, t) in branches {
10528 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10529 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10530 }
10531 if let Some(e) = else_branch {
10532 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10533 }
10534 }
10535 }
10536 Ok(())
10537 }
10538
10539 fn subquery_replacement(
10540 &self,
10541 e: &Expr,
10542 cancel: CancelToken<'_>,
10543 ) -> Result<Option<Expr>, EngineError> {
10544 match e {
10545 Expr::ScalarSubquery(inner) => {
10546 let mut s = (**inner).clone();
10547 self.resolve_select_subqueries(&mut s, cancel)?;
10550 let r = match self.exec_bare_select_cancel(&s, cancel) {
10551 Ok(r) => r,
10552 Err(e) if is_correlation_error(&e) => return Ok(None),
10553 Err(e) => return Err(e),
10554 };
10555 let QueryResult::Rows { rows, .. } = r else {
10556 return Err(EngineError::Unsupported(
10557 "scalar subquery: inner statement did not return rows".into(),
10558 ));
10559 };
10560 let value = match rows.as_slice() {
10561 [] => Value::Null,
10562 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10563 _ => {
10564 return Err(EngineError::Unsupported(alloc::format!(
10565 "scalar subquery returned {} rows; expected 0 or 1",
10566 rows.len()
10567 )));
10568 }
10569 };
10570 Ok(Some(value_to_literal_expr(value)?))
10571 }
10572 Expr::Exists { subquery, negated } => {
10573 let mut s = (**subquery).clone();
10574 self.resolve_select_subqueries(&mut s, cancel)?;
10575 let r = match self.exec_bare_select_cancel(&s, cancel) {
10576 Ok(r) => r,
10577 Err(e) if is_correlation_error(&e) => return Ok(None),
10578 Err(e) => return Err(e),
10579 };
10580 let exists = match r {
10581 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10582 QueryResult::CommandOk { .. } => false,
10583 };
10584 let bit = if *negated { !exists } else { exists };
10585 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10586 }
10587 Expr::InSubquery {
10588 expr,
10589 subquery,
10590 negated,
10591 } => {
10592 let mut s = (**subquery).clone();
10593 self.resolve_select_subqueries(&mut s, cancel)?;
10594 let r = match self.exec_bare_select_cancel(&s, cancel) {
10595 Ok(r) => r,
10596 Err(e) if is_correlation_error(&e) => return Ok(None),
10597 Err(e) => return Err(e),
10598 };
10599 let QueryResult::Rows { columns, rows, .. } = r else {
10600 return Err(EngineError::Unsupported(
10601 "IN-subquery: inner statement did not return rows".into(),
10602 ));
10603 };
10604 if columns.len() != 1 {
10605 return Err(EngineError::Unsupported(alloc::format!(
10606 "IN-subquery must project exactly one column; got {}",
10607 columns.len()
10608 )));
10609 }
10610 let mut acc: Option<Expr> = None;
10613 for row in rows {
10614 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10615 let lit = value_to_literal_expr(v)?;
10616 let cmp = Expr::Binary {
10617 lhs: expr.clone(),
10618 op: BinOp::Eq,
10619 rhs: Box::new(lit),
10620 };
10621 acc = Some(match acc {
10622 None => cmp,
10623 Some(prev) => Expr::Binary {
10624 lhs: Box::new(prev),
10625 op: BinOp::Or,
10626 rhs: Box::new(cmp),
10627 },
10628 });
10629 }
10630 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10631 let final_expr = if *negated {
10632 Expr::Unary {
10633 op: UnOp::Not,
10634 expr: Box::new(combined),
10635 }
10636 } else {
10637 combined
10638 };
10639 Ok(Some(final_expr))
10640 }
10641 _ => Ok(None),
10642 }
10643 }
10644}
10645
10646fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10658 if let Some(from) = &stmt.from
10659 && from_refers_to(from, target)
10660 {
10661 return true;
10662 }
10663 for (_, peer) in &stmt.unions {
10664 if select_refers_to(peer, target) {
10665 return true;
10666 }
10667 }
10668 for item in &stmt.items {
10669 if let SelectItem::Expr { expr, .. } = item
10670 && expr_refers_to(expr, target)
10671 {
10672 return true;
10673 }
10674 }
10675 if let Some(w) = &stmt.where_
10676 && expr_refers_to(w, target)
10677 {
10678 return true;
10679 }
10680 false
10681}
10682
10683fn from_refers_to(from: &FromClause, target: &str) -> bool {
10684 if from.primary.name.eq_ignore_ascii_case(target) {
10685 return true;
10686 }
10687 from.joins
10688 .iter()
10689 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10690}
10691
10692fn expr_refers_to(e: &Expr, target: &str) -> bool {
10693 match e {
10694 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10695 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10696 select_refers_to(subquery, target)
10697 }
10698 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10699 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10700 expr_refers_to(expr, target)
10701 }
10702 Expr::Like { expr, pattern, .. } => {
10703 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10704 }
10705 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10706 Expr::Extract { source, .. } => expr_refers_to(source, target),
10707 Expr::WindowFunction {
10708 args,
10709 partition_by,
10710 order_by,
10711 ..
10712 } => {
10713 args.iter().any(|a| expr_refers_to(a, target))
10714 || partition_by.iter().any(|p| expr_refers_to(p, target))
10715 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10716 }
10717 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10718 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10719 Expr::ArraySubscript { target: t, index } => {
10720 expr_refers_to(t, target) || expr_refers_to(index, target)
10721 }
10722 Expr::AnyAll { expr, array, .. } => {
10723 expr_refers_to(expr, target) || expr_refers_to(array, target)
10724 }
10725 Expr::Case {
10726 operand,
10727 branches,
10728 else_branch,
10729 } => {
10730 operand
10731 .as_deref()
10732 .is_some_and(|o| expr_refers_to(o, target))
10733 || branches
10734 .iter()
10735 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10736 || else_branch
10737 .as_deref()
10738 .is_some_and(|e| expr_refers_to(e, target))
10739 }
10740 }
10741}
10742
10743fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10754 let s = match ty {
10755 DataType::Int => "integer",
10756 DataType::BigInt => "bigint",
10757 DataType::SmallInt => "smallint",
10758 DataType::Float => "double precision",
10759 DataType::Bool => "boolean",
10760 DataType::Text => "text",
10761 DataType::Varchar(_) => "character varying",
10762 DataType::Date => "date",
10763 DataType::Timestamp => "timestamp without time zone",
10764 DataType::Timestamptz => "timestamp with time zone",
10765 DataType::Json => "jsonb",
10766 DataType::Bytes => "bytea",
10767 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10768 DataType::TsVector => "tsvector",
10769 DataType::TsQuery => "tsquery",
10770 DataType::Vector { .. } => "USER-DEFINED",
10771 _ => "USER-DEFINED",
10774 };
10775 alloc::string::String::from(s)
10776}
10777
10778fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10785 let schema = alloc::vec![
10786 ColumnSchema::new("table_catalog", DataType::Text, false),
10787 ColumnSchema::new("table_schema", DataType::Text, false),
10788 ColumnSchema::new("table_name", DataType::Text, false),
10789 ColumnSchema::new("column_name", DataType::Text, false),
10790 ColumnSchema::new("ordinal_position", DataType::Int, false),
10791 ColumnSchema::new("is_nullable", DataType::Text, false),
10792 ColumnSchema::new("data_type", DataType::Text, false),
10793 ];
10794 let mut rows: Vec<Row> = Vec::new();
10795 for tname in cat.table_names() {
10796 let Some(t) = cat.get(&tname) else { continue };
10797 for (i, col) in t.schema().columns.iter().enumerate() {
10798 #[allow(clippy::cast_possible_wrap)]
10799 let ordinal = (i + 1) as i32;
10800 rows.push(Row::new(alloc::vec![
10801 Value::Text("spg".into()),
10802 Value::Text("public".into()),
10803 Value::Text(tname.clone()),
10804 Value::Text(col.name.clone()),
10805 Value::Int(ordinal),
10806 Value::Text(if col.nullable {
10807 "YES".into()
10808 } else {
10809 "NO".into()
10810 }),
10811 Value::Text(pg_data_type_text(col.ty)),
10812 ]));
10813 }
10814 }
10815 (schema, rows)
10816}
10817
10818fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10820 let schema = alloc::vec![
10821 ColumnSchema::new("table_catalog", DataType::Text, false),
10822 ColumnSchema::new("table_schema", DataType::Text, false),
10823 ColumnSchema::new("table_name", DataType::Text, false),
10824 ColumnSchema::new("table_type", DataType::Text, false),
10825 ];
10826 let mut rows: Vec<Row> = Vec::new();
10827 for tname in cat.table_names() {
10828 rows.push(Row::new(alloc::vec![
10829 Value::Text("spg".into()),
10830 Value::Text("public".into()),
10831 Value::Text(tname.clone()),
10832 Value::Text("BASE TABLE".into()),
10833 ]));
10834 }
10835 (schema, rows)
10836}
10837
10838fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10842 let schema = alloc::vec![
10843 ColumnSchema::new("relname", DataType::Text, false),
10844 ColumnSchema::new("relkind", DataType::Text, false),
10845 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10846 ];
10847 let mut rows: Vec<Row> = Vec::new();
10848 for tname in cat.table_names() {
10849 rows.push(Row::new(alloc::vec![
10850 Value::Text(tname.clone()),
10851 Value::Text("r".into()),
10852 Value::BigInt(2200), ]));
10854 }
10855 (schema, rows)
10856}
10857
10858fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10862 let schema = alloc::vec![
10863 ColumnSchema::new("attrelid", DataType::Text, false),
10864 ColumnSchema::new("attname", DataType::Text, false),
10865 ColumnSchema::new("attnum", DataType::Int, false),
10866 ColumnSchema::new("atttypid", DataType::Text, false),
10867 ColumnSchema::new("attnotnull", DataType::Bool, false),
10868 ];
10869 let mut rows: Vec<Row> = Vec::new();
10870 for tname in cat.table_names() {
10871 let Some(t) = cat.get(&tname) else { continue };
10872 for (i, col) in t.schema().columns.iter().enumerate() {
10873 #[allow(clippy::cast_possible_wrap)]
10874 let ordinal = (i + 1) as i32;
10875 rows.push(Row::new(alloc::vec![
10876 Value::Text(tname.clone()),
10877 Value::Text(col.name.clone()),
10878 Value::Int(ordinal),
10879 Value::Text(pg_data_type_text(col.ty)),
10880 Value::Bool(!col.nullable),
10881 ]));
10882 }
10883 }
10884 (schema, rows)
10885}
10886
10887fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10904 let schema = alloc::vec![
10905 ColumnSchema::new("oid", DataType::BigInt, false),
10906 ColumnSchema::new("typname", DataType::Text, false),
10907 ColumnSchema::new("typlen", DataType::SmallInt, false),
10908 ColumnSchema::new("typtype", DataType::Text, false),
10909 ColumnSchema::new("typcategory", DataType::Text, false),
10910 ColumnSchema::new("typelem", DataType::BigInt, false),
10911 ColumnSchema::new("typarray", DataType::BigInt, false),
10912 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10913 ];
10914 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10917 (16, "bool", 1, "b", "B", 0, 1000),
10919 (17, "bytea", -1, "b", "U", 0, 1001),
10920 (18, "char", 1, "b", "S", 0, 1002),
10921 (19, "name", 64, "b", "S", 0, 1003),
10922 (20, "int8", 8, "b", "N", 0, 1016),
10923 (21, "int2", 2, "b", "N", 0, 1005),
10924 (23, "int4", 4, "b", "N", 0, 1007),
10925 (24, "regproc", 4, "b", "N", 0, 1008),
10926 (25, "text", -1, "b", "S", 0, 1009),
10927 (26, "oid", 4, "b", "N", 0, 1028),
10928 (114, "json", -1, "b", "U", 0, 199),
10929 (142, "xml", -1, "b", "U", 0, 143),
10930 (700, "float4", 4, "b", "N", 0, 1021),
10931 (701, "float8", 8, "b", "N", 0, 1022),
10932 (650, "cidr", -1, "b", "I", 0, 651),
10933 (869, "inet", -1, "b", "I", 0, 1041),
10934 (829, "macaddr", 6, "b", "U", 0, 1040),
10935 (1042, "bpchar", -1, "b", "S", 0, 1014),
10936 (1043, "varchar", -1, "b", "S", 0, 1015),
10937 (1082, "date", 4, "b", "D", 0, 1182),
10938 (1083, "time", 8, "b", "D", 0, 1183),
10939 (1114, "timestamp", 8, "b", "D", 0, 1115),
10940 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10941 (1186, "interval", 16, "b", "T", 0, 1187),
10942 (1266, "timetz", 12, "b", "D", 0, 1270),
10943 (1700, "numeric", -1, "b", "N", 0, 1231),
10944 (790, "money", 8, "b", "N", 0, 791),
10945 (2950, "uuid", 16, "b", "U", 0, 2951),
10946 (3802, "jsonb", -1, "b", "U", 0, 3807),
10947 (3614, "tsvector", -1, "b", "U", 0, 3643),
10948 (3615, "tsquery", -1, "b", "U", 0, 3645),
10949 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10951 (3910, "tsrange", -1, "r", "R", 0, 3911),
10952 (3904, "int4range", -1, "r", "R", 0, 3905),
10953 (3926, "int8range", -1, "r", "R", 0, 3927),
10954 (3906, "numrange", -1, "r", "R", 0, 3907),
10955 (3912, "daterange", -1, "r", "R", 0, 3913),
10956 ];
10957 let arrays: &[(i64, &str, i64)] = &[
10960 (1000, "_bool", 16),
10961 (1001, "_bytea", 17),
10962 (1002, "_char", 18),
10963 (1003, "_name", 19),
10964 (1016, "_int8", 20),
10965 (1005, "_int2", 21),
10966 (1007, "_int4", 23),
10967 (1008, "_regproc", 24),
10968 (1009, "_text", 25),
10969 (1028, "_oid", 26),
10970 (199, "_json", 114),
10971 (143, "_xml", 142),
10972 (1021, "_float4", 700),
10973 (1022, "_float8", 701),
10974 (651, "_cidr", 650),
10975 (1041, "_inet", 869),
10976 (1040, "_macaddr", 829),
10977 (1014, "_bpchar", 1042),
10978 (1015, "_varchar", 1043),
10979 (1182, "_date", 1082),
10980 (1183, "_time", 1083),
10981 (1115, "_timestamp", 1114),
10982 (1185, "_timestamptz", 1184),
10983 (1187, "_interval", 1186),
10984 (1270, "_timetz", 1266),
10985 (1231, "_numeric", 1700),
10986 (791, "_money", 790),
10987 (2951, "_uuid", 2950),
10988 (3807, "_jsonb", 3802),
10989 (3643, "_tsvector", 3614),
10990 (3645, "_tsquery", 3615),
10991 ];
10992 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10993 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10994 rows.push(Row::new(alloc::vec![
10995 Value::BigInt(oid),
10996 Value::Text(name.into()),
10997 Value::SmallInt(len),
10998 Value::Text(ty.into()),
10999 Value::Text(cat.into()),
11000 Value::BigInt(elem),
11001 Value::BigInt(arr),
11002 Value::BigInt(2200),
11003 ]));
11004 }
11005 for &(oid, name, elem) in arrays {
11006 rows.push(Row::new(alloc::vec![
11007 Value::BigInt(oid),
11008 Value::Text(name.into()),
11009 Value::SmallInt(-1),
11010 Value::Text("b".into()),
11011 Value::Text("A".into()),
11012 Value::BigInt(elem),
11013 Value::BigInt(0),
11014 Value::BigInt(2200),
11015 ]));
11016 }
11017 (schema, rows)
11018}
11019
11020fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11034 let schema = alloc::vec![
11035 ColumnSchema::new("oid", DataType::BigInt, false),
11036 ColumnSchema::new("proname", DataType::Text, false),
11037 ColumnSchema::new("pronamespace", DataType::BigInt, false),
11038 ColumnSchema::new("prokind", DataType::Text, false),
11039 ColumnSchema::new("pronargs", DataType::Int, false),
11040 ColumnSchema::new("prorettype", DataType::BigInt, false),
11041 ];
11042 let funcs: &[(i64, &str, &str, i32, i64)] = &[
11045 (1318, "length", "f", 1, 23),
11047 (871, "upper", "f", 1, 25),
11048 (870, "lower", "f", 1, 25),
11049 (936, "substring", "f", 3, 25),
11050 (937, "substring", "f", 2, 25),
11051 (3055, "btrim", "f", 1, 25),
11052 (885, "btrim", "f", 2, 25),
11053 (3056, "ltrim", "f", 1, 25),
11054 (875, "ltrim", "f", 2, 25),
11055 (3057, "rtrim", "f", 1, 25),
11056 (876, "rtrim", "f", 2, 25),
11057 (1397, "abs", "f", 1, 23),
11058 (1396, "abs", "f", 1, 20),
11059 (1606, "round", "f", 1, 1700),
11060 (1707, "round", "f", 2, 1700),
11061 (2308, "ceil", "f", 1, 701),
11062 (2309, "ceiling", "f", 1, 701),
11063 (2310, "floor", "f", 1, 701),
11064 (1376, "sqrt", "f", 1, 701),
11065 (1369, "ln", "f", 1, 701),
11066 (1373, "exp", "f", 1, 701),
11067 (1368, "power", "f", 2, 701),
11068 (2228, "random", "f", 0, 701),
11069 (1299, "now", "f", 0, 1184),
11071 (1274, "current_timestamp", "f", 0, 1184),
11072 (1140, "current_date", "f", 0, 1082),
11073 (2050, "current_time", "f", 0, 1083),
11074 (1158, "date_trunc", "f", 2, 1184),
11075 (1171, "date_part", "f", 2, 701),
11076 (1172, "age", "f", 1, 1186),
11077 (936, "to_char", "f", 2, 25),
11078 (861, "current_database", "f", 0, 19),
11080 (745, "current_user", "f", 0, 19),
11081 (745, "session_user", "f", 0, 19),
11082 (1402, "current_schema", "f", 0, 19),
11083 (3058, "concat", "f", -1, 25),
11085 (3059, "concat_ws", "f", -1, 25),
11086 (3539, "format", "f", -1, 25),
11087 (2877, "pg_typeof", "f", 1, 2206),
11089 (3198, "json_build_object", "f", -1, 114),
11091 (3199, "jsonb_build_object", "f", -1, 3802),
11092 (3271, "json_build_array", "f", -1, 114),
11093 (3272, "jsonb_build_array", "f", -1, 3802),
11094 (3253, "gen_random_uuid", "f", 0, 2950),
11096 (3252, "uuid_generate_v4", "f", 0, 2950),
11097 (2147, "count", "a", 0, 20),
11099 (2803, "count", "a", -1, 20),
11100 (2116, "max", "a", 1, 23),
11101 (2132, "min", "a", 1, 23),
11102 (2108, "sum", "a", 1, 20),
11103 (2100, "avg", "a", 1, 1700),
11104 (2517, "string_agg", "a", 2, 25),
11105 (2747, "array_agg", "a", 1, 1009),
11106 (2517, "bool_and", "a", 1, 16),
11107 (2518, "bool_or", "a", 1, 16),
11108 (2519, "every", "a", 1, 16),
11109 (3100, "row_number", "w", 0, 20),
11111 (3101, "rank", "w", 0, 20),
11112 (3102, "dense_rank", "w", 0, 20),
11113 (3103, "percent_rank", "w", 0, 701),
11114 (3104, "cume_dist", "w", 0, 701),
11115 (3105, "lag", "w", -1, 2283),
11116 (3106, "lead", "w", -1, 2283),
11117 (3107, "first_value", "w", 1, 2283),
11118 (3108, "last_value", "w", 1, 2283),
11119 (3109, "nth_value", "w", 2, 2283),
11120 ];
11121 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11122 for &(oid, name, kind, nargs, rettype) in funcs {
11123 rows.push(Row::new(alloc::vec![
11124 Value::BigInt(oid),
11125 Value::Text(name.into()),
11126 Value::BigInt(11),
11127 Value::Text(kind.into()),
11128 Value::Int(nargs),
11129 Value::BigInt(rettype),
11130 ]));
11131 }
11132 (schema, rows)
11133}
11134
11135fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11141 let schema = alloc::vec![
11142 ColumnSchema::new("user", DataType::Text, false),
11143 ColumnSchema::new("host", DataType::Text, false),
11144 ColumnSchema::new("select_priv", DataType::Text, false),
11145 ];
11146 let mut rows: Vec<Row> = Vec::new();
11147 rows.push(Row::new(alloc::vec![
11148 Value::Text("root".into()),
11149 Value::Text("localhost".into()),
11150 Value::Text("Y".into()),
11151 ]));
11152 for (name, _) in engine.users.iter() {
11153 if name != "root" {
11154 rows.push(Row::new(alloc::vec![
11155 Value::Text(name.to_string()),
11156 Value::Text("%".into()),
11157 Value::Text("Y".into()),
11158 ]));
11159 }
11160 }
11161 (schema, rows)
11162}
11163
11164fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11169 let schema = alloc::vec![
11170 ColumnSchema::new("host", DataType::Text, false),
11171 ColumnSchema::new("db", DataType::Text, false),
11172 ColumnSchema::new("user", DataType::Text, false),
11173 ColumnSchema::new("select_priv", DataType::Text, false),
11174 ];
11175 let rows = alloc::vec![Row::new(alloc::vec![
11176 Value::Text("localhost".into()),
11177 Value::Text("postgres".into()),
11178 Value::Text("root".into()),
11179 Value::Text("Y".into()),
11180 ])];
11181 (schema, rows)
11182}
11183
11184fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11197 let schema = alloc::vec![
11198 ColumnSchema::new("constraint_name", DataType::Text, false),
11199 ColumnSchema::new("table_name", DataType::Text, false),
11200 ColumnSchema::new("column_name", DataType::Text, false),
11201 ColumnSchema::new("ordinal_position", DataType::Int, false),
11202 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11203 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11204 ];
11205 let mut rows: Vec<Row> = Vec::new();
11206 for tname in cat.table_names() {
11207 let Some(t) = cat.get(&tname) else { continue };
11208 let cols = &t.schema().columns;
11209 let col_name_at = |pos: usize| -> String {
11210 cols.get(pos)
11211 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11212 };
11213 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11215 let conname = fk
11216 .name
11217 .clone()
11218 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11219 for (i, (&local, &parent)) in fk
11220 .local_columns
11221 .iter()
11222 .zip(fk.parent_columns.iter())
11223 .enumerate()
11224 {
11225 let parent_name = cat
11226 .get(&fk.parent_table)
11227 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11228 .unwrap_or_else(|| alloc::format!("col{parent}"));
11229 #[allow(clippy::cast_possible_wrap)]
11230 let ordinal = (i + 1) as i32;
11231 rows.push(Row::new(alloc::vec![
11232 Value::Text(conname.clone()),
11233 Value::Text(tname.clone()),
11234 Value::Text(col_name_at(local)),
11235 Value::Int(ordinal),
11236 Value::Text(fk.parent_table.clone()),
11237 Value::Text(parent_name),
11238 ]));
11239 }
11240 }
11241 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11243 let conname = if uc.is_primary_key {
11244 alloc::format!("{}_pkey", tname)
11245 } else {
11246 alloc::format!("{}_uniq{ci}", tname)
11247 };
11248 for (i, &local) in uc.columns.iter().enumerate() {
11249 #[allow(clippy::cast_possible_wrap)]
11250 let ordinal = (i + 1) as i32;
11251 rows.push(Row::new(alloc::vec![
11252 Value::Text(conname.clone()),
11253 Value::Text(tname.clone()),
11254 Value::Text(col_name_at(local)),
11255 Value::Int(ordinal),
11256 Value::Text(String::new()),
11257 Value::Text(String::new()),
11258 ]));
11259 }
11260 }
11261 }
11262 (schema, rows)
11263}
11264
11265fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11268 let schema = alloc::vec![
11269 ColumnSchema::new("constraint_name", DataType::Text, false),
11270 ColumnSchema::new("table_name", DataType::Text, false),
11271 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11272 ColumnSchema::new("update_rule", DataType::Text, false),
11273 ColumnSchema::new("delete_rule", DataType::Text, false),
11274 ];
11275 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11276 match a {
11277 spg_storage::FkAction::Cascade => "CASCADE",
11278 spg_storage::FkAction::SetNull => "SET NULL",
11279 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11280 spg_storage::FkAction::Restrict => "RESTRICT",
11281 spg_storage::FkAction::NoAction => "NO ACTION",
11282 }
11283 }
11284 let mut rows: Vec<Row> = Vec::new();
11285 for tname in cat.table_names() {
11286 let Some(t) = cat.get(&tname) else { continue };
11287 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11288 let conname = fk
11289 .name
11290 .clone()
11291 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11292 rows.push(Row::new(alloc::vec![
11293 Value::Text(conname),
11294 Value::Text(tname.clone()),
11295 Value::Text(fk.parent_table.clone()),
11296 Value::Text(rule_name(fk.on_update).into()),
11297 Value::Text(rule_name(fk.on_delete).into()),
11298 ]));
11299 }
11300 }
11301 (schema, rows)
11302}
11303
11304fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11308 let schema = alloc::vec![
11309 ColumnSchema::new("table_name", DataType::Text, false),
11310 ColumnSchema::new("index_name", DataType::Text, false),
11311 ColumnSchema::new("column_name", DataType::Text, false),
11312 ColumnSchema::new("seq_in_index", DataType::Int, false),
11313 ColumnSchema::new("non_unique", DataType::Int, false),
11314 ColumnSchema::new("index_type", DataType::Text, false),
11315 ];
11316 let mut rows: Vec<Row> = Vec::new();
11317 for tname in cat.table_names() {
11318 let Some(t) = cat.get(&tname) else { continue };
11319 for idx in t.indices() {
11320 let col = t
11321 .schema()
11322 .columns
11323 .get(idx.column_position)
11324 .map_or("?".into(), |c| c.name.clone());
11325 rows.push(Row::new(alloc::vec![
11326 Value::Text(tname.clone()),
11327 Value::Text(idx.name.clone()),
11328 Value::Text(col),
11329 Value::Int(1),
11330 Value::Int(i32::from(!idx.is_unique)),
11331 Value::Text("BTREE".into()),
11332 ]));
11333 }
11334 }
11335 (schema, rows)
11336}
11337
11338fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11342 let schema = alloc::vec![
11343 ColumnSchema::new("routine_name", DataType::Text, false),
11344 ColumnSchema::new("routine_type", DataType::Text, false),
11345 ColumnSchema::new("data_type", DataType::Text, false),
11346 ];
11347 (schema, Vec::new())
11348}
11349
11350fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11365 let schema = alloc::vec![
11366 ColumnSchema::new("conname", DataType::Text, false),
11367 ColumnSchema::new("contype", DataType::Text, false),
11368 ColumnSchema::new("conrelid", DataType::Text, false),
11369 ColumnSchema::new("confrelid", DataType::Text, false),
11370 ColumnSchema::new("conkey", DataType::Text, false),
11371 ColumnSchema::new("confkey", DataType::Text, false),
11372 ];
11373 let mut rows: Vec<Row> = Vec::new();
11374 for tname in cat.table_names() {
11375 let Some(t) = cat.get(&tname) else { continue };
11376 let cols = &t.schema().columns;
11377 let col_name_at = |pos: usize| -> String {
11378 cols.get(pos)
11379 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11380 };
11381 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11383 let kind = if uc.is_primary_key { "p" } else { "u" };
11384 let conname = if uc.is_primary_key {
11385 alloc::format!("{}_pkey", tname)
11386 } else {
11387 alloc::format!("{}_uniq{ci}", tname)
11388 };
11389 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11390 rows.push(Row::new(alloc::vec![
11391 Value::Text(conname),
11392 Value::Text(kind.into()),
11393 Value::Text(tname.clone()),
11394 Value::Text(String::new()),
11395 Value::Text(conkey.join(",")),
11396 Value::Text(String::new()),
11397 ]));
11398 }
11399 for idx in t.indices() {
11404 if !idx.is_unique {
11405 continue;
11406 }
11407 let is_primary = idx.name.ends_with("_pkey");
11408 let conname = idx.name.clone();
11409 let kind = if is_primary { "p" } else { "u" };
11410 let col_name = col_name_at(idx.column_position);
11411 let already = t
11414 .schema()
11415 .uniqueness_constraints
11416 .iter()
11417 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11418 if already {
11419 continue;
11420 }
11421 rows.push(Row::new(alloc::vec![
11422 Value::Text(conname),
11423 Value::Text(kind.into()),
11424 Value::Text(tname.clone()),
11425 Value::Text(String::new()),
11426 Value::Text(col_name),
11427 Value::Text(String::new()),
11428 ]));
11429 }
11430 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11432 let conname = fk
11433 .name
11434 .clone()
11435 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11436 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11437 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11440 fk.parent_columns
11441 .iter()
11442 .map(|&p| {
11443 parent
11444 .schema()
11445 .columns
11446 .get(p)
11447 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11448 })
11449 .collect()
11450 } else {
11451 fk.parent_columns
11452 .iter()
11453 .map(|p| alloc::format!("col{p}"))
11454 .collect()
11455 };
11456 rows.push(Row::new(alloc::vec![
11457 Value::Text(conname),
11458 Value::Text("f".into()),
11459 Value::Text(tname.clone()),
11460 Value::Text(fk.parent_table.clone()),
11461 Value::Text(conkey.join(",")),
11462 Value::Text(confkey.join(",")),
11463 ]));
11464 }
11465 }
11466 (schema, rows)
11467}
11468
11469fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11474 let schema = alloc::vec![
11475 ColumnSchema::new("oid", DataType::BigInt, false),
11476 ColumnSchema::new("datname", DataType::Text, false),
11477 ColumnSchema::new("datdba", DataType::BigInt, false),
11478 ColumnSchema::new("encoding", DataType::Int, false),
11479 ColumnSchema::new("datcollate", DataType::Text, false),
11480 ];
11481 let rows = alloc::vec![Row::new(alloc::vec![
11482 Value::BigInt(16384),
11483 Value::Text("postgres".into()),
11484 Value::BigInt(10),
11485 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11487 ])];
11488 (schema, rows)
11489}
11490
11491fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11496 let schema = alloc::vec![
11497 ColumnSchema::new("oid", DataType::BigInt, false),
11498 ColumnSchema::new("rolname", DataType::Text, false),
11499 ColumnSchema::new("rolsuper", DataType::Bool, false),
11500 ColumnSchema::new("rolinherit", DataType::Bool, false),
11501 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11502 ];
11503 let mut rows: Vec<Row> = Vec::new();
11504 let oid: i64 = 10;
11505 for (i, (name, _)) in engine.users.iter().enumerate() {
11506 rows.push(Row::new(alloc::vec![
11507 Value::BigInt(oid + (i as i64) + 1),
11508 Value::Text(name.to_string()),
11509 Value::Bool(false),
11510 Value::Bool(true),
11511 Value::Bool(true),
11512 ]));
11513 }
11514 if !rows
11517 .iter()
11518 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11519 {
11520 rows.insert(
11521 0,
11522 Row::new(alloc::vec![
11523 Value::BigInt(10),
11524 Value::Text("postgres".into()),
11525 Value::Bool(true),
11526 Value::Bool(true),
11527 Value::Bool(true),
11528 ]),
11529 );
11530 }
11531 (schema, rows)
11532}
11533
11534fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11543 let schema = alloc::vec![
11544 ColumnSchema::new("oid", DataType::BigInt, false),
11545 ColumnSchema::new("extname", DataType::Text, false),
11546 ColumnSchema::new("extversion", DataType::Text, false),
11547 ColumnSchema::new("extnamespace", DataType::Text, false),
11548 ];
11549 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11550 let rows = exts
11551 .iter()
11552 .enumerate()
11553 .map(|(i, (name, ver))| {
11554 Row::new(alloc::vec![
11555 Value::BigInt(16384 + i as i64),
11556 Value::Text((*name).into()),
11557 Value::Text((*ver).into()),
11558 Value::Text("pg_catalog".into()),
11559 ])
11560 })
11561 .collect();
11562 (schema, rows)
11563}
11564
11565fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11566 let schema = alloc::vec![
11567 ColumnSchema::new("schemaname", DataType::Text, false),
11568 ColumnSchema::new("viewname", DataType::Text, false),
11569 ColumnSchema::new("definition", DataType::Text, false),
11570 ];
11571 let mut rows: Vec<Row> = Vec::new();
11572 for (name, def) in cat.views() {
11573 rows.push(Row::new(alloc::vec![
11574 Value::Text("public".into()),
11575 Value::Text(name.clone()),
11576 Value::Text(def.body.clone()),
11577 ]));
11578 }
11579 (schema, rows)
11580}
11581
11582fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11588 let schema = alloc::vec![
11589 ColumnSchema::new("name", DataType::Text, false),
11590 ColumnSchema::new("setting", DataType::Text, false),
11591 ColumnSchema::new("category", DataType::Text, false),
11592 ];
11593 let mut rows: Vec<Row> = Vec::new();
11594 let defaults: &[(&str, &str, &str)] = &[
11596 ("server_version", "16.0 (spg)", "Preset Options"),
11597 ("server_encoding", "UTF8", "Client Connection Defaults"),
11598 ("client_encoding", "UTF8", "Client Connection Defaults"),
11599 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11600 ("TimeZone", "UTC", "Client Connection Defaults"),
11601 ("standard_conforming_strings", "on", "Compatibility"),
11602 ("integer_datetimes", "on", "Compatibility"),
11603 ("max_connections", "100", "Connections and Authentication"),
11604 ];
11605 for &(name, val, cat) in defaults {
11606 rows.push(Row::new(alloc::vec![
11607 Value::Text(name.into()),
11608 Value::Text(val.into()),
11609 Value::Text(cat.into()),
11610 ]));
11611 }
11612 for (k, v) in &engine.session_params {
11614 if !defaults
11615 .iter()
11616 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11617 {
11618 rows.push(Row::new(alloc::vec![
11619 Value::Text(k.clone()),
11620 Value::Text(v.clone()),
11621 Value::Text("Session".into()),
11622 ]));
11623 }
11624 }
11625 (schema, rows)
11626}
11627
11628fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11639 let schema = alloc::vec![
11640 ColumnSchema::new("schemaname", DataType::Text, false),
11641 ColumnSchema::new("tablename", DataType::Text, false),
11642 ColumnSchema::new("indexname", DataType::Text, false),
11643 ColumnSchema::new("indexdef", DataType::Text, false),
11644 ];
11645 let mut rows: Vec<Row> = Vec::new();
11646 for tname in cat.table_names() {
11647 let Some(t) = cat.get(&tname) else { continue };
11648 for idx in t.indices() {
11649 let col_name = t
11650 .schema()
11651 .columns
11652 .get(idx.column_position)
11653 .map_or("?".into(), |c| c.name.clone());
11654 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11655 let indexdef = alloc::format!(
11656 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11657 idx.name,
11658 tname,
11659 col_name
11660 );
11661 rows.push(Row::new(alloc::vec![
11662 Value::Text("public".into()),
11663 Value::Text(tname.clone()),
11664 Value::Text(idx.name.clone()),
11665 Value::Text(indexdef),
11666 ]));
11667 }
11668 }
11669 (schema, rows)
11670}
11671
11672fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11684 let schema = alloc::vec![
11685 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11686 ColumnSchema::new("indrelid", DataType::BigInt, false),
11687 ColumnSchema::new("indnatts", DataType::Int, false),
11688 ColumnSchema::new("indisunique", DataType::Bool, false),
11689 ColumnSchema::new("indisprimary", DataType::Bool, false),
11690 ];
11691 let mut rows: Vec<Row> = Vec::new();
11692 let mut idx_oid: i64 = 100_000;
11693 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11694 let Some(t) = cat.get(tname) else { continue };
11695 for idx in t.indices() {
11696 idx_oid += 1;
11697 #[allow(clippy::cast_possible_wrap)]
11698 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11699 let is_primary = idx.name.ends_with("_pkey");
11702 rows.push(Row::new(alloc::vec![
11703 Value::BigInt(idx_oid),
11704 Value::BigInt((table_idx + 1) as i64),
11705 Value::Int(nattrs),
11706 Value::Bool(idx.is_unique),
11707 Value::Bool(is_primary),
11708 ]));
11709 }
11710 }
11711 (schema, rows)
11712}
11713
11714fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11719 let schema = alloc::vec![
11720 ColumnSchema::new("oid", DataType::BigInt, false),
11721 ColumnSchema::new("nspname", DataType::Text, false),
11722 ColumnSchema::new("nspowner", DataType::BigInt, false),
11723 ];
11724 let rows = alloc::vec![
11725 Row::new(alloc::vec![
11726 Value::BigInt(11),
11727 Value::Text("pg_catalog".into()),
11728 Value::BigInt(10),
11729 ]),
11730 Row::new(alloc::vec![
11731 Value::BigInt(2200),
11732 Value::Text("public".into()),
11733 Value::BigInt(10),
11734 ]),
11735 Row::new(alloc::vec![
11736 Value::BigInt(13000),
11737 Value::Text("information_schema".into()),
11738 Value::BigInt(10),
11739 ]),
11740 ];
11741 (schema, rows)
11742}
11743
11744fn materialise_meta_view(
11747 catalog: &mut Catalog,
11748 name: &str,
11749 columns: Vec<ColumnSchema>,
11750 rows: Vec<Row>,
11751) -> Result<(), EngineError> {
11752 let schema = TableSchema::new(name.to_string(), columns);
11753 catalog.create_table(schema).map_err(EngineError::Storage)?;
11754 let table = catalog
11755 .get_mut(name)
11756 .expect("just-created meta view must exist");
11757 for row in rows {
11758 table.insert(row).map_err(EngineError::Storage)?;
11759 }
11760 Ok(())
11761}
11762
11763fn collect_view_refs(
11776 tref: &spg_sql::ast::TableRef,
11777 cat: &spg_storage::Catalog,
11778 into: &mut Vec<String>,
11779) {
11780 if cat.views().contains_key(&tref.name)
11781 && cat.get(&tref.name).is_none()
11782 && !into.iter().any(|n| n == &tref.name)
11783 {
11784 into.push(tref.name.clone());
11785 }
11786}
11787
11788fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11789 fn is_meta(name: &str) -> bool {
11790 name.starts_with("__spg_info_")
11791 || name.starts_with("__spg_pg_")
11792 || name.starts_with("__spg_mysql_")
11793 }
11794 if let Some(from) = &stmt.from {
11795 if is_meta(&from.primary.name) {
11796 return true;
11797 }
11798 for j in &from.joins {
11799 if is_meta(&j.table.name) {
11800 return true;
11801 }
11802 }
11803 }
11804 for cte in &stmt.ctes {
11805 if select_references_meta_view(&cte.body) {
11806 return true;
11807 }
11808 }
11809 false
11810}
11811
11812fn collect_meta_view_names(
11817 stmt: &SelectStatement,
11818 into: &mut alloc::collections::BTreeSet<String>,
11819) {
11820 fn is_meta(name: &str) -> bool {
11821 name.starts_with("__spg_info_")
11822 || name.starts_with("__spg_pg_")
11823 || name.starts_with("__spg_mysql_")
11824 }
11825 if let Some(from) = &stmt.from {
11826 if is_meta(&from.primary.name) {
11827 into.insert(from.primary.name.clone());
11828 }
11829 for j in &from.joins {
11830 if is_meta(&j.table.name) {
11831 into.insert(j.table.name.clone());
11832 }
11833 }
11834 }
11835 for cte in &stmt.ctes {
11836 collect_meta_view_names(&cte.body, into);
11837 }
11838}
11839
11840fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11841 let mut out = columns.to_vec();
11842 for (col_idx, col) in out.iter_mut().enumerate() {
11843 if col.ty != DataType::Text {
11844 continue;
11845 }
11846 let mut inferred: Option<DataType> = None;
11847 let mut all_null = true;
11848 for row in rows {
11849 let Some(v) = row.values.get(col_idx) else {
11850 continue;
11851 };
11852 let ty = match v {
11853 Value::Null => continue,
11854 Value::SmallInt(_) => DataType::SmallInt,
11855 Value::Int(_) => DataType::Int,
11856 Value::BigInt(_) => DataType::BigInt,
11857 Value::Float(_) => DataType::Float,
11858 Value::Bool(_) => DataType::Bool,
11859 Value::Vector(_) => DataType::Vector {
11860 dim: 0,
11861 encoding: VecEncoding::F32,
11862 },
11863 _ => DataType::Text,
11864 };
11865 all_null = false;
11866 inferred = Some(match inferred {
11867 None => ty,
11868 Some(prev) if prev == ty => prev,
11869 Some(_) => DataType::Text,
11870 });
11871 }
11872 if let Some(t) = inferred {
11873 col.ty = t;
11874 col.nullable = true;
11875 } else if all_null {
11876 col.nullable = true;
11877 }
11878 }
11879 out
11880}
11881
11882#[allow(clippy::too_many_lines, clippy::format_push_string)]
11887fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11904 use alloc::collections::BTreeSet;
11905 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11906 let mut out: Vec<String> = Vec::new();
11907 let cat = engine.active_catalog();
11908 let Some(from) = &stmt.from else {
11912 return out;
11913 };
11914 let mut tables: Vec<String> = Vec::new();
11915 tables.push(from.primary.name.clone());
11916 for j in &from.joins {
11917 tables.push(j.table.name.clone());
11918 }
11919 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11922 if let Some(w) = &stmt.where_ {
11923 collect_column_refs(w, &mut col_refs);
11924 }
11925 for j in &from.joins {
11926 if let Some(on) = &j.on {
11927 collect_column_refs(on, &mut col_refs);
11928 }
11929 }
11930 for cn in &col_refs {
11931 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11934 tables.iter().find(|t| t == &q).cloned()
11935 } else {
11936 tables.iter().find_map(|t| {
11937 cat.get(t).and_then(|tbl| {
11938 if tbl.schema().column_position(&cn.name).is_some() {
11939 Some(t.clone())
11940 } else {
11941 None
11942 }
11943 })
11944 })
11945 };
11946 let Some(owner) = owner else {
11947 continue;
11948 };
11949 let Some(tbl) = cat.get(&owner) else {
11950 continue;
11951 };
11952 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11953 continue;
11954 };
11955 let already_indexed = tbl.indices().iter().any(|i| {
11958 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11959 && i.column_position == col_pos
11960 && i.expression.is_none()
11961 && i.partial_predicate.is_none()
11962 });
11963 if already_indexed {
11964 continue;
11965 }
11966 if seen.insert((owner.clone(), cn.name.clone())) {
11967 out.push(alloc::format!(
11968 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11969 owner,
11970 cn.name,
11971 owner,
11972 cn.name
11973 ));
11974 }
11975 }
11976 out
11977}
11978
11979fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11982 match expr {
11983 Expr::Column(cn) => out.push(cn.clone()),
11984 Expr::FunctionCall { args, .. } => {
11985 for a in args {
11986 collect_column_refs(a, out);
11987 }
11988 }
11989 Expr::Binary { lhs, rhs, .. } => {
11990 collect_column_refs(lhs, out);
11991 collect_column_refs(rhs, out);
11992 }
11993 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11994 _ => {}
11995 }
11996}
11997
11998fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11999 let catalog = engine.active_catalog();
12000 let cold_ids = catalog.cold_segment_ids_global();
12001 let any_cold = !cold_ids.is_empty();
12002 let cold_ids_repr = if any_cold {
12003 let mut s = alloc::string::String::from("[");
12004 for (i, id) in cold_ids.iter().enumerate() {
12005 if i > 0 {
12006 s.push(',');
12007 }
12008 s.push_str(&alloc::format!("{id}"));
12009 }
12010 s.push(']');
12011 s
12012 } else {
12013 alloc::string::String::new()
12014 };
12015 for (idx, line) in lines.iter_mut().enumerate() {
12016 let trimmed = line.trim_start();
12017 let is_top_level = idx == 0;
12018 if is_top_level {
12019 line.push_str(&alloc::format!(" (rows={total_rows})"));
12020 continue;
12021 }
12022 if let Some(rest) = trimmed.strip_prefix("From: ") {
12023 let (name, scan_kind) = match rest.split_once(" [") {
12024 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
12025 None => (rest.trim(), ""),
12026 };
12027 let bare = name.split_whitespace().next().unwrap_or(name);
12028 let hot = catalog.get(bare).map(|t| t.rows().len());
12029 let annot = match (hot, scan_kind) {
12034 (Some(h), "full scan") => {
12035 let mut s = alloc::format!(" (hot_rows={h}");
12036 if any_cold {
12037 s.push_str(&alloc::format!(
12038 ", cold_tier=present, cold_segments={cold_ids_repr}"
12039 ));
12040 }
12041 s.push(')');
12042 s
12043 }
12044 (Some(h), "index seek") => {
12045 let mut s = alloc::format!(" (hot_rows≤{h}");
12046 if any_cold {
12047 s.push_str(&alloc::format!(
12048 ", cold_tier=present, cold_segments={cold_ids_repr}"
12049 ));
12050 }
12051 s.push(')');
12052 s
12053 }
12054 _ => " (rows=—)".to_string(),
12055 };
12056 line.push_str(&annot);
12057 continue;
12058 }
12059 line.push_str(" (rows=—)");
12061 }
12062}
12063
12064fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
12065 let pad = " ".repeat(depth);
12066 let top = if !stmt.ctes.is_empty() {
12068 if stmt.ctes.iter().any(|c| c.recursive) {
12069 "CTEScan (WITH RECURSIVE)"
12070 } else {
12071 "CTEScan (WITH)"
12072 }
12073 } else if !stmt.unions.is_empty() {
12074 "UnionScan"
12075 } else if select_has_window(stmt) {
12076 "WindowAgg"
12077 } else if aggregate::uses_aggregate(stmt) {
12078 "Aggregate"
12079 } else if stmt.distinct {
12080 "Distinct"
12081 } else if stmt.from.is_some() {
12082 "TableScan"
12083 } else {
12084 "Result"
12085 };
12086 out.push(alloc::format!("{pad}{top}"));
12087 let child = " ".repeat(depth + 1);
12088 for cte in &stmt.ctes {
12090 let head = if cte.recursive {
12091 alloc::format!("{child}CTE (recursive): {}", cte.name)
12092 } else {
12093 alloc::format!("{child}CTE: {}", cte.name)
12094 };
12095 out.push(head);
12096 explain_select(&cte.body, engine, depth + 2, out);
12097 }
12098 if let Some(from) = &stmt.from {
12100 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12101 if let Some(alias) = &from.primary.alias {
12102 tag.push_str(&alloc::format!(" AS {alias}"));
12103 }
12104 if let Some(w) = &stmt.where_
12107 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12108 {
12109 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12110 let cols = &table.schema().columns;
12111 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12112 tag.push_str(" [index seek]");
12113 } else {
12114 tag.push_str(" [full scan]");
12115 }
12116 } else {
12117 tag.push_str(" [full scan]");
12118 }
12119 out.push(tag);
12120 for j in &from.joins {
12121 let kind = match j.kind {
12122 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12123 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12124 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12125 };
12126 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12127 if let Some(alias) = &j.table.alias {
12128 s.push_str(&alloc::format!(" AS {alias}"));
12129 }
12130 if j.on.is_some() {
12131 s.push_str(" (ON …)");
12132 }
12133 out.push(s);
12134 }
12135 }
12136 if let Some(w) = &stmt.where_ {
12138 let mut s = alloc::format!("{child}Filter: {w}");
12139 if expr_has_subquery(w) {
12140 s.push_str(" [subquery]");
12141 }
12142 out.push(s);
12143 }
12144 if let Some(gs) = &stmt.group_by {
12145 let mut parts = Vec::new();
12146 for g in gs {
12147 parts.push(alloc::format!("{g}"));
12148 }
12149 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12150 }
12151 if let Some(h) = &stmt.having {
12152 out.push(alloc::format!("{child}Having: {h}"));
12153 }
12154 for o in &stmt.order_by {
12155 let dir = if o.desc { "DESC" } else { "ASC" };
12156 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12157 }
12158 if let Some(lim) = stmt.limit {
12159 out.push(alloc::format!("{child}Limit: {lim}"));
12160 }
12161 if let Some(off) = stmt.offset {
12162 out.push(alloc::format!("{child}Offset: {off}"));
12163 }
12164 if stmt
12166 .items
12167 .iter()
12168 .any(|it| matches!(it, SelectItem::Wildcard))
12169 {
12170 out.push(alloc::format!("{child}Project: *"));
12171 } else {
12172 out.push(alloc::format!(
12173 "{child}Project: {} item(s)",
12174 stmt.items.len()
12175 ));
12176 }
12177 for (kind, peer) in &stmt.unions {
12179 let label = match kind {
12180 UnionKind::All => "UNION ALL",
12181 UnionKind::Distinct => "UNION",
12182 };
12183 out.push(alloc::format!("{child}{label}"));
12184 explain_select(peer, engine, depth + 2, out);
12185 }
12186}
12187
12188fn is_correlation_error(e: &EngineError) -> bool {
12193 matches!(
12194 e,
12195 EngineError::Eval(
12196 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12197 )
12198 )
12199}
12200
12201struct JoinedPeer<'a> {
12212 eager_rows: Option<Vec<Row>>,
12213 cols: Vec<ColumnSchema>,
12214 alias: String,
12215 kind: JoinKind,
12216 on: Option<&'a Expr>,
12217 lateral: Option<&'a SelectStatement>,
12218}
12219
12220fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12227 match expr {
12228 Expr::Column(c) => c.name.clone(),
12230 Expr::FunctionCall { name, .. } => name.clone(),
12233 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12235 _ => alloc::format!("column{}", idx + 1),
12237 }
12238}
12239
12240fn substitute_outer_columns_multi(
12247 stmt: &mut SelectStatement,
12248 outer_row: &Row,
12249 outer_schema: &[ColumnSchema],
12250) {
12251 substitute_outer_in_select(stmt, outer_row, outer_schema);
12252}
12253
12254fn substitute_outer_in_select(
12255 stmt: &mut SelectStatement,
12256 outer_row: &Row,
12257 outer_schema: &[ColumnSchema],
12258) {
12259 for item in &mut stmt.items {
12260 if let SelectItem::Expr { expr, .. } = item {
12261 substitute_outer_in_expr(expr, outer_row, outer_schema);
12262 }
12263 }
12264 if let Some(w) = &mut stmt.where_ {
12265 substitute_outer_in_expr(w, outer_row, outer_schema);
12266 }
12267 if let Some(gs) = &mut stmt.group_by {
12268 for g in gs {
12269 substitute_outer_in_expr(g, outer_row, outer_schema);
12270 }
12271 }
12272 if let Some(h) = &mut stmt.having {
12273 substitute_outer_in_expr(h, outer_row, outer_schema);
12274 }
12275 for o in &mut stmt.order_by {
12276 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12277 }
12278 for (_, peer) in &mut stmt.unions {
12279 substitute_outer_in_select(peer, outer_row, outer_schema);
12280 }
12281}
12282
12283fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12284 if let Expr::Column(c) = e
12285 && let Some(qual) = &c.qualifier
12286 {
12287 let composite = alloc::format!("{qual}.{}", c.name);
12288 if let Some(idx) = outer_schema
12289 .iter()
12290 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12291 {
12292 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12293 if let Ok(lit) = value_to_literal_expr(v) {
12294 *e = lit;
12295 return;
12296 }
12297 }
12298 }
12299 match e {
12300 Expr::Binary { lhs, rhs, .. } => {
12301 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12302 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12303 }
12304 Expr::Unary { expr: inner, .. } => {
12305 substitute_outer_in_expr(inner, outer_row, outer_schema);
12306 }
12307 Expr::FunctionCall { args, .. } => {
12308 for a in args {
12309 substitute_outer_in_expr(a, outer_row, outer_schema);
12310 }
12311 }
12312 Expr::Cast { expr: inner, .. } => {
12313 substitute_outer_in_expr(inner, outer_row, outer_schema);
12314 }
12315 Expr::Case {
12316 operand,
12317 branches,
12318 else_branch,
12319 } => {
12320 if let Some(op) = operand {
12321 substitute_outer_in_expr(op, outer_row, outer_schema);
12322 }
12323 for (cond, val) in branches {
12324 substitute_outer_in_expr(cond, outer_row, outer_schema);
12325 substitute_outer_in_expr(val, outer_row, outer_schema);
12326 }
12327 if let Some(e) = else_branch {
12328 substitute_outer_in_expr(e, outer_row, outer_schema);
12329 }
12330 }
12331 _ => {}
12332 }
12333}
12334
12335fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12336 let Some(outer_alias) = ctx.table_alias else {
12337 return;
12338 };
12339 substitute_in_select(stmt, row, ctx, outer_alias);
12340}
12341
12342fn substitute_in_select(
12343 stmt: &mut SelectStatement,
12344 row: &Row,
12345 ctx: &EvalContext<'_>,
12346 outer_alias: &str,
12347) {
12348 for item in &mut stmt.items {
12349 if let SelectItem::Expr { expr, .. } = item {
12350 substitute_in_expr(expr, row, ctx, outer_alias);
12351 }
12352 }
12353 if let Some(w) = &mut stmt.where_ {
12354 substitute_in_expr(w, row, ctx, outer_alias);
12355 }
12356 if let Some(gs) = &mut stmt.group_by {
12357 for g in gs {
12358 substitute_in_expr(g, row, ctx, outer_alias);
12359 }
12360 }
12361 if let Some(h) = &mut stmt.having {
12362 substitute_in_expr(h, row, ctx, outer_alias);
12363 }
12364 for o in &mut stmt.order_by {
12365 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12366 }
12367 for (_, peer) in &mut stmt.unions {
12368 substitute_in_select(peer, row, ctx, outer_alias);
12369 }
12370}
12371
12372fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12373 if let Expr::Column(c) = e
12374 && let Some(qual) = &c.qualifier
12375 && qual.eq_ignore_ascii_case(outer_alias)
12376 {
12377 if let Some(idx) = ctx
12379 .columns
12380 .iter()
12381 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12382 {
12383 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12384 if let Ok(lit) = value_to_literal_expr(v) {
12385 *e = lit;
12386 return;
12387 }
12388 }
12389 }
12390 match e {
12391 Expr::Binary { lhs, rhs, .. } => {
12392 substitute_in_expr(lhs, row, ctx, outer_alias);
12393 substitute_in_expr(rhs, row, ctx, outer_alias);
12394 }
12395 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12396 substitute_in_expr(expr, row, ctx, outer_alias);
12397 }
12398 Expr::Like { expr, pattern, .. } => {
12399 substitute_in_expr(expr, row, ctx, outer_alias);
12400 substitute_in_expr(pattern, row, ctx, outer_alias);
12401 }
12402 Expr::FunctionCall { args, .. } => {
12403 for a in args {
12404 substitute_in_expr(a, row, ctx, outer_alias);
12405 }
12406 }
12407 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12408 Expr::WindowFunction {
12409 args,
12410 partition_by,
12411 order_by,
12412 ..
12413 } => {
12414 for a in args {
12415 substitute_in_expr(a, row, ctx, outer_alias);
12416 }
12417 for p in partition_by {
12418 substitute_in_expr(p, row, ctx, outer_alias);
12419 }
12420 for (o, _) in order_by {
12421 substitute_in_expr(o, row, ctx, outer_alias);
12422 }
12423 }
12424 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12425 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12426 substitute_in_select(subquery, row, ctx, outer_alias);
12427 }
12428 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12429 Expr::Array(items) => {
12430 for elem in items {
12431 substitute_in_expr(elem, row, ctx, outer_alias);
12432 }
12433 }
12434 Expr::ArraySubscript { target, index } => {
12435 substitute_in_expr(target, row, ctx, outer_alias);
12436 substitute_in_expr(index, row, ctx, outer_alias);
12437 }
12438 Expr::AnyAll { expr, array, .. } => {
12439 substitute_in_expr(expr, row, ctx, outer_alias);
12440 substitute_in_expr(array, row, ctx, outer_alias);
12441 }
12442 Expr::Case {
12443 operand,
12444 branches,
12445 else_branch,
12446 } => {
12447 if let Some(o) = operand {
12448 substitute_in_expr(o, row, ctx, outer_alias);
12449 }
12450 for (w, t) in branches {
12451 substitute_in_expr(w, row, ctx, outer_alias);
12452 substitute_in_expr(t, row, ctx, outer_alias);
12453 }
12454 if let Some(e) = else_branch {
12455 substitute_in_expr(e, row, ctx, outer_alias);
12456 }
12457 }
12458 }
12459}
12460
12461fn encode_row_key(row: &Row) -> Vec<u8> {
12465 let mut out = Vec::new();
12466 for v in &row.values {
12467 let s = alloc::format!("{v:?}|");
12468 out.extend_from_slice(s.as_bytes());
12469 }
12470 out
12471}
12472
12473fn select_has_window(stmt: &SelectStatement) -> bool {
12474 for item in &stmt.items {
12475 if let SelectItem::Expr { expr, .. } = item
12476 && expr_has_window(expr)
12477 {
12478 return true;
12479 }
12480 }
12481 false
12482}
12483
12484fn expr_has_window(e: &Expr) -> bool {
12485 match e {
12486 Expr::WindowFunction { .. } => true,
12487 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12488 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12489 expr_has_window(expr)
12490 }
12491 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12492 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12493 Expr::Extract { source, .. } => expr_has_window(source),
12494 Expr::ScalarSubquery(_)
12495 | Expr::Exists { .. }
12496 | Expr::InSubquery { .. }
12497 | Expr::Literal(_)
12498 | Expr::Placeholder(_)
12499 | Expr::Column(_) => false,
12500 Expr::Array(items) => items.iter().any(expr_has_window),
12501 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12502 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12503 Expr::Case {
12504 operand,
12505 branches,
12506 else_branch,
12507 } => {
12508 operand.as_deref().is_some_and(expr_has_window)
12509 || branches
12510 .iter()
12511 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12512 || else_branch.as_deref().is_some_and(expr_has_window)
12513 }
12514 }
12515}
12516
12517fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12518 if let Expr::WindowFunction { .. } = e {
12519 if !out.iter().any(|x| x == e) {
12524 out.push(e.clone());
12525 }
12526 return;
12527 }
12528 match e {
12529 Expr::WindowFunction { .. } => unreachable!(),
12531 Expr::Binary { lhs, rhs, .. } => {
12532 collect_window_nodes(lhs, out);
12533 collect_window_nodes(rhs, out);
12534 }
12535 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12536 collect_window_nodes(expr, out);
12537 }
12538 Expr::FunctionCall { args, .. } => {
12539 for a in args {
12540 collect_window_nodes(a, out);
12541 }
12542 }
12543 Expr::Like { expr, pattern, .. } => {
12544 collect_window_nodes(expr, out);
12545 collect_window_nodes(pattern, out);
12546 }
12547 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12548 _ => {}
12549 }
12550}
12551
12552fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12553 if let Expr::WindowFunction { .. } = e
12554 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12555 {
12556 *e = Expr::Column(spg_sql::ast::ColumnName {
12557 qualifier: None,
12558 name: alloc::format!("__win_{idx}"),
12559 });
12560 return;
12561 }
12562 match e {
12563 Expr::Binary { lhs, rhs, .. } => {
12564 rewrite_window_to_columns(lhs, window_nodes);
12565 rewrite_window_to_columns(rhs, window_nodes);
12566 }
12567 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12568 rewrite_window_to_columns(expr, window_nodes);
12569 }
12570 Expr::FunctionCall { args, .. } => {
12571 for a in args {
12572 rewrite_window_to_columns(a, window_nodes);
12573 }
12574 }
12575 Expr::Like { expr, pattern, .. } => {
12576 rewrite_window_to_columns(expr, window_nodes);
12577 rewrite_window_to_columns(pattern, window_nodes);
12578 }
12579 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12580 _ => {}
12581 }
12582}
12583
12584fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12588 for (x, y) in a.iter().zip(b.iter()) {
12589 let c = value_cmp(x, y);
12590 if c != core::cmp::Ordering::Equal {
12591 return c;
12592 }
12593 }
12594 a.len().cmp(&b.len())
12595}
12596
12597fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12598 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12599 let c = value_cmp(va, vb);
12600 let c = if *desc { c.reverse() } else { c };
12601 if c != core::cmp::Ordering::Equal {
12602 return c;
12603 }
12604 }
12605 a.len().cmp(&b.len())
12606}
12607
12608const fn value_is_integer(v: &Value) -> bool {
12614 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12615}
12616
12617const fn value_to_i64(v: &Value) -> i64 {
12621 match v {
12622 Value::SmallInt(n) => *n as i64,
12623 Value::Int(n) => *n as i64,
12624 Value::BigInt(n) => *n,
12625 _ => panic!("value_to_i64 called on non-integer Value"),
12626 }
12627}
12628
12629fn generate_series_integers(
12635 start: i64,
12636 stop: i64,
12637 step: i64,
12638 cancel: &CancelToken<'_>,
12639) -> Result<alloc::vec::Vec<Row>, EngineError> {
12640 if step == 0 {
12641 return Err(EngineError::Unsupported(
12642 "generate_series(): step argument cannot be zero".into(),
12643 ));
12644 }
12645 let mut out = alloc::vec::Vec::new();
12646 let mut cur = start;
12647 const MAX_ROWS: usize = 10_000_000;
12651 loop {
12652 cancel.check()?;
12653 if step > 0 && cur > stop {
12654 break;
12655 }
12656 if step < 0 && cur < stop {
12657 break;
12658 }
12659 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12660 if out.len() > MAX_ROWS {
12661 return Err(EngineError::Unsupported(alloc::format!(
12662 "generate_series(): exceeded {MAX_ROWS} rows; \
12663 narrow start/stop or use a larger step"
12664 )));
12665 }
12666 cur = match cur.checked_add(step) {
12667 Some(n) => n,
12668 None => break,
12669 };
12670 }
12671 Ok(out)
12672}
12673
12674fn generate_series_timestamps(
12679 start: i64,
12680 stop: i64,
12681 step: Value,
12682 cancel: &CancelToken<'_>,
12683) -> Result<alloc::vec::Vec<Row>, EngineError> {
12684 let (months, micros) = match &step {
12685 Value::Interval { months, micros } => (*months, *micros),
12686 _ => unreachable!("caller guards step.is_interval"),
12687 };
12688 if months == 0 && micros == 0 {
12689 return Err(EngineError::Unsupported(
12690 "generate_series(): INTERVAL step cannot be zero".into(),
12691 ));
12692 }
12693 let ascending = months > 0 || micros > 0;
12694 let mut out = alloc::vec::Vec::new();
12695 let mut cur = Value::Timestamp(start);
12696 const MAX_ROWS: usize = 10_000_000;
12697 loop {
12698 cancel.check()?;
12699 let cur_t = match cur {
12700 Value::Timestamp(t) => t,
12701 _ => unreachable!("loop invariant: cur is Timestamp"),
12702 };
12703 if ascending && cur_t > stop {
12704 break;
12705 }
12706 if !ascending && cur_t < stop {
12707 break;
12708 }
12709 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12710 if out.len() > MAX_ROWS {
12711 return Err(EngineError::Unsupported(alloc::format!(
12712 "generate_series(): exceeded {MAX_ROWS} rows; \
12713 narrow start/stop or use a larger step"
12714 )));
12715 }
12716 let next = eval::apply_binary_interval(
12717 spg_sql::ast::BinOp::Add,
12718 &cur,
12719 &Value::Interval { months, micros },
12720 )
12721 .map_err(EngineError::Eval)?;
12722 cur = match next {
12723 Some(v) => v,
12724 None => break,
12725 };
12726 }
12727 Ok(out)
12728}
12729
12730#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12732 use core::cmp::Ordering;
12733 match (a, b) {
12734 (Value::Null, Value::Null) => Ordering::Equal,
12735 (Value::Null, _) => Ordering::Less,
12736 (_, Value::Null) => Ordering::Greater,
12737 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12738 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12739 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12740 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12741 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12742 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12743 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12744 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12745 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12748 }
12749}
12750
12751#[allow(
12757 clippy::too_many_arguments,
12758 clippy::cast_possible_truncation,
12759 clippy::cast_possible_wrap,
12760 clippy::cast_precision_loss,
12761 clippy::cast_sign_loss,
12762 clippy::doc_markdown,
12763 clippy::too_many_lines,
12764 clippy::type_complexity,
12765 clippy::match_same_arms
12766)]
12767fn compute_window_partition(
12768 name: &str,
12769 args: &[Expr],
12770 ordered: bool,
12771 frame: Option<&WindowFrame>,
12772 null_treatment: spg_sql::ast::NullTreatment,
12773 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12774 filtered_rows: &[&Row],
12775 ctx: &EvalContext<'_>,
12776 out_vals: &mut [Value],
12777) -> Result<(), EngineError> {
12778 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12779 let lower = name.to_ascii_lowercase();
12780 match lower.as_str() {
12781 "row_number" => {
12782 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12783 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12784 }
12785 Ok(())
12786 }
12787 "rank" => {
12788 let mut prev_key: Option<&[(Value, bool)]> = None;
12789 let mut current_rank: i64 = 1;
12790 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12791 if let Some(p) = prev_key
12792 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12793 {
12794 current_rank = (i + 1) as i64;
12795 }
12796 if prev_key.is_none() {
12797 current_rank = 1;
12798 }
12799 out_vals[*idx] = Value::BigInt(current_rank);
12800 prev_key = Some(okey.as_slice());
12801 }
12802 Ok(())
12803 }
12804 "dense_rank" => {
12805 let mut prev_key: Option<&[(Value, bool)]> = None;
12806 let mut current_rank: i64 = 0;
12807 for (_, okey, idx) in slice {
12808 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12809 current_rank += 1;
12810 }
12811 out_vals[*idx] = Value::BigInt(current_rank);
12812 prev_key = Some(okey.as_slice());
12813 }
12814 Ok(())
12815 }
12816 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12817 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12820 slice.iter().map(|_| Value::Null).collect()
12821 } else {
12822 slice
12823 .iter()
12824 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12825 .collect::<Result<_, _>>()
12826 .map_err(EngineError::Eval)?
12827 };
12828 let eff = effective_frame(frame, ordered)?;
12832 #[allow(clippy::needless_range_loop)]
12833 for i in 0..slice.len() {
12834 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12835 let mut sum: f64 = 0.0;
12836 let mut count: i64 = 0;
12837 let mut min_v: Option<f64> = None;
12838 let mut max_v: Option<f64> = None;
12839 let mut row_count: i64 = 0;
12840 if lo <= hi {
12841 for j in lo..=hi {
12842 let v = &arg_values[j];
12843 match lower.as_str() {
12844 "count_star" => row_count += 1,
12845 "count" => {
12846 if !v.is_null() {
12847 count += 1;
12848 }
12849 }
12850 _ => {
12851 if let Some(x) = value_to_f64(v) {
12852 sum += x;
12853 count += 1;
12854 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12855 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12856 }
12857 }
12858 }
12859 }
12860 }
12861 let value = match lower.as_str() {
12862 "count_star" => Value::BigInt(row_count),
12863 "count" => Value::BigInt(count),
12864 "sum" => Value::Float(sum),
12865 "avg" => {
12866 if count == 0 {
12867 Value::Null
12868 } else {
12869 Value::Float(sum / count as f64)
12870 }
12871 }
12872 "min" => min_v.map_or(Value::Null, Value::Float),
12873 "max" => max_v.map_or(Value::Null, Value::Float),
12874 _ => unreachable!(),
12875 };
12876 let (_, _, idx) = &slice[i];
12877 out_vals[*idx] = value;
12878 }
12879 Ok(())
12880 }
12881 "lag" | "lead" => {
12882 if args.is_empty() {
12885 return Err(EngineError::Unsupported(alloc::format!(
12886 "{lower}() requires at least one argument"
12887 )));
12888 }
12889 let offset: i64 = if args.len() >= 2 {
12890 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12891 .map_err(EngineError::Eval)?;
12892 match v {
12893 Value::SmallInt(n) => i64::from(n),
12894 Value::Int(n) => i64::from(n),
12895 Value::BigInt(n) => n,
12896 _ => {
12897 return Err(EngineError::Unsupported(alloc::format!(
12898 "{lower}() offset must be integer"
12899 )));
12900 }
12901 }
12902 } else {
12903 1
12904 };
12905 let default: Value = if args.len() >= 3 {
12906 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12907 .map_err(EngineError::Eval)?
12908 } else {
12909 Value::Null
12910 };
12911 let values: Vec<Value> = slice
12912 .iter()
12913 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12914 .collect::<Result<_, _>>()
12915 .map_err(EngineError::Eval)?;
12916 let n = slice.len();
12917 for (i, (_, _, idx)) in slice.iter().enumerate() {
12918 let signed_offset = if lower == "lag" { -offset } else { offset };
12919 let v = if ignore_nulls {
12920 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12924 let needed: i64 = signed_offset.abs();
12925 if needed == 0 {
12926 values[i].clone()
12927 } else {
12928 let mut j: i64 = i as i64;
12929 let mut hits: i64 = 0;
12930 let mut found: Option<Value> = None;
12931 loop {
12932 j += step;
12933 if j < 0 || j >= n as i64 {
12934 break;
12935 }
12936 #[allow(clippy::cast_sign_loss)]
12937 let v = &values[j as usize];
12938 if !v.is_null() {
12939 hits += 1;
12940 if hits == needed {
12941 found = Some(v.clone());
12942 break;
12943 }
12944 }
12945 }
12946 found.unwrap_or_else(|| default.clone())
12947 }
12948 } else {
12949 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12950 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12951 default.clone()
12952 } else {
12953 #[allow(clippy::cast_sign_loss)]
12954 {
12955 values[target_signed as usize].clone()
12956 }
12957 }
12958 };
12959 out_vals[*idx] = v;
12960 }
12961 Ok(())
12962 }
12963 "first_value" | "last_value" | "nth_value" => {
12964 if args.is_empty() {
12965 return Err(EngineError::Unsupported(alloc::format!(
12966 "{lower}() requires at least one argument"
12967 )));
12968 }
12969 let values: Vec<Value> = slice
12970 .iter()
12971 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12972 .collect::<Result<_, _>>()
12973 .map_err(EngineError::Eval)?;
12974 let nth: usize = if lower == "nth_value" {
12975 if args.len() < 2 {
12976 return Err(EngineError::Unsupported(
12977 "nth_value() requires (expr, n)".into(),
12978 ));
12979 }
12980 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12981 .map_err(EngineError::Eval)?;
12982 let raw = match v {
12983 Value::SmallInt(n) => i64::from(n),
12984 Value::Int(n) => i64::from(n),
12985 Value::BigInt(n) => n,
12986 _ => {
12987 return Err(EngineError::Unsupported(
12988 "nth_value() n must be integer".into(),
12989 ));
12990 }
12991 };
12992 if raw < 1 {
12993 return Err(EngineError::Unsupported(
12994 "nth_value() n must be >= 1".into(),
12995 ));
12996 }
12997 #[allow(clippy::cast_sign_loss)]
12998 {
12999 raw as usize
13000 }
13001 } else {
13002 0
13003 };
13004 let eff = effective_frame(frame, ordered)?;
13005 for i in 0..slice.len() {
13006 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
13007 let (_, _, idx) = &slice[i];
13008 let v = if lo > hi {
13009 Value::Null
13010 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
13011 if lower == "first_value" {
13014 (lo..=hi)
13015 .find_map(|j| {
13016 let v = &values[j];
13017 (!v.is_null()).then(|| v.clone())
13018 })
13019 .unwrap_or(Value::Null)
13020 } else {
13021 (lo..=hi)
13022 .rev()
13023 .find_map(|j| {
13024 let v = &values[j];
13025 (!v.is_null()).then(|| v.clone())
13026 })
13027 .unwrap_or(Value::Null)
13028 }
13029 } else {
13030 match lower.as_str() {
13031 "first_value" => values[lo].clone(),
13032 "last_value" => values[hi].clone(),
13033 "nth_value" => {
13034 let pos = lo + nth - 1;
13035 if pos > hi {
13036 Value::Null
13037 } else {
13038 values[pos].clone()
13039 }
13040 }
13041 _ => unreachable!(),
13042 }
13043 };
13044 out_vals[*idx] = v;
13045 }
13046 Ok(())
13047 }
13048 "ntile" => {
13049 if args.is_empty() {
13050 return Err(EngineError::Unsupported(
13051 "ntile(n) requires an integer argument".into(),
13052 ));
13053 }
13054 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
13055 .map_err(EngineError::Eval)?;
13056 let bucket_count: i64 = match v {
13057 Value::SmallInt(n) => i64::from(n),
13058 Value::Int(n) => i64::from(n),
13059 Value::BigInt(n) => n,
13060 _ => {
13061 return Err(EngineError::Unsupported(
13062 "ntile() argument must be integer".into(),
13063 ));
13064 }
13065 };
13066 if bucket_count < 1 {
13067 return Err(EngineError::Unsupported(
13068 "ntile() argument must be >= 1".into(),
13069 ));
13070 }
13071 #[allow(clippy::cast_sign_loss)]
13072 let buckets = bucket_count as usize;
13073 let n = slice.len();
13074 let base = n / buckets;
13077 let extras = n % buckets;
13078 let mut bucket: usize = 1;
13079 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
13080 let mut buckets_with_extra_remaining = extras;
13081 for (_, _, idx) in slice {
13082 if remaining_in_bucket == 0 {
13083 bucket += 1;
13084 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
13085 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
13086 base + 1
13087 } else {
13088 base
13089 };
13090 if remaining_in_bucket == 0 {
13093 remaining_in_bucket = 1;
13094 }
13095 }
13096 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13097 remaining_in_bucket -= 1;
13098 }
13099 Ok(())
13100 }
13101 "percent_rank" => {
13102 let n = slice.len();
13105 let mut prev_key: Option<&[(Value, bool)]> = None;
13106 let mut current_rank: i64 = 1;
13107 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13108 if let Some(p) = prev_key
13109 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13110 {
13111 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13112 }
13113 if prev_key.is_none() {
13114 current_rank = 1;
13115 }
13116 #[allow(clippy::cast_precision_loss)]
13117 let pr = if n <= 1 {
13118 0.0
13119 } else {
13120 (current_rank - 1) as f64 / (n - 1) as f64
13121 };
13122 out_vals[*idx] = Value::Float(pr);
13123 prev_key = Some(okey.as_slice());
13124 }
13125 Ok(())
13126 }
13127 "cume_dist" => {
13128 let n = slice.len();
13130 for i in 0..slice.len() {
13132 let peer_end = peer_group_end(slice, i);
13133 #[allow(clippy::cast_precision_loss)]
13134 let cd = (peer_end + 1) as f64 / n as f64;
13135 let (_, _, idx) = &slice[i];
13136 out_vals[*idx] = Value::Float(cd);
13137 }
13138 Ok(())
13139 }
13140 other => Err(EngineError::Unsupported(alloc::format!(
13141 "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)"
13142 ))),
13143 }
13144}
13145
13146fn effective_frame(
13153 frame: Option<&WindowFrame>,
13154 ordered: bool,
13155) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13156 match frame {
13157 None => {
13158 if ordered {
13159 Ok((
13160 FrameKind::Range,
13161 FrameBound::UnboundedPreceding,
13162 FrameBound::CurrentRow,
13163 ))
13164 } else {
13165 Ok((
13166 FrameKind::Rows,
13167 FrameBound::UnboundedPreceding,
13168 FrameBound::UnboundedFollowing,
13169 ))
13170 }
13171 }
13172 Some(fr) => {
13173 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13174 if matches!(fr.start, FrameBound::UnboundedFollowing)
13176 || matches!(end, FrameBound::UnboundedPreceding)
13177 {
13178 return Err(EngineError::Unsupported(alloc::format!(
13179 "invalid frame: start={:?} end={:?}",
13180 fr.start,
13181 end
13182 )));
13183 }
13184 if fr.kind == FrameKind::Range
13189 && (matches!(
13190 fr.start,
13191 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13192 ) || matches!(
13193 end,
13194 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13195 ))
13196 {
13197 return Err(EngineError::Unsupported(
13198 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13199 ));
13200 }
13201 Ok((fr.kind, fr.start.clone(), end))
13202 }
13203 }
13204}
13205
13206#[allow(clippy::type_complexity)]
13210fn frame_bounds_for_row(
13211 eff: &(FrameKind, FrameBound, FrameBound),
13212 i: usize,
13213 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
13214) -> (usize, usize) {
13215 let (kind, start, end) = eff;
13216 let n = slice.len();
13217 let last = n.saturating_sub(1);
13218 let (mut lo, mut hi) = match kind {
13219 FrameKind::Rows => {
13220 let lo = match start {
13221 FrameBound::UnboundedPreceding => 0,
13222 FrameBound::OffsetPreceding(k) => {
13223 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13224 i.saturating_sub(k)
13225 }
13226 FrameBound::CurrentRow => i,
13227 FrameBound::OffsetFollowing(k) => {
13228 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13229 i.saturating_add(k).min(last)
13230 }
13231 FrameBound::UnboundedFollowing => last,
13232 };
13233 let hi = match end {
13234 FrameBound::UnboundedPreceding => 0,
13235 FrameBound::OffsetPreceding(k) => {
13236 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13237 i.saturating_sub(k)
13238 }
13239 FrameBound::CurrentRow => i,
13240 FrameBound::OffsetFollowing(k) => {
13241 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13242 i.saturating_add(k).min(last)
13243 }
13244 FrameBound::UnboundedFollowing => last,
13245 };
13246 (lo, hi)
13247 }
13248 FrameKind::Range => {
13249 let lo = match start {
13255 FrameBound::UnboundedPreceding => 0,
13256 FrameBound::CurrentRow => peer_group_start(slice, i),
13257 FrameBound::UnboundedFollowing => last,
13258 _ => unreachable!("offset bounds rejected for RANGE"),
13259 };
13260 let hi = match end {
13261 FrameBound::UnboundedPreceding => 0,
13262 FrameBound::CurrentRow => peer_group_end(slice, i),
13263 FrameBound::UnboundedFollowing => last,
13264 _ => unreachable!("offset bounds rejected for RANGE"),
13265 };
13266 (lo, hi)
13267 }
13268 };
13269 if hi >= n {
13270 hi = last;
13271 }
13272 if lo >= n {
13273 lo = last;
13274 }
13275 (lo, hi)
13276}
13277
13278#[allow(clippy::type_complexity)]
13282fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13283 let key = &slice[i].1;
13284 let mut j = i;
13285 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13286 j -= 1;
13287 }
13288 j
13289}
13290
13291#[allow(clippy::type_complexity)]
13294fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13295 let key = &slice[i].1;
13296 let mut j = i;
13297 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13298 j += 1;
13299 }
13300 j
13301}
13302
13303fn value_to_f64(v: &Value) -> Option<f64> {
13304 match v {
13305 Value::SmallInt(n) => Some(f64::from(*n)),
13306 Value::Int(n) => Some(f64::from(*n)),
13307 #[allow(clippy::cast_precision_loss)]
13308 Value::BigInt(n) => Some(*n as f64),
13309 Value::Float(x) => Some(*x),
13310 _ => None,
13311 }
13312}
13313
13314fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13318 let mut any = false;
13319 for item in &stmt.items {
13320 if let SelectItem::Expr { expr, .. } = item {
13321 any = any || expr_has_subquery(expr);
13322 }
13323 }
13324 if let Some(w) = &stmt.where_ {
13325 any = any || expr_has_subquery(w);
13326 }
13327 if let Some(h) = &stmt.having {
13328 any = any || expr_has_subquery(h);
13329 }
13330 for o in &stmt.order_by {
13331 any = any || expr_has_subquery(&o.expr);
13332 }
13333 for (_, peer) in &stmt.unions {
13334 any = any || expr_tree_has_subquery(peer);
13335 }
13336 any
13337}
13338
13339fn expr_has_subquery(e: &Expr) -> bool {
13340 match e {
13341 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13342 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13343 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13344 expr_has_subquery(expr)
13345 }
13346 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13347 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13348 Expr::Extract { source, .. } => expr_has_subquery(source),
13349 Expr::WindowFunction {
13350 args,
13351 partition_by,
13352 order_by,
13353 ..
13354 } => {
13355 args.iter().any(expr_has_subquery)
13356 || partition_by.iter().any(expr_has_subquery)
13357 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13358 }
13359 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13360 Expr::Array(items) => items.iter().any(expr_has_subquery),
13361 Expr::ArraySubscript { target, index } => {
13362 expr_has_subquery(target) || expr_has_subquery(index)
13363 }
13364 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13365 Expr::Case {
13366 operand,
13367 branches,
13368 else_branch,
13369 } => {
13370 operand.as_deref().is_some_and(expr_has_subquery)
13371 || branches
13372 .iter()
13373 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13374 || else_branch.as_deref().is_some_and(expr_has_subquery)
13375 }
13376 }
13377}
13378
13379fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13386 let lit = match v {
13387 Value::Null => Literal::Null,
13388 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13389 Value::Int(n) => Literal::Integer(i64::from(n)),
13390 Value::BigInt(n) => Literal::Integer(n),
13391 Value::Float(x) => Literal::Float(x),
13392 Value::Text(s) | Value::Json(s) => Literal::String(s),
13393 Value::Bool(b) => Literal::Bool(b),
13394 other => {
13395 return Err(EngineError::Unsupported(alloc::format!(
13396 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13397 other.data_type()
13398 )));
13399 }
13400 };
13401 Ok(Expr::Literal(lit))
13402}
13403
13404fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13410 let lit = match v {
13411 Value::Null => Literal::Null,
13412 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13413 Value::Int(n) => Literal::Integer(i64::from(n)),
13414 Value::BigInt(n) => Literal::Integer(n),
13415 Value::Float(x) => Literal::Float(x),
13416 Value::Text(s) | Value::Json(s) => Literal::String(s),
13417 Value::Bool(b) => Literal::Bool(b),
13418 Value::Vector(xs) => Literal::Vector(xs),
13419 Value::Date(days) => {
13423 let micros = (i64::from(days)) * 86_400_000_000;
13424 Literal::String(format_timestamp_micros_as_date(micros))
13425 }
13426 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13427 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13428 other => {
13429 return Err(EngineError::Unsupported(alloc::format!(
13430 "INSERT … SELECT cannot materialise value of type {:?}; \
13431 add an explicit CAST in the inner SELECT",
13432 other.data_type()
13433 )));
13434 }
13435 };
13436 Ok(Expr::Literal(lit))
13437}
13438
13439fn format_timestamp_micros(us: i64) -> String {
13440 let days = us.div_euclid(86_400_000_000);
13442 let intra_day = us.rem_euclid(86_400_000_000);
13443 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13444 let secs = intra_day / 1_000_000;
13445 let us_rem = intra_day % 1_000_000;
13446 let h = (secs / 3600) % 24;
13447 let m = (secs / 60) % 60;
13448 let s = secs % 60;
13449 if us_rem == 0 {
13450 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13451 } else {
13452 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13453 }
13454}
13455
13456fn format_timestamp_micros_as_date(us: i64) -> String {
13457 let days = us.div_euclid(86_400_000_000);
13460 let jdn = days + 2_440_588;
13462 let (y, mo, d) = jdn_to_ymd(jdn);
13463 alloc::format!("{y:04}-{mo:02}-{d:02}")
13464}
13465
13466fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13467 let l = jdn + 68569;
13469 let n = (4 * l) / 146_097;
13470 let l = l - (146_097 * n + 3) / 4;
13471 let i = (4000 * (l + 1)) / 1_461_001;
13472 let l = l - (1461 * i) / 4 + 31;
13473 let j = (80 * l) / 2447;
13474 let day = (l - (2447 * j) / 80) as u32;
13475 let l = j / 11;
13476 let month = (j + 2 - 12 * l) as u32;
13477 let year = 100 * (n - 49) + i + l;
13478 (year, month, day)
13479}
13480
13481fn format_numeric(scaled: i128, scale: u8) -> String {
13482 if scale == 0 {
13483 return alloc::format!("{scaled}");
13484 }
13485 let abs = scaled.unsigned_abs();
13486 let divisor = 10u128.pow(u32::from(scale));
13487 let whole = abs / divisor;
13488 let frac = abs % divisor;
13489 let sign = if scaled < 0 { "-" } else { "" };
13490 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13491}
13492
13493fn rewrite_column_in_source(
13517 src: &str,
13518 old: &str,
13519 new: &str,
13520) -> Result<alloc::string::String, EngineError> {
13521 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13522 EngineError::Unsupported(alloc::format!(
13523 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13524 failed to parse for rewrite ({e})"
13525 ))
13526 })?;
13527 rewrite_column_in_expr(&mut expr, old, new);
13528 Ok(alloc::format!("{expr}"))
13529}
13530
13531fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13539 match e {
13540 Expr::Column(c) => {
13541 if c.name.eq_ignore_ascii_case(old) {
13542 c.name = new.to_string();
13543 }
13544 }
13545 Expr::Binary { lhs, rhs, .. } => {
13546 rewrite_column_in_expr(lhs, old, new);
13547 rewrite_column_in_expr(rhs, old, new);
13548 }
13549 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13550 rewrite_column_in_expr(expr, old, new);
13551 }
13552 Expr::FunctionCall { args, .. } => {
13553 for a in args {
13554 rewrite_column_in_expr(a, old, new);
13555 }
13556 }
13557 Expr::Like { expr, pattern, .. } => {
13558 rewrite_column_in_expr(expr, old, new);
13559 rewrite_column_in_expr(pattern, old, new);
13560 }
13561 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13562 Expr::WindowFunction {
13563 args,
13564 partition_by,
13565 order_by,
13566 ..
13567 } => {
13568 for a in args {
13569 rewrite_column_in_expr(a, old, new);
13570 }
13571 for p in partition_by {
13572 rewrite_column_in_expr(p, old, new);
13573 }
13574 for (o, _) in order_by {
13575 rewrite_column_in_expr(o, old, new);
13576 }
13577 }
13578 Expr::Array(items) => {
13579 for elem in items {
13580 rewrite_column_in_expr(elem, old, new);
13581 }
13582 }
13583 Expr::ArraySubscript { target, index } => {
13584 rewrite_column_in_expr(target, old, new);
13585 rewrite_column_in_expr(index, old, new);
13586 }
13587 Expr::AnyAll { expr, array, .. } => {
13588 rewrite_column_in_expr(expr, old, new);
13589 rewrite_column_in_expr(array, old, new);
13590 }
13591 Expr::Case {
13592 operand,
13593 branches,
13594 else_branch,
13595 } => {
13596 if let Some(o) = operand {
13597 rewrite_column_in_expr(o, old, new);
13598 }
13599 for (w, t) in branches {
13600 rewrite_column_in_expr(w, old, new);
13601 rewrite_column_in_expr(t, old, new);
13602 }
13603 if let Some(e) = else_branch {
13604 rewrite_column_in_expr(e, old, new);
13605 }
13606 }
13607 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13611 Expr::Literal(_) | Expr::Placeholder(_) => {}
13612 }
13613}
13614
13615pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13623 match stmt {
13624 Statement::Select(s) => substitute_select(s, params)?,
13625 Statement::Insert(ins) => {
13626 for row in &mut ins.rows {
13627 for e in row {
13628 substitute_expr(e, params)?;
13629 }
13630 }
13631 if let Some(clause) = &mut ins.on_conflict
13635 && let spg_sql::ast::OnConflictAction::Update {
13636 assignments,
13637 where_,
13638 } = &mut clause.action
13639 {
13640 for (_, e) in assignments.iter_mut() {
13641 substitute_expr(e, params)?;
13642 }
13643 if let Some(w) = where_ {
13644 substitute_expr(w, params)?;
13645 }
13646 }
13647 }
13648 Statement::Update(u) => {
13649 for (_, e) in &mut u.assignments {
13650 substitute_expr(e, params)?;
13651 }
13652 if let Some(w) = &mut u.where_ {
13653 substitute_expr(w, params)?;
13654 }
13655 }
13656 Statement::Delete(d) => {
13657 if let Some(w) = &mut d.where_ {
13658 substitute_expr(w, params)?;
13659 }
13660 }
13661 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13662 _ => {}
13665 }
13666 Ok(())
13667}
13668
13669fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13670 for item in &mut s.items {
13671 if let SelectItem::Expr { expr, .. } = item {
13672 substitute_expr(expr, params)?;
13673 }
13674 }
13675 if let Some(w) = &mut s.where_ {
13676 substitute_expr(w, params)?;
13677 }
13678 if let Some(gs) = &mut s.group_by {
13679 for g in gs {
13680 substitute_expr(g, params)?;
13681 }
13682 }
13683 if let Some(h) = &mut s.having {
13684 substitute_expr(h, params)?;
13685 }
13686 for o in &mut s.order_by {
13687 substitute_expr(&mut o.expr, params)?;
13688 }
13689 for (_, peer) in &mut s.unions {
13690 substitute_select(peer, params)?;
13691 }
13692 if let Some(le) = s.limit {
13697 s.limit = Some(resolve_limit_placeholder(le, params)?);
13698 }
13699 if let Some(le) = s.offset {
13700 s.offset = Some(resolve_limit_placeholder(le, params)?);
13701 }
13702 Ok(())
13703}
13704
13705fn resolve_limit_placeholder(
13706 le: spg_sql::ast::LimitExpr,
13707 params: &[Value],
13708) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13709 use spg_sql::ast::LimitExpr;
13710 match le {
13711 LimitExpr::Literal(_) => Ok(le),
13712 LimitExpr::Placeholder(n) => {
13713 let idx = usize::from(n).saturating_sub(1);
13714 let v = params.get(idx).ok_or_else(|| {
13715 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13716 n,
13717 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13718 })
13719 })?;
13720 let int = match v {
13721 Value::SmallInt(x) => Some(i64::from(*x)),
13722 Value::Int(x) => Some(i64::from(*x)),
13723 Value::BigInt(x) => Some(*x),
13724 _ => None,
13725 }
13726 .ok_or_else(|| {
13727 EngineError::Unsupported(alloc::format!(
13728 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13729 ))
13730 })?;
13731 if int < 0 {
13732 return Err(EngineError::Unsupported(alloc::format!(
13733 "LIMIT/OFFSET ${n} bound to negative value {int}"
13734 )));
13735 }
13736 let bounded = u32::try_from(int).map_err(|_| {
13737 EngineError::Unsupported(alloc::format!(
13738 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13739 ))
13740 })?;
13741 Ok(LimitExpr::Literal(bounded))
13742 }
13743 }
13744}
13745
13746fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13747 if let Expr::Placeholder(n) = e {
13748 let idx = usize::from(*n).saturating_sub(1);
13749 let v = params.get(idx).ok_or_else(|| {
13750 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13751 n: *n,
13752 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13753 })
13754 })?;
13755 *e = Expr::Literal(value_to_literal(v.clone()));
13756 return Ok(());
13757 }
13758 match e {
13759 Expr::Binary { lhs, rhs, .. } => {
13760 substitute_expr(lhs, params)?;
13761 substitute_expr(rhs, params)?;
13762 }
13763 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13764 substitute_expr(expr, params)?;
13765 }
13766 Expr::FunctionCall { args, .. } => {
13767 for a in args {
13768 substitute_expr(a, params)?;
13769 }
13770 }
13771 Expr::Like { expr, pattern, .. } => {
13772 substitute_expr(expr, params)?;
13773 substitute_expr(pattern, params)?;
13774 }
13775 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13776 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13777 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13778 Expr::InSubquery { expr, subquery, .. } => {
13779 substitute_expr(expr, params)?;
13780 substitute_select(subquery, params)?;
13781 }
13782 Expr::WindowFunction {
13783 args,
13784 partition_by,
13785 order_by,
13786 ..
13787 } => {
13788 for a in args {
13789 substitute_expr(a, params)?;
13790 }
13791 for p in partition_by {
13792 substitute_expr(p, params)?;
13793 }
13794 for (e, _) in order_by {
13795 substitute_expr(e, params)?;
13796 }
13797 }
13798 Expr::Literal(_) | Expr::Column(_) => {}
13799 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13801 Expr::Array(items) => {
13802 for elem in items {
13803 substitute_expr(elem, params)?;
13804 }
13805 }
13806 Expr::ArraySubscript { target, index } => {
13807 substitute_expr(target, params)?;
13808 substitute_expr(index, params)?;
13809 }
13810 Expr::AnyAll { expr, array, .. } => {
13811 substitute_expr(expr, params)?;
13812 substitute_expr(array, params)?;
13813 }
13814 Expr::Case {
13815 operand,
13816 branches,
13817 else_branch,
13818 } => {
13819 if let Some(o) = operand {
13820 substitute_expr(o, params)?;
13821 }
13822 for (w, t) in branches {
13823 substitute_expr(w, params)?;
13824 substitute_expr(t, params)?;
13825 }
13826 if let Some(e) = else_branch {
13827 substitute_expr(e, params)?;
13828 }
13829 }
13830 }
13831 Ok(())
13832}
13833
13834fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13852 use core::cmp::Ordering;
13853 match (a, b) {
13854 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13855 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13856 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13857 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13858 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13859 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13860 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13861 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13862 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13863 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13864 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13865 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13866 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13867 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13868 (Value::SmallInt(n), Value::Float(x)) => {
13870 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13871 }
13872 (Value::Float(x), Value::SmallInt(n)) => {
13873 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13874 }
13875 (Value::Int(n), Value::Float(x)) => {
13876 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13877 }
13878 (Value::Float(x), Value::Int(n)) => {
13879 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13880 }
13881 (Value::BigInt(n), Value::Float(x)) => {
13882 #[allow(clippy::cast_precision_loss)]
13883 let nf = *n as f64;
13884 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13885 }
13886 (Value::Float(x), Value::BigInt(n)) => {
13887 #[allow(clippy::cast_precision_loss)]
13888 let nf = *n as f64;
13889 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13890 }
13891 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13894 }
13895}
13896
13897fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13904 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13905 out.push('[');
13906 for (i, b) in bounds.iter().enumerate() {
13907 if i > 0 {
13908 out.push_str(", ");
13909 }
13910 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13911 if needs_quote {
13912 out.push('"');
13913 for ch in b.chars() {
13914 if ch == '"' || ch == '\\' {
13915 out.push('\\');
13916 }
13917 out.push(ch);
13918 }
13919 out.push('"');
13920 } else {
13921 out.push_str(b);
13922 }
13923 }
13924 out.push(']');
13925 out
13926}
13927
13928pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13938 match v {
13939 Value::Null => "NULL".to_string(),
13940 Value::SmallInt(n) => alloc::format!("{n}"),
13941 Value::Int(n) => alloc::format!("{n}"),
13942 Value::BigInt(n) => alloc::format!("{n}"),
13943 Value::Float(x) => alloc::format!("{x:?}"),
13944 Value::Text(s) | Value::Json(s) => s.clone(),
13945 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13946 Value::Date(d) => eval::format_date(*d),
13947 Value::Timestamp(t) => eval::format_timestamp(*t),
13948 Value::Time(us) => eval::format_time(*us),
13950 Value::Year(y) => alloc::format!("{y:04}"),
13952 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13954 Value::Money(c) => eval::format_money(*c),
13956 v @ Value::Range { .. } => format_range_str(v),
13958 Value::Hstore(pairs) => format_hstore_str(pairs),
13960 Value::IntArray2D(rows) => format_int_2d_text(rows),
13962 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13963 Value::TextArray2D(rows) => format_text_2d_text(rows),
13964 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13965 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13966 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13967 alloc::format!("{v:?}")
13971 }
13972 _ => alloc::format!("{v:?}"),
13976 }
13977}
13978
13979const fn is_internal_table_name(_name: &str) -> bool {
13986 false
13987}
13988
13989fn value_to_literal(v: Value) -> Literal {
13990 match v {
13991 Value::Null => Literal::Null,
13992 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13993 Value::Int(n) => Literal::Integer(i64::from(n)),
13994 Value::BigInt(n) => Literal::Integer(n),
13995 Value::Float(x) => Literal::Float(x),
13996 Value::Text(s) | Value::Json(s) => Literal::String(s),
13997 Value::Bool(b) => Literal::Bool(b),
13998 Value::Vector(v) => Literal::Vector(v),
13999 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
14000 Value::Date(d) => Literal::String(eval::format_date(d)),
14001 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
14002 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
14008 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
14013 Value::TextArray(items) => Literal::TextArray(items),
14018 Value::IntArray(items) => Literal::IntArray(items),
14019 Value::BigIntArray(items) => Literal::BigIntArray(items),
14020 Value::Interval { months, micros } => Literal::Interval {
14021 months,
14022 micros,
14023 text: eval::format_interval(months, micros),
14024 },
14025 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
14028 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
14029 v => Literal::String(alloc::format!("{v:?}")),
14033 }
14034}
14035
14036fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
14037 let Some(now) = now_micros else {
14038 return;
14039 };
14040 match stmt {
14041 Statement::Select(s) => rewrite_select_clock(s, now),
14042 Statement::Insert(ins) => {
14043 for row in &mut ins.rows {
14044 for e in row {
14045 rewrite_expr_clock(e, now);
14046 }
14047 }
14048 if let Some(clause) = &mut ins.on_conflict
14052 && let spg_sql::ast::OnConflictAction::Update {
14053 assignments,
14054 where_,
14055 } = &mut clause.action
14056 {
14057 for (_, e) in assignments.iter_mut() {
14058 rewrite_expr_clock(e, now);
14059 }
14060 if let Some(w) = where_ {
14061 rewrite_expr_clock(w, now);
14062 }
14063 }
14064 }
14065 Statement::Update(u) => {
14069 for (_, e) in &mut u.assignments {
14070 rewrite_expr_clock(e, now);
14071 }
14072 if let Some(w) = &mut u.where_ {
14073 rewrite_expr_clock(w, now);
14074 }
14075 }
14076 Statement::Delete(d) => {
14077 if let Some(w) = &mut d.where_ {
14078 rewrite_expr_clock(w, now);
14079 }
14080 }
14081 _ => {}
14082 }
14083}
14084
14085fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
14086 for item in &mut s.items {
14087 if let SelectItem::Expr { expr, .. } = item {
14088 rewrite_expr_clock(expr, now);
14089 }
14090 }
14091 if let Some(w) = &mut s.where_ {
14092 rewrite_expr_clock(w, now);
14093 }
14094 if let Some(gs) = &mut s.group_by {
14095 for g in gs {
14096 rewrite_expr_clock(g, now);
14097 }
14098 }
14099 if let Some(h) = &mut s.having {
14100 rewrite_expr_clock(h, now);
14101 }
14102 for o in &mut s.order_by {
14103 rewrite_expr_clock(&mut o.expr, now);
14104 }
14105 for (_, peer) in &mut s.unions {
14106 rewrite_select_clock(peer, now);
14107 }
14108}
14109
14110fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14118 if let Some(replacement) = clock_replacement_for(e, now) {
14122 *e = replacement;
14123 return;
14124 }
14125 match e {
14126 Expr::Binary { lhs, rhs, .. } => {
14127 rewrite_expr_clock(lhs, now);
14128 rewrite_expr_clock(rhs, now);
14129 }
14130 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14131 rewrite_expr_clock(expr, now);
14132 }
14133 Expr::FunctionCall { args, .. } => {
14134 for a in args {
14135 rewrite_expr_clock(a, now);
14136 }
14137 }
14138 Expr::Like { expr, pattern, .. } => {
14139 rewrite_expr_clock(expr, now);
14140 rewrite_expr_clock(pattern, now);
14141 }
14142 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14143 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14147 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14148 Expr::InSubquery { expr, subquery, .. } => {
14149 rewrite_expr_clock(expr, now);
14150 rewrite_select_clock(subquery, now);
14151 }
14152 Expr::WindowFunction {
14155 args,
14156 partition_by,
14157 order_by,
14158 ..
14159 } => {
14160 for a in args {
14161 rewrite_expr_clock(a, now);
14162 }
14163 for p in partition_by {
14164 rewrite_expr_clock(p, now);
14165 }
14166 for (e, _) in order_by {
14167 rewrite_expr_clock(e, now);
14168 }
14169 }
14170 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14171 Expr::Array(items) => {
14172 for elem in items {
14173 rewrite_expr_clock(elem, now);
14174 }
14175 }
14176 Expr::ArraySubscript { target, index } => {
14177 rewrite_expr_clock(target, now);
14178 rewrite_expr_clock(index, now);
14179 }
14180 Expr::AnyAll { expr, array, .. } => {
14181 rewrite_expr_clock(expr, now);
14182 rewrite_expr_clock(array, now);
14183 }
14184 Expr::Case {
14185 operand,
14186 branches,
14187 else_branch,
14188 } => {
14189 if let Some(o) = operand {
14190 rewrite_expr_clock(o, now);
14191 }
14192 for (w, t) in branches {
14193 rewrite_expr_clock(w, now);
14194 rewrite_expr_clock(t, now);
14195 }
14196 if let Some(e) = else_branch {
14197 rewrite_expr_clock(e, now);
14198 }
14199 }
14200 }
14201}
14202
14203fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14210 let (kind, name) = match e {
14211 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14212 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14213 _ => return None,
14214 };
14215 enum ClockShape {
14223 Timestamp,
14224 Date,
14225 UnixSeconds,
14226 }
14227 let shape = match name.len() {
14228 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14229 Some(ClockShape::Timestamp)
14230 }
14231 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14232 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14233 Some(ClockShape::UnixSeconds)
14234 }
14235 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14236 _ => None,
14237 };
14238 let shape = shape?;
14239 let payload = match shape {
14240 ClockShape::Timestamp => now,
14241 ClockShape::Date => now.div_euclid(86_400_000_000),
14242 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14243 };
14244 let target = match shape {
14245 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14246 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14247 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14248 };
14249 Some(Expr::Cast {
14250 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14251 target,
14252 })
14253}
14254
14255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14256enum ClockSite {
14257 Fn,
14258 BareIdent,
14259}
14260
14261fn expand_group_by_all(s: &mut SelectStatement) {
14272 if !s.group_by_all {
14273 for (_, peer) in &mut s.unions {
14274 expand_group_by_all(peer);
14275 }
14276 return;
14277 }
14278 let mut groups: Vec<Expr> = Vec::new();
14279 for item in &s.items {
14280 if let SelectItem::Expr { expr, .. } = item
14281 && !aggregate::contains_aggregate(expr)
14282 {
14283 groups.push(expr.clone());
14284 }
14285 }
14286 s.group_by = Some(groups);
14287 s.group_by_all = false;
14288 for (_, peer) in &mut s.unions {
14289 expand_group_by_all(peer);
14290 }
14291}
14292
14293fn resolve_order_by_position(s: &mut SelectStatement) {
14294 for order in &mut s.order_by {
14299 match &order.expr {
14300 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14301 if let Ok(idx_one_based) = usize::try_from(*n) {
14302 let idx = idx_one_based - 1;
14303 if idx < s.items.len()
14304 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14305 {
14306 order.expr = expr.clone();
14307 }
14308 }
14309 }
14310 Expr::Column(c) if c.qualifier.is_none() => {
14311 for item in &s.items {
14313 if let SelectItem::Expr {
14314 expr,
14315 alias: Some(a),
14316 } = item
14317 && a == &c.name
14318 {
14319 order.expr = expr.clone();
14320 break;
14321 }
14322 }
14323 }
14324 _ => {}
14325 }
14326 }
14327 for (_, peer) in &mut s.unions {
14328 resolve_order_by_position(peer);
14329 }
14330}
14331
14332fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14345 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14346 match keep {
14347 Some(k) if k < tagged.len() && k > 0 => {
14348 let pivot = k - 1;
14349 tagged.select_nth_unstable_by(pivot, cmp);
14350 tagged[..k].sort_by(cmp);
14351 tagged.truncate(k);
14352 }
14353 _ => {
14354 tagged.sort_by(cmp);
14355 }
14356 }
14357}
14358
14359fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14360 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14361}
14362
14363fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14367 use core::cmp::Ordering;
14368 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14369 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14370 let ord = if descs.get(i).copied().unwrap_or(false) {
14371 ord.reverse()
14372 } else {
14373 ord
14374 };
14375 if ord != Ordering::Equal {
14376 return ord;
14377 }
14378 }
14379 Ordering::Equal
14380}
14381
14382fn build_order_keys(
14385 order_by: &[OrderBy],
14386 row: &Row,
14387 ctx: &EvalContext,
14388) -> Result<Vec<f64>, EngineError> {
14389 let mut keys = Vec::with_capacity(order_by.len());
14390 for o in order_by {
14391 let v = eval::eval_expr(&o.expr, row, ctx)?;
14392 keys.push(value_to_order_key(&v)?);
14393 }
14394 Ok(keys)
14395}
14396
14397fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14401 if let Some(off) = offset {
14402 let off = off as usize;
14403 if off >= rows.len() {
14404 rows.clear();
14405 } else {
14406 rows.drain(..off);
14407 }
14408 }
14409 if let Some(n) = limit {
14410 rows.truncate(n as usize);
14411 }
14412}
14413
14414fn apply_offset_and_limit_tagged(
14425 tagged: &mut Vec<(Vec<f64>, Row)>,
14426 offset: Option<u32>,
14427 limit: Option<u32>,
14428 with_ties: bool,
14429) {
14430 if let Some(off) = offset {
14431 let off = off as usize;
14432 if off >= tagged.len() {
14433 tagged.clear();
14434 } else {
14435 tagged.drain(..off);
14436 }
14437 }
14438 if let Some(n) = limit {
14439 let n = n as usize;
14440 if with_ties && n > 0 && n < tagged.len() {
14441 let cutoff_key = tagged[n - 1].0.clone();
14442 let mut end = n;
14443 while end < tagged.len() && tagged[end].0 == cutoff_key {
14444 end += 1;
14445 }
14446 tagged.truncate(end);
14447 } else {
14448 tagged.truncate(n);
14449 }
14450 }
14451}
14452
14453fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14459 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14460 return Err(EngineError::Unsupported(alloc::string::String::from(
14461 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14462 )));
14463 }
14464 Ok(())
14465}
14466
14467fn resolve_foreign_key(
14481 local_table_name: &str,
14482 local_cols: &[ColumnSchema],
14483 fk: spg_sql::ast::ForeignKeyConstraint,
14484 catalog: &Catalog,
14485) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14486 let mut local_columns = Vec::with_capacity(fk.columns.len());
14488 for name in &fk.columns {
14489 let pos = local_cols
14490 .iter()
14491 .position(|c| c.name == *name)
14492 .ok_or_else(|| {
14493 EngineError::Unsupported(alloc::format!(
14494 "FOREIGN KEY references unknown local column {name:?}"
14495 ))
14496 })?;
14497 local_columns.push(pos);
14498 }
14499 let is_self_ref = fk.parent_table == local_table_name;
14503 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14504 (local_cols, local_table_name)
14505 } else {
14506 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14507 EngineError::Storage(StorageError::TableNotFound {
14508 name: fk.parent_table.clone(),
14509 })
14510 })?;
14511 (
14512 parent_table.schema().columns.as_slice(),
14513 fk.parent_table.as_str(),
14514 )
14515 };
14516 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14521 if fk.columns.len() != 1 {
14522 return Err(EngineError::Unsupported(
14523 "composite FOREIGN KEY without explicit parent column list is not supported \
14524 — list the parent columns explicitly"
14525 .into(),
14526 ));
14527 }
14528 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14530 .ok_or_else(|| {
14531 EngineError::Unsupported(alloc::format!(
14532 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14533 to default the FOREIGN KEY against"
14534 ))
14535 })?;
14536 alloc::vec![pos]
14537 } else {
14538 let mut out = Vec::with_capacity(fk.parent_columns.len());
14539 for name in &fk.parent_columns {
14540 let pos = parent_cols_for_lookup
14541 .iter()
14542 .position(|c| c.name == *name)
14543 .ok_or_else(|| {
14544 EngineError::Unsupported(alloc::format!(
14545 "FOREIGN KEY references unknown parent column \
14546 {name:?} on table {parent_table_str:?}"
14547 ))
14548 })?;
14549 out.push(pos);
14550 }
14551 out
14552 };
14553 if parent_columns.len() != local_columns.len() {
14554 return Err(EngineError::Unsupported(alloc::format!(
14555 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14556 local_columns.len(),
14557 parent_columns.len()
14558 )));
14559 }
14560 if !is_self_ref {
14570 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14571 let primary_parent_col = parent_columns[0];
14572 let has_btree = parent_table
14573 .schema()
14574 .columns
14575 .get(primary_parent_col)
14576 .is_some()
14577 && parent_table.indices().iter().any(|idx| {
14578 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14579 && idx.column_position == primary_parent_col
14580 && idx.partial_predicate.is_none()
14581 });
14582 if !has_btree {
14583 return Err(EngineError::Unsupported(alloc::format!(
14584 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14585 index — create one with `CREATE INDEX ... ON {} ({})` first",
14586 parent_table_str,
14587 parent_table_str,
14588 parent_table.schema().columns[primary_parent_col].name,
14589 )));
14590 }
14591 }
14592 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14593 let on_update = fk_action_sql_to_storage(fk.on_update);
14594 Ok(spg_storage::ForeignKeyConstraint {
14595 name: fk.name,
14596 local_columns,
14597 parent_table: fk.parent_table,
14598 parent_columns,
14599 on_delete,
14600 on_update,
14601 })
14602}
14603
14604fn pick_pk_index_column(
14610 catalog: &Catalog,
14611 parent_name: &str,
14612 is_self_ref: bool,
14613 local_cols: &[ColumnSchema],
14614) -> Option<usize> {
14615 if is_self_ref {
14616 let _ = local_cols;
14620 return Some(0);
14621 }
14622 let parent = catalog.get(parent_name)?;
14623 parent.indices().iter().find_map(|idx| {
14624 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14625 && idx.partial_predicate.is_none()
14626 && idx.included_columns.is_empty()
14627 && idx.expression.is_none()
14628 {
14629 Some(idx.column_position)
14630 } else {
14631 None
14632 }
14633 })
14634}
14635
14636fn resolve_on_conflict_columns(
14643 catalog: &Catalog,
14644 table_name: &str,
14645 target: &[String],
14646) -> Result<Vec<usize>, EngineError> {
14647 let table = catalog.get(table_name).ok_or_else(|| {
14648 EngineError::Storage(StorageError::TableNotFound {
14649 name: table_name.into(),
14650 })
14651 })?;
14652 if target.is_empty() {
14653 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14663 return Ok(uc.columns.clone());
14664 }
14665 let pos = table
14666 .indices()
14667 .iter()
14668 .find_map(|idx| {
14669 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14670 && idx.partial_predicate.is_none()
14671 && idx.included_columns.is_empty()
14672 && idx.expression.is_none()
14673 {
14674 Some(idx.column_position)
14675 } else {
14676 None
14677 }
14678 })
14679 .ok_or_else(|| {
14680 EngineError::Unsupported(alloc::format!(
14681 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14682 ))
14683 })?;
14684 return Ok(alloc::vec![pos]);
14685 }
14686 let mut out = Vec::with_capacity(target.len());
14687 for name in target {
14688 let pos = table
14689 .schema()
14690 .columns
14691 .iter()
14692 .position(|c| c.name == *name)
14693 .ok_or_else(|| {
14694 EngineError::Unsupported(alloc::format!(
14695 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14696 ))
14697 })?;
14698 out.push(pos);
14699 }
14700 Ok(out)
14701}
14702
14703fn on_conflict_key_exists(
14706 catalog: &Catalog,
14707 table_name: &str,
14708 column_pos: usize,
14709 key: &Value,
14710) -> bool {
14711 let Some(table) = catalog.get(table_name) else {
14712 return false;
14713 };
14714 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14715 return false;
14716 };
14717 table.indices().iter().any(|idx| {
14718 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14719 && idx.column_position == column_pos
14720 && idx.partial_predicate.is_none()
14721 && !idx.lookup_eq(&idx_key).is_empty()
14722 })
14723}
14724
14725fn lookup_row_position_by_keys(
14731 catalog: &Catalog,
14732 table_name: &str,
14733 column_positions: &[usize],
14734 key: &[&Value],
14735) -> Option<usize> {
14736 let table = catalog.get(table_name)?;
14737 table.rows().iter().position(|r| {
14738 column_positions
14739 .iter()
14740 .enumerate()
14741 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14742 })
14743}
14744
14745fn on_conflict_keys_exist(
14750 catalog: &Catalog,
14751 table_name: &str,
14752 column_positions: &[usize],
14753 key: &[&Value],
14754) -> bool {
14755 if column_positions.len() == 1 {
14756 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14757 }
14758 let Some(table) = catalog.get(table_name) else {
14759 return false;
14760 };
14761 table.rows().iter().any(|r| {
14762 column_positions
14763 .iter()
14764 .enumerate()
14765 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14766 })
14767}
14768
14769fn apply_on_conflict_assignments(
14782 catalog: &Catalog,
14783 table_name: &str,
14784 target_pos: usize,
14785 incoming: &[Value],
14786 assignments: &[(String, Expr)],
14787 where_: Option<&Expr>,
14788) -> Result<Option<Vec<Value>>, EngineError> {
14789 let table = catalog.get(table_name).ok_or_else(|| {
14790 EngineError::Storage(StorageError::TableNotFound {
14791 name: table_name.into(),
14792 })
14793 })?;
14794 let schema_cols = table.schema().columns.clone();
14795 let existing = table
14796 .rows()
14797 .get(target_pos)
14798 .ok_or_else(|| {
14799 EngineError::Unsupported(alloc::format!(
14800 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14801 ))
14802 })?
14803 .clone();
14804 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14805 if let Some(w) = where_ {
14807 let pred = w.clone();
14808 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14809 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14810 if !matches!(v, Value::Bool(true)) {
14811 return Ok(None);
14812 }
14813 }
14814 let mut new_values = existing.values.clone();
14815 for (col_name, expr) in assignments {
14816 let target_idx = schema_cols
14817 .iter()
14818 .position(|c| c.name == *col_name)
14819 .ok_or_else(|| {
14820 EngineError::Eval(EvalError::ColumnNotFound {
14821 name: col_name.clone(),
14822 })
14823 })?;
14824 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14825 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14826 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14827 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14828 new_values[target_idx] = coerced;
14829 }
14830 Ok(Some(new_values))
14831}
14832
14833fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14838 use spg_sql::ast::ColumnName;
14839 match expr {
14840 Expr::Column(ColumnName { qualifier, name })
14841 if qualifier
14842 .as_deref()
14843 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14844 {
14845 let pos = schema_cols.iter().position(|c| c.name == name);
14846 match pos {
14847 Some(p) => {
14848 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14849 value_to_literal_expr(v)
14850 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14851 }
14852 None => Expr::Column(ColumnName { qualifier, name }),
14853 }
14854 }
14855 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14856 op,
14857 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14858 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14859 },
14860 Expr::Unary { op, expr } => Expr::Unary {
14861 op,
14862 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14863 },
14864 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14865 name,
14866 args: args
14867 .into_iter()
14868 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14869 .collect(),
14870 },
14871 other => other,
14872 }
14873}
14874
14875fn enforce_uniqueness_inserts(
14898 catalog: &Catalog,
14899 child_table: &str,
14900 constraints: &[spg_storage::UniquenessConstraint],
14901 rows: &[Vec<Value>],
14902) -> Result<(), EngineError> {
14903 if constraints.is_empty() {
14904 return Ok(());
14905 }
14906 let table = catalog.get(child_table).ok_or_else(|| {
14907 EngineError::Storage(StorageError::TableNotFound {
14908 name: child_table.into(),
14909 })
14910 })?;
14911 let schema = table.schema();
14912 for uc in constraints {
14913 for (batch_idx, row_values) in rows.iter().enumerate() {
14914 let key: Vec<Value> = uc
14923 .columns
14924 .iter()
14925 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14926 .collect();
14927 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14928 if has_null && !uc.nulls_not_distinct {
14933 continue;
14934 }
14935 let collides_in_table = table.rows().iter().any(|prow| {
14937 uc.columns.iter().enumerate().all(|(i, &p)| {
14938 prow.values
14939 .get(p)
14940 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14941 })
14942 });
14943 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14945 uc.columns.iter().enumerate().all(|(i, &p)| {
14946 earlier
14947 .get(p)
14948 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14949 })
14950 });
14951 if collides_in_table || collides_in_batch {
14952 let kind = if uc.is_primary_key {
14953 "PRIMARY KEY"
14954 } else {
14955 "UNIQUE"
14956 };
14957 let col_names: Vec<String> = uc
14958 .columns
14959 .iter()
14960 .map(|&i| table.schema().columns[i].name.clone())
14961 .collect();
14962 return Err(EngineError::Unsupported(alloc::format!(
14963 "{kind} violation on {child_table:?} columns {col_names:?}: \
14964 row #{batch_idx} duplicates an existing key"
14965 )));
14966 }
14967 }
14968 }
14969 Ok(())
14970}
14971
14972fn collated_key_cell(
14979 v: &spg_storage::Value,
14980 column_position: usize,
14981 schema: &spg_storage::TableSchema,
14982) -> spg_storage::Value {
14983 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14984 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14985 spg_storage::Value::Text(s.to_ascii_lowercase())
14986 }
14987 _ => v.clone(),
14988 }
14989}
14990
14991fn predicate_truthy(v: &spg_storage::Value) -> bool {
14999 use spg_storage::Value as V;
15000 match v {
15001 V::Bool(b) => *b,
15002 V::Int(n) => *n != 0,
15003 V::BigInt(n) => *n != 0,
15004 V::SmallInt(n) => *n != 0,
15005 _ => false,
15006 }
15007}
15008
15009fn check_existing_unique_violation(
15014 idx: &spg_storage::Index,
15015 schema: &spg_storage::TableSchema,
15016 rows: &[spg_storage::Row],
15017) -> Result<(), EngineError> {
15018 let predicate_expr = match idx.partial_predicate.as_deref() {
15019 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15020 EngineError::Unsupported(alloc::format!(
15021 "stored partial predicate {s:?} failed to re-parse: {e:?}"
15022 ))
15023 })?),
15024 None => None,
15025 };
15026 let ctx = eval::EvalContext::new(&schema.columns, None);
15027 let key_positions = unique_key_positions(idx);
15028 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
15029 for row in rows {
15030 if let Some(expr) = &predicate_expr {
15031 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
15032 EngineError::Unsupported(alloc::format!(
15033 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
15034 ))
15035 })?;
15036 if !predicate_truthy(&v) {
15037 continue;
15038 }
15039 }
15040 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
15041 .iter()
15042 .map(|&p| {
15043 let v = row
15044 .values
15045 .get(p)
15046 .cloned()
15047 .unwrap_or(spg_storage::Value::Null);
15048 collated_key_cell(&v, p, schema)
15049 })
15050 .collect();
15051 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15052 continue;
15053 }
15054 if seen.iter().any(|other| *other == key) {
15055 return Err(EngineError::Unsupported(alloc::format!(
15056 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
15057 idx.name
15058 )));
15059 }
15060 seen.push(key);
15061 }
15062 Ok(())
15063}
15064
15065fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
15069 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
15070 out.push(idx.column_position);
15071 out.extend_from_slice(&idx.extra_column_positions);
15072 out
15073}
15074
15075fn enforce_unique_index_inserts(
15083 catalog: &Catalog,
15084 table_name: &str,
15085 rows: &[alloc::vec::Vec<spg_storage::Value>],
15086) -> Result<(), EngineError> {
15087 let table = catalog.get(table_name).ok_or_else(|| {
15088 EngineError::Storage(StorageError::TableNotFound {
15089 name: table_name.into(),
15090 })
15091 })?;
15092 let schema = table.schema();
15093 let ctx = eval::EvalContext::new(&schema.columns, None);
15094 for idx in table.indices() {
15095 if !idx.is_unique {
15096 continue;
15097 }
15098 let predicate_expr = match idx.partial_predicate.as_deref() {
15100 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15101 EngineError::Unsupported(alloc::format!(
15102 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15103 idx.name
15104 ))
15105 })?),
15106 None => None,
15107 };
15108 let key_positions = unique_key_positions(idx);
15109 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15110 key_positions
15114 .iter()
15115 .map(|&p| {
15116 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15117 collated_key_cell(&v, p, schema)
15118 })
15119 .collect()
15120 };
15121 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15125 let Some(expr) = &predicate_expr else {
15126 return Ok(true);
15127 };
15128 let tmp_row = spg_storage::Row {
15129 values: values.to_vec(),
15130 };
15131 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15132 EngineError::Unsupported(alloc::format!(
15133 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15134 idx.name
15135 ))
15136 })?;
15137 Ok(predicate_truthy(&v))
15138 };
15139 for (batch_idx, row_values) in rows.iter().enumerate() {
15140 if !participates(row_values)? {
15141 continue;
15142 }
15143 let key = key_of(row_values);
15144 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15145 continue;
15146 }
15147 for prow in table.rows() {
15149 if !participates(&prow.values)? {
15150 continue;
15151 }
15152 if key_of(&prow.values) == key {
15153 return Err(EngineError::Unsupported(alloc::format!(
15154 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15155 row #{batch_idx} duplicates an existing key",
15156 idx.name
15157 )));
15158 }
15159 }
15160 for earlier in &rows[..batch_idx] {
15162 if !participates(earlier)? {
15163 continue;
15164 }
15165 if key_of(earlier) == key {
15166 return Err(EngineError::Unsupported(alloc::format!(
15167 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15168 row #{batch_idx} duplicates an earlier row in the same batch",
15169 idx.name
15170 )));
15171 }
15172 }
15173 }
15174 }
15175 Ok(())
15176}
15177
15178fn any_column_changed(
15186 filter_cols: &[String],
15187 schema_cols: &[ColumnSchema],
15188 old_row: &Row,
15189 new_row: &Row,
15190) -> bool {
15191 for col_name in filter_cols {
15192 let Some(pos) = schema_cols
15193 .iter()
15194 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15195 else {
15196 continue;
15197 };
15198 let old_v = old_row.values.get(pos);
15199 let new_v = new_row.values.get(pos);
15200 if old_v != new_v {
15201 return true;
15202 }
15203 }
15204 false
15205}
15206
15207fn enforce_check_constraints(
15212 catalog: &Catalog,
15213 table_name: &str,
15214 rows: &[alloc::vec::Vec<spg_storage::Value>],
15215) -> Result<(), EngineError> {
15216 let table = catalog.get(table_name).ok_or_else(|| {
15217 EngineError::Storage(StorageError::TableNotFound {
15218 name: table_name.into(),
15219 })
15220 })?;
15221 let schema = table.schema();
15222 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15226 alloc::vec::Vec::new();
15227 for (idx, col) in schema.columns.iter().enumerate() {
15228 let Some(dname) = &col.user_domain_type else {
15229 continue;
15230 };
15231 let Some(dom) = catalog.domain_types().get(dname) else {
15232 continue;
15233 };
15234 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15235 alloc::vec::Vec::with_capacity(dom.checks.len());
15236 for src in &dom.checks {
15237 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15238 EngineError::Unsupported(alloc::format!(
15239 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15240 col.name
15241 ))
15242 })?;
15243 parsed_for_col.push(expr);
15244 }
15245 if !parsed_for_col.is_empty() {
15246 domain_checks_per_col.push((idx, parsed_for_col));
15247 }
15248 }
15249 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15250 return Ok(());
15251 }
15252 let ctx = eval::EvalContext::new(&schema.columns, None);
15253 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15254 for (i, src) in schema.checks.iter().enumerate() {
15255 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15256 EngineError::Unsupported(alloc::format!(
15257 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15258 ))
15259 })?;
15260 parsed.push((i, expr));
15261 }
15262 for (batch_idx, row_values) in rows.iter().enumerate() {
15263 let tmp_row = spg_storage::Row {
15264 values: row_values.clone(),
15265 };
15266 for (i, expr) in &parsed {
15267 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15268 EngineError::Unsupported(alloc::format!(
15269 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15270 ))
15271 })?;
15272 if matches!(v, spg_storage::Value::Bool(false)) {
15274 return Err(EngineError::Unsupported(alloc::format!(
15275 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15276 schema.checks[*i]
15277 )));
15278 }
15279 }
15280 for (col_idx, checks) in &domain_checks_per_col {
15286 let cell = row_values
15287 .get(*col_idx)
15288 .cloned()
15289 .unwrap_or(spg_storage::Value::Null);
15290 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15291 "value",
15292 schema.columns[*col_idx].ty,
15293 schema.columns[*col_idx].nullable,
15294 )];
15295 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15296 let synth_row = spg_storage::Row {
15297 values: alloc::vec![cell],
15298 };
15299 for (ci, expr) in checks.iter().enumerate() {
15300 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15301 EngineError::Unsupported(alloc::format!(
15302 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15303 schema.columns[*col_idx].name
15304 ))
15305 })?;
15306 if matches!(v, spg_storage::Value::Bool(false)) {
15307 return Err(EngineError::Unsupported(alloc::format!(
15308 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15309 schema.columns[*col_idx].name
15310 )));
15311 }
15312 }
15313 }
15314 }
15315 Ok(())
15316}
15317
15318fn enforce_fk_inserts(
15319 catalog: &Catalog,
15320 child_table: &str,
15321 fks: &[spg_storage::ForeignKeyConstraint],
15322 rows: &[Vec<Value>],
15323) -> Result<(), EngineError> {
15324 for fk in fks {
15325 let parent_is_self = fk.parent_table == child_table;
15326 let parent = if parent_is_self {
15327 catalog.get(child_table).ok_or_else(|| {
15330 EngineError::Storage(StorageError::TableNotFound {
15331 name: child_table.into(),
15332 })
15333 })?
15334 } else {
15335 catalog.get(&fk.parent_table).ok_or_else(|| {
15336 EngineError::Storage(StorageError::TableNotFound {
15337 name: fk.parent_table.clone(),
15338 })
15339 })?
15340 };
15341 for (batch_idx, row_values) in rows.iter().enumerate() {
15342 if fk.local_columns.len() == 1 {
15346 let v = &row_values[fk.local_columns[0]];
15347 if matches!(v, Value::Null) {
15348 continue;
15349 }
15350 let parent_col = fk.parent_columns[0];
15351 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15352 EngineError::Unsupported(alloc::format!(
15353 "FOREIGN KEY column value of type {:?} is not index-eligible",
15354 v.data_type()
15355 ))
15356 })?;
15357 let present_committed = parent.indices().iter().any(|idx| {
15358 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15359 && idx.column_position == parent_col
15360 && idx.partial_predicate.is_none()
15361 && !idx.lookup_eq(&key).is_empty()
15362 });
15363 let present_in_batch = parent_is_self
15367 && rows[..batch_idx]
15368 .iter()
15369 .any(|earlier| earlier.get(parent_col) == Some(v));
15370 if !(present_committed || present_in_batch) {
15371 return Err(EngineError::Unsupported(alloc::format!(
15372 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15373 fk.parent_table,
15374 parent
15375 .schema()
15376 .columns
15377 .get(parent_col)
15378 .map_or("?", |c| c.name.as_str()),
15379 v,
15380 )));
15381 }
15382 } else {
15383 if fk
15387 .local_columns
15388 .iter()
15389 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15390 {
15391 continue;
15392 }
15393 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15394 let parent_match_committed = parent.rows().iter().any(|prow| {
15395 fk.parent_columns
15396 .iter()
15397 .enumerate()
15398 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15399 });
15400 let parent_match_in_batch = parent_is_self
15401 && rows[..batch_idx].iter().any(|earlier| {
15402 fk.parent_columns
15403 .iter()
15404 .enumerate()
15405 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15406 });
15407 if !(parent_match_committed || parent_match_in_batch) {
15408 return Err(EngineError::Unsupported(alloc::format!(
15409 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15410 fk.parent_table,
15411 )));
15412 }
15413 }
15414 }
15415 }
15416 Ok(())
15417}
15418
15419#[derive(Debug, Clone)]
15423struct FkChildStep {
15424 child_table: String,
15425 action: FkChildAction,
15426}
15427
15428#[derive(Debug, Clone)]
15429enum FkChildAction {
15430 Delete { positions: Vec<usize> },
15432 SetNull {
15436 positions: Vec<usize>,
15437 columns: Vec<usize>,
15438 },
15439 SetDefault {
15443 positions: Vec<usize>,
15444 columns: Vec<usize>,
15445 defaults: Vec<Value>,
15446 },
15447}
15448
15449fn plan_fk_parent_deletions(
15465 catalog: &Catalog,
15466 parent_table_name: &str,
15467 to_delete_positions: &[usize],
15468 to_delete_rows: &[Vec<Value>],
15469) -> Result<Vec<FkChildStep>, EngineError> {
15470 use alloc::collections::{BTreeMap, BTreeSet};
15471 if to_delete_rows.is_empty() {
15472 return Ok(Vec::new());
15473 }
15474 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15475 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15477 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15478 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15479 for &p in to_delete_positions {
15480 visited.insert((parent_table_name.to_string(), p));
15481 }
15482 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15483 .iter()
15484 .map(|r| (parent_table_name.to_string(), r.clone()))
15485 .collect();
15486 while let Some((cur_parent, parent_row)) = work.pop() {
15487 for child_name in catalog.table_names() {
15488 let child = catalog
15489 .get(&child_name)
15490 .expect("table_names → catalog.get round-trip is total");
15491 for fk in &child.schema().foreign_keys {
15492 if fk.parent_table != cur_parent {
15493 continue;
15494 }
15495 let parent_key: Vec<&Value> = fk
15496 .parent_columns
15497 .iter()
15498 .map(|&pi| &parent_row[pi])
15499 .collect();
15500 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15501 continue;
15502 }
15503 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15504 if child_name == cur_parent
15505 && visited.contains(&(child_name.clone(), child_row_idx))
15506 {
15507 continue;
15508 }
15509 let matches_key = fk
15510 .local_columns
15511 .iter()
15512 .enumerate()
15513 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15514 if !matches_key {
15515 continue;
15516 }
15517 match fk.on_delete {
15518 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15519 return Err(EngineError::Unsupported(alloc::format!(
15520 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15521 restricted by FK from {child_name:?}.{:?}",
15522 fk.local_columns,
15523 )));
15524 }
15525 spg_storage::FkAction::Cascade => {
15526 if visited.insert((child_name.clone(), child_row_idx)) {
15527 delete_plan
15528 .entry(child_name.clone())
15529 .or_default()
15530 .insert(child_row_idx);
15531 work.push((child_name.clone(), child_row.values.clone()));
15532 }
15533 }
15534 spg_storage::FkAction::SetNull => {
15535 for &li in &fk.local_columns {
15537 let col = child.schema().columns.get(li).ok_or_else(|| {
15538 EngineError::Unsupported(alloc::format!(
15539 "FK local column {li} missing in {child_name:?}"
15540 ))
15541 })?;
15542 if !col.nullable {
15543 return Err(EngineError::Unsupported(alloc::format!(
15544 "FOREIGN KEY ON DELETE SET NULL: column \
15545 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15546 col.name,
15547 )));
15548 }
15549 }
15550 let entry = setnull_plan.entry(child_name.clone()).or_default();
15551 for &li in &fk.local_columns {
15552 entry.insert((child_row_idx, li));
15553 }
15554 }
15555 spg_storage::FkAction::SetDefault => {
15556 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15558 for &li in &fk.local_columns {
15559 let col = child.schema().columns.get(li).ok_or_else(|| {
15560 EngineError::Unsupported(alloc::format!(
15561 "FK local column {li} missing in {child_name:?}"
15562 ))
15563 })?;
15564 let default = col.default.clone().ok_or_else(|| {
15565 EngineError::Unsupported(alloc::format!(
15566 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15567 {child_name:?}.{:?} has no DEFAULT declared",
15568 col.name,
15569 ))
15570 })?;
15571 entry.insert((child_row_idx, li), default);
15572 }
15573 }
15574 }
15575 }
15576 }
15577 }
15578 }
15579 let mut steps: Vec<FkChildStep> = Vec::new();
15587 for (child_table, entries) in setnull_plan {
15588 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15589 steps.push(FkChildStep {
15590 child_table,
15591 action: FkChildAction::SetNull { positions, columns },
15592 });
15593 }
15594 for (child_table, entries) in setdefault_plan {
15595 let mut positions = Vec::with_capacity(entries.len());
15596 let mut columns = Vec::with_capacity(entries.len());
15597 let mut defaults = Vec::with_capacity(entries.len());
15598 for ((p, c), v) in entries {
15599 positions.push(p);
15600 columns.push(c);
15601 defaults.push(v);
15602 }
15603 steps.push(FkChildStep {
15604 child_table,
15605 action: FkChildAction::SetDefault {
15606 positions,
15607 columns,
15608 defaults,
15609 },
15610 });
15611 }
15612 for (child_table, positions) in delete_plan {
15613 steps.push(FkChildStep {
15614 child_table,
15615 action: FkChildAction::Delete {
15616 positions: positions.into_iter().collect(),
15617 },
15618 });
15619 }
15620 Ok(steps)
15621}
15622
15623fn plan_fk_parent_updates(
15640 catalog: &Catalog,
15641 parent_table_name: &str,
15642 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15643) -> Result<Vec<FkChildStep>, EngineError> {
15644 use alloc::collections::BTreeMap;
15645 if plan_with_old.is_empty() {
15646 return Ok(Vec::new());
15647 }
15648 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15653 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15654 BTreeMap::new();
15655 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15656 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15658
15659 for child_name in catalog.table_names() {
15660 let child = catalog
15661 .get(&child_name)
15662 .expect("table_names → catalog.get total");
15663 for fk in &child.schema().foreign_keys {
15664 if fk.parent_table != parent_table_name {
15665 continue;
15666 }
15667 for (_pos, old_row, new_row) in plan_with_old {
15668 let key_changed = fk
15670 .parent_columns
15671 .iter()
15672 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15673 if !key_changed {
15674 continue;
15675 }
15676 let old_key: Vec<&Value> =
15678 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15679 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15680 continue;
15682 }
15683 let new_key: Vec<&Value> =
15684 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15685 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15686 if child_name == parent_table_name
15689 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15690 {
15691 continue;
15692 }
15693 let matches_key = fk
15694 .local_columns
15695 .iter()
15696 .enumerate()
15697 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15698 if !matches_key {
15699 continue;
15700 }
15701 match fk.on_update {
15702 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15703 return Err(EngineError::Unsupported(alloc::format!(
15704 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15705 restricted by FK from {child_name:?}.{:?}",
15706 fk.local_columns,
15707 )));
15708 }
15709 spg_storage::FkAction::Cascade => {
15710 let entry = cascade_plan.entry(child_name.clone()).or_default();
15712 for (i, &li) in fk.local_columns.iter().enumerate() {
15713 entry.insert((child_row_idx, li), new_key[i].clone());
15714 }
15715 }
15716 spg_storage::FkAction::SetNull => {
15717 for &li in &fk.local_columns {
15718 let col = child.schema().columns.get(li).ok_or_else(|| {
15719 EngineError::Unsupported(alloc::format!(
15720 "FK local column {li} missing in {child_name:?}"
15721 ))
15722 })?;
15723 if !col.nullable {
15724 return Err(EngineError::Unsupported(alloc::format!(
15725 "FOREIGN KEY ON UPDATE SET NULL: column \
15726 {child_name:?}.{:?} is NOT NULL",
15727 col.name,
15728 )));
15729 }
15730 }
15731 let entry = setnull_plan.entry(child_name.clone()).or_default();
15732 for &li in &fk.local_columns {
15733 entry.insert((child_row_idx, li));
15734 }
15735 }
15736 spg_storage::FkAction::SetDefault => {
15737 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15738 for &li in &fk.local_columns {
15739 let col = child.schema().columns.get(li).ok_or_else(|| {
15740 EngineError::Unsupported(alloc::format!(
15741 "FK local column {li} missing in {child_name:?}"
15742 ))
15743 })?;
15744 let default = col.default.clone().ok_or_else(|| {
15745 EngineError::Unsupported(alloc::format!(
15746 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15747 {child_name:?}.{:?} has no DEFAULT",
15748 col.name,
15749 ))
15750 })?;
15751 entry.insert((child_row_idx, li), default);
15752 }
15753 }
15754 }
15755 }
15756 }
15757 }
15758 }
15759 let mut steps: Vec<FkChildStep> = Vec::new();
15762 for (child_table, entries) in cascade_plan {
15763 let mut positions = Vec::with_capacity(entries.len());
15764 let mut columns = Vec::with_capacity(entries.len());
15765 let mut defaults = Vec::with_capacity(entries.len());
15766 for ((p, c), v) in entries {
15767 positions.push(p);
15768 columns.push(c);
15769 defaults.push(v);
15770 }
15771 steps.push(FkChildStep {
15776 child_table,
15777 action: FkChildAction::SetDefault {
15778 positions,
15779 columns,
15780 defaults,
15781 },
15782 });
15783 }
15784 for (child_table, entries) in setnull_plan {
15785 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15786 steps.push(FkChildStep {
15787 child_table,
15788 action: FkChildAction::SetNull { positions, columns },
15789 });
15790 }
15791 for (child_table, entries) in setdefault_plan {
15792 let mut positions = Vec::with_capacity(entries.len());
15793 let mut columns = Vec::with_capacity(entries.len());
15794 let mut defaults = Vec::with_capacity(entries.len());
15795 for ((p, c), v) in entries {
15796 positions.push(p);
15797 columns.push(c);
15798 defaults.push(v);
15799 }
15800 steps.push(FkChildStep {
15801 child_table,
15802 action: FkChildAction::SetDefault {
15803 positions,
15804 columns,
15805 defaults,
15806 },
15807 });
15808 }
15809 let _ = delete_plan; Ok(steps)
15811}
15812
15813fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15817 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15818 EngineError::Storage(StorageError::TableNotFound {
15819 name: step.child_table.clone(),
15820 })
15821 })?;
15822 match &step.action {
15823 FkChildAction::Delete { positions } => {
15824 let _ = child.delete_rows(positions);
15825 }
15826 FkChildAction::SetNull { positions, columns } => {
15827 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15828 }
15829 FkChildAction::SetDefault {
15830 positions,
15831 columns,
15832 defaults,
15833 } => {
15834 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15835 }
15836 }
15837 Ok(())
15838}
15839
15840fn apply_per_cell_writes(
15846 child: &mut spg_storage::Table,
15847 positions: &[usize],
15848 columns: &[usize],
15849 mut value_for: impl FnMut(usize) -> Value,
15850) -> Result<(), EngineError> {
15851 use alloc::collections::BTreeMap;
15852 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15853 for i in 0..positions.len() {
15854 by_row
15855 .entry(positions[i])
15856 .or_default()
15857 .push((columns[i], value_for(i)));
15858 }
15859 for (pos, mutations) in by_row {
15860 let mut new_values = child.rows()[pos].values.clone();
15861 for (col, v) in mutations {
15862 if let Some(slot) = new_values.get_mut(col) {
15863 *slot = v;
15864 }
15865 }
15866 child
15867 .update_row(pos, new_values)
15868 .map_err(EngineError::Storage)?;
15869 }
15870 Ok(())
15871}
15872
15873fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15874 match a {
15875 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15876 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15877 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15878 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15879 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15880 }
15881}
15882
15883fn resolve_column_default_free(
15889 col: &ColumnSchema,
15890 clock_fn: Option<ClockFn>,
15891) -> Result<Value, EngineError> {
15892 if let Some(rt) = &col.runtime_default {
15893 return eval_runtime_default_free(rt, col.ty, clock_fn);
15894 }
15895 Ok(col.default.clone().unwrap_or(Value::Null))
15896}
15897
15898fn eval_runtime_default_free(
15899 rt: &str,
15900 ty: DataType,
15901 clock_fn: Option<ClockFn>,
15902) -> Result<Value, EngineError> {
15903 let s = rt.trim().to_ascii_lowercase();
15904 let with_no_parens = s.trim_end_matches("()");
15910 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15911 if with_no_parens.ends_with(')') {
15912 &with_no_parens[..open_idx]
15913 } else {
15914 with_no_parens
15915 }
15916 } else {
15917 with_no_parens
15918 };
15919 let now_us = match clock_fn {
15920 Some(f) => f(),
15921 None => 0,
15922 };
15923 let v = match canonical {
15924 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15925 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15926 "current_time" | "localtime" => Value::Timestamp(now_us),
15927 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15933 other => {
15934 return Err(EngineError::Unsupported(alloc::format!(
15935 "runtime DEFAULT expression {other:?} not supported \
15936 (v7.17.0 whitelist: now() / current_timestamp / \
15937 current_date / current_time / localtimestamp / \
15938 localtime / gen_random_uuid() / \
15939 uuid_generate_v4())"
15940 )));
15941 }
15942 };
15943 coerce_value(v, ty, "DEFAULT", 0)
15944}
15945
15946fn is_runtime_default_expr(expr: &Expr) -> bool {
15952 match expr {
15953 Expr::FunctionCall { .. } => true,
15954 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15955 _ => false,
15956 }
15957}
15958
15959fn canonicalize_set_value(
15972 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15973 col_idx: usize,
15974 col_name: &str,
15975 value: Value,
15976) -> Result<Value, EngineError> {
15977 let Some(variants) = lookup.get(&col_idx) else {
15978 return Ok(value);
15979 };
15980 match value {
15981 Value::Null => Ok(Value::Null),
15982 Value::Text(s) => {
15983 if s.is_empty() {
15984 return Ok(Value::Text(alloc::string::String::new()));
15985 }
15986 let mut present = alloc::vec![false; variants.len()];
15989 for raw in s.split(',') {
15990 let tok = raw.trim();
15991 if tok.is_empty() {
15992 continue;
15993 }
15994 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15995 EngineError::Unsupported(alloc::format!(
15996 "column {col_name:?}: invalid SET token {tok:?}; \
15997 allowed: {variants:?}"
15998 ))
15999 })?;
16000 present[idx] = true;
16001 }
16002 let mut out = alloc::string::String::new();
16004 let mut first = true;
16005 for (i, keep) in present.iter().enumerate() {
16006 if !keep {
16007 continue;
16008 }
16009 if !first {
16010 out.push(',');
16011 }
16012 first = false;
16013 out.push_str(&variants[i]);
16014 }
16015 Ok(Value::Text(out))
16016 }
16017 other => Err(EngineError::Unsupported(alloc::format!(
16018 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
16019 other.data_type()
16020 ))),
16021 }
16022}
16023
16024fn enforce_enum_label(
16025 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
16026 col_idx: usize,
16027 col_name: &str,
16028 value: &Value,
16029) -> Result<(), EngineError> {
16030 if let Some(labels) = lookup.get(&col_idx) {
16031 match value {
16032 Value::Null => Ok(()),
16033 Value::Text(s) => {
16034 if labels.iter().any(|l| l == s) {
16035 Ok(())
16036 } else {
16037 Err(EngineError::Unsupported(alloc::format!(
16038 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
16039 )))
16040 }
16041 }
16042 other => Err(EngineError::Unsupported(alloc::format!(
16043 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
16044 other.data_type()
16045 ))),
16046 }
16047 } else {
16048 Ok(())
16049 }
16050}
16051
16052fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
16053 let ty = column_type_to_data_type(c.ty);
16054 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
16055 if let Some(name) = c.user_type_ref {
16062 schema.user_enum_type = Some(name);
16063 }
16064 if let Some(expr) = c.on_update_runtime {
16067 schema.on_update_runtime = Some(alloc::format!("{expr}"));
16068 }
16069 schema.collation = match c.collation {
16073 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
16074 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
16075 };
16076 schema.is_unsigned = c.is_unsigned;
16079 schema.inline_enum_variants = c.inline_enum_variants;
16083 schema.inline_set_variants = c.inline_set_variants;
16087 if let Some(default_expr) = c.default {
16088 if is_runtime_default_expr(&default_expr) {
16094 let display = alloc::format!("{default_expr}");
16095 schema = schema.with_runtime_default(display);
16096 } else {
16097 let raw = literal_expr_to_value(default_expr)?;
16098 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16099 schema = schema.with_default(coerced);
16100 }
16101 }
16102 if c.auto_increment {
16103 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16105 return Err(EngineError::Unsupported(alloc::format!(
16106 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16107 )));
16108 }
16109 schema = schema.with_auto_increment();
16110 }
16111 Ok(schema)
16112}
16113
16114fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16119 let s = s.trim();
16120 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16121 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16123 if cleaned.len() % 2 != 0 {
16124 return Err("odd-length hex literal");
16125 }
16126 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16127 let cleaned_bytes = cleaned.as_bytes();
16128 for i in (0..cleaned_bytes.len()).step_by(2) {
16129 let hi = hex_nibble(cleaned_bytes[i])?;
16130 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16131 out.push((hi << 4) | lo);
16132 }
16133 return Ok(out);
16134 }
16135 let bytes = s.as_bytes();
16138 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16139 let mut i = 0;
16140 while i < bytes.len() {
16141 let b = bytes[i];
16142 if b == b'\\' && i + 1 < bytes.len() {
16143 let n = bytes[i + 1];
16144 if n == b'\\' {
16145 out.push(b'\\');
16146 i += 2;
16147 continue;
16148 }
16149 if n.is_ascii_digit()
16150 && i + 3 < bytes.len()
16151 && bytes[i + 2].is_ascii_digit()
16152 && bytes[i + 3].is_ascii_digit()
16153 {
16154 let oct = |x: u8| (x - b'0') as u32;
16155 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16156 if v <= 0xFF {
16157 out.push(v as u8);
16158 i += 4;
16159 continue;
16160 }
16161 }
16162 }
16163 out.push(b);
16164 i += 1;
16165 }
16166 Ok(out)
16167}
16168
16169fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16170 match b {
16171 b'0'..=b'9' => Ok(b - b'0'),
16172 b'a'..=b'f' => Ok(b - b'a' + 10),
16173 b'A'..=b'F' => Ok(b - b'A' + 10),
16174 _ => Err("invalid hex digit"),
16175 }
16176}
16177
16178fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16192 let mut has_text = false;
16193 let mut has_bigint = false;
16194 let mut has_int = false;
16195 for v in &items {
16196 match v {
16197 Value::Null => {}
16198 Value::Text(_) | Value::Json(_) => has_text = true,
16199 Value::BigInt(_) => has_bigint = true,
16200 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16201 _ => has_text = true,
16202 }
16203 }
16204 if has_text || (!has_bigint && !has_int) {
16205 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16206 .into_iter()
16207 .map(|v| match v {
16208 Value::Null => None,
16209 Value::Text(s) | Value::Json(s) => Some(s),
16210 other => Some(alloc::format!("{other:?}")),
16211 })
16212 .collect();
16213 return Value::TextArray(out);
16214 }
16215 if has_bigint {
16216 let out: alloc::vec::Vec<Option<i64>> = items
16217 .into_iter()
16218 .map(|v| match v {
16219 Value::Null => None,
16220 Value::Int(n) => Some(i64::from(n)),
16221 Value::SmallInt(n) => Some(i64::from(n)),
16222 Value::BigInt(n) => Some(n),
16223 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16224 })
16225 .collect();
16226 return Value::BigIntArray(out);
16227 }
16228 let out: alloc::vec::Vec<Option<i32>> = items
16229 .into_iter()
16230 .map(|v| match v {
16231 Value::Null => None,
16232 Value::Int(n) => Some(n),
16233 Value::SmallInt(n) => Some(i32::from(n)),
16234 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16235 })
16236 .collect();
16237 Value::IntArray(out)
16238}
16239
16240fn decode_text_array_literal(
16241 s: &str,
16242) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16243 let trimmed = s.trim();
16244 let inner = trimmed
16245 .strip_prefix('{')
16246 .and_then(|x| x.strip_suffix('}'))
16247 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16248 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16249 if inner.trim().is_empty() {
16250 return Ok(out);
16251 }
16252 let bytes = inner.as_bytes();
16253 let mut i = 0;
16254 while i <= bytes.len() {
16255 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16257 i += 1;
16258 }
16259 if i < bytes.len() && bytes[i] == b'"' {
16261 i += 1; let mut buf = alloc::string::String::new();
16263 while i < bytes.len() && bytes[i] != b'"' {
16264 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16265 buf.push(bytes[i + 1] as char);
16266 i += 2;
16267 } else {
16268 buf.push(bytes[i] as char);
16269 i += 1;
16270 }
16271 }
16272 if i >= bytes.len() {
16273 return Err("unterminated quoted element");
16274 }
16275 i += 1; out.push(Some(buf));
16277 } else {
16278 let start = i;
16280 while i < bytes.len() && bytes[i] != b',' {
16281 i += 1;
16282 }
16283 let raw = inner[start..i].trim();
16284 if raw.eq_ignore_ascii_case("NULL") {
16285 out.push(None);
16286 } else {
16287 out.push(Some(alloc::string::ToString::to_string(raw)));
16288 }
16289 }
16290 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16292 i += 1;
16293 }
16294 if i >= bytes.len() {
16295 break;
16296 }
16297 if bytes[i] != b',' {
16298 return Err("expected ',' between TEXT[] elements");
16299 }
16300 i += 1;
16301 }
16302 Ok(out)
16303}
16304
16305fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16310 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16311 out.push('{');
16312 for (i, item) in items.iter().enumerate() {
16313 if i > 0 {
16314 out.push(',');
16315 }
16316 match item {
16317 None => out.push_str("NULL"),
16318 Some(s) => {
16319 let needs_quote = s.is_empty()
16320 || s.eq_ignore_ascii_case("NULL")
16321 || s.chars()
16322 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16323 if needs_quote {
16324 out.push('"');
16325 for c in s.chars() {
16326 if c == '"' || c == '\\' {
16327 out.push('\\');
16328 }
16329 out.push(c);
16330 }
16331 out.push('"');
16332 } else {
16333 out.push_str(s);
16334 }
16335 }
16336 }
16337 }
16338 out.push('}');
16339 out
16340}
16341
16342fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16346 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16347 out.push_str("\\x");
16348 for byte in b {
16349 let hi = byte >> 4;
16350 let lo = byte & 0x0F;
16351 out.push(hex_digit(hi));
16352 out.push(hex_digit(lo));
16353 }
16354 out
16355}
16356
16357const fn hex_digit(n: u8) -> char {
16358 match n {
16359 0..=9 => (b'0' + n) as char,
16360 10..=15 => (b'a' + n - 10) as char,
16361 _ => '?',
16362 }
16363}
16364
16365fn parse_hstore_str(
16377 s: &str,
16378) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16379 let bytes = s.as_bytes();
16380 let mut i = 0;
16381 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16382 let skip_ws = |bytes: &[u8], i: &mut usize| {
16383 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16384 *i += 1;
16385 }
16386 };
16387 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16388 if *i >= bytes.len() {
16389 return None;
16390 }
16391 if bytes[*i] == b'"' {
16392 *i += 1;
16393 let mut out = alloc::string::String::new();
16394 while *i < bytes.len() {
16395 match bytes[*i] {
16396 b'"' => {
16397 *i += 1;
16398 return Some(out);
16399 }
16400 b'\\' if *i + 1 < bytes.len() => {
16401 out.push(bytes[*i + 1] as char);
16402 *i += 2;
16403 }
16404 c => {
16405 out.push(c as char);
16406 *i += 1;
16407 }
16408 }
16409 }
16410 None
16411 } else {
16412 let start = *i;
16413 while *i < bytes.len()
16414 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16415 {
16416 *i += 1;
16417 }
16418 if *i == start {
16419 return None;
16420 }
16421 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16422 }
16423 };
16424 skip_ws(bytes, &mut i);
16425 while i < bytes.len() {
16426 let key = parse_token(bytes, &mut i)?;
16427 skip_ws(bytes, &mut i);
16428 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16429 return None;
16430 }
16431 i += 2;
16432 skip_ws(bytes, &mut i);
16433 let val_token = if i + 4 <= bytes.len()
16435 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16436 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16437 {
16438 i += 4;
16439 None
16440 } else {
16441 Some(parse_token(bytes, &mut i)?)
16442 };
16443 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16445 out[pos] = (key, val_token);
16446 } else {
16447 out.push((key, val_token));
16448 }
16449 skip_ws(bytes, &mut i);
16450 if i >= bytes.len() {
16451 break;
16452 }
16453 if bytes[i] == b',' {
16454 i += 1;
16455 skip_ws(bytes, &mut i);
16456 continue;
16457 }
16458 return None;
16459 }
16460 Some(out)
16461}
16462
16463fn format_hstore_str(
16467 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16468) -> alloc::string::String {
16469 let mut out = alloc::string::String::new();
16470 for (i, (k, v)) in pairs.iter().enumerate() {
16471 if i > 0 {
16472 out.push_str(", ");
16473 }
16474 out.push('"');
16475 out.push_str(k);
16476 out.push_str("\"=>");
16477 match v {
16478 None => out.push_str("NULL"),
16479 Some(val) => {
16480 out.push('"');
16481 out.push_str(val);
16482 out.push('"');
16483 }
16484 }
16485 }
16486 out
16487}
16488
16489pub fn format_hstore_text(
16492 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16493) -> alloc::string::String {
16494 format_hstore_str(pairs)
16495}
16496
16497fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16502 let s = s.trim();
16503 let outer = s
16504 .strip_prefix('{')
16505 .and_then(|x| x.strip_suffix('}'))
16506 .ok_or("missing outer '{...}' braces")?;
16507 let trimmed = outer.trim();
16508 if trimmed.is_empty() {
16509 return Ok(Vec::new());
16510 }
16511 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16512 let mut i = 0;
16513 let bytes = trimmed.as_bytes();
16514 while i < bytes.len() {
16515 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16516 i += 1;
16517 }
16518 if i >= bytes.len() {
16519 break;
16520 }
16521 if bytes[i] != b'{' {
16522 return Err("expected '{' opening a row");
16523 }
16524 i += 1;
16525 let row_start = i;
16526 let mut depth = 1;
16527 while i < bytes.len() && depth > 0 {
16528 match bytes[i] {
16529 b'{' => depth += 1,
16530 b'}' => depth -= 1,
16531 _ => {}
16532 }
16533 if depth > 0 {
16534 i += 1;
16535 }
16536 }
16537 if depth != 0 {
16538 return Err("unbalanced '{...}' in row");
16539 }
16540 let row_text = &trimmed[row_start..i];
16541 i += 1;
16542 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16543 Vec::new()
16544 } else {
16545 row_text.split(',').map(|t| t.trim().to_string()).collect()
16546 };
16547 rows.push(cells);
16548 }
16549 if let Some(first) = rows.first() {
16550 let cols = first.len();
16551 for r in &rows {
16552 if r.len() != cols {
16553 return Err("ragged 2D array (rows have different column counts)");
16554 }
16555 }
16556 }
16557 Ok(rows)
16558}
16559
16560fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16561 let raw = split_2d_literal(s)?;
16562 raw.into_iter()
16563 .map(|row| {
16564 row.into_iter()
16565 .map(|cell| {
16566 if cell.eq_ignore_ascii_case("NULL") {
16567 Ok(None)
16568 } else {
16569 cell.parse::<i32>()
16570 .map(Some)
16571 .map_err(|_| "invalid int element")
16572 }
16573 })
16574 .collect()
16575 })
16576 .collect()
16577}
16578
16579fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16580 let raw = split_2d_literal(s)?;
16581 raw.into_iter()
16582 .map(|row| {
16583 row.into_iter()
16584 .map(|cell| {
16585 if cell.eq_ignore_ascii_case("NULL") {
16586 Ok(None)
16587 } else {
16588 cell.parse::<i64>()
16589 .map(Some)
16590 .map_err(|_| "invalid bigint element")
16591 }
16592 })
16593 .collect()
16594 })
16595 .collect()
16596}
16597
16598fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16599 let raw = split_2d_literal(s)?;
16600 Ok(raw
16601 .into_iter()
16602 .map(|row| {
16603 row.into_iter()
16604 .map(|cell| {
16605 if cell.eq_ignore_ascii_case("NULL") {
16606 None
16607 } else {
16608 Some(cell.trim_matches('"').to_string())
16609 }
16610 })
16611 .collect()
16612 })
16613 .collect())
16614}
16615
16616fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16617 let mut out = alloc::string::String::from("{");
16618 for (i, row) in rows.iter().enumerate() {
16619 if i > 0 {
16620 out.push(',');
16621 }
16622 out.push('{');
16623 for (j, cell) in row.iter().enumerate() {
16624 if j > 0 {
16625 out.push(',');
16626 }
16627 match cell {
16628 None => out.push_str("NULL"),
16629 Some(n) => out.push_str(&alloc::format!("{n}")),
16630 }
16631 }
16632 out.push('}');
16633 }
16634 out.push('}');
16635 out
16636}
16637
16638fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16639 let mut out = alloc::string::String::from("{");
16640 for (i, row) in rows.iter().enumerate() {
16641 if i > 0 {
16642 out.push(',');
16643 }
16644 out.push('{');
16645 for (j, cell) in row.iter().enumerate() {
16646 if j > 0 {
16647 out.push(',');
16648 }
16649 match cell {
16650 None => out.push_str("NULL"),
16651 Some(n) => out.push_str(&alloc::format!("{n}")),
16652 }
16653 }
16654 out.push('}');
16655 }
16656 out.push('}');
16657 out
16658}
16659
16660fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16661 let mut out = alloc::string::String::from("{");
16662 for (i, row) in rows.iter().enumerate() {
16663 if i > 0 {
16664 out.push(',');
16665 }
16666 out.push('{');
16667 for (j, cell) in row.iter().enumerate() {
16668 if j > 0 {
16669 out.push(',');
16670 }
16671 match cell {
16672 None => out.push_str("NULL"),
16673 Some(s) => out.push_str(s),
16674 }
16675 }
16676 out.push('}');
16677 }
16678 out.push('}');
16679 out
16680}
16681
16682pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16685 format_int_2d_text(rows)
16686}
16687pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16688 format_bigint_2d_text(rows)
16689}
16690pub fn format_text_2d_text_pub(
16691 rows: &[Vec<Option<alloc::string::String>>],
16692) -> alloc::string::String {
16693 format_text_2d_text(rows)
16694}
16695
16696fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16701 let s = s.trim();
16702 if s.eq_ignore_ascii_case("empty") {
16703 return Some(Value::Range {
16704 kind,
16705 lower: None,
16706 upper: None,
16707 lower_inc: false,
16708 upper_inc: false,
16709 empty: true,
16710 });
16711 }
16712 let bytes = s.as_bytes();
16713 if bytes.len() < 3 {
16714 return None;
16715 }
16716 let lower_inc = match bytes[0] {
16717 b'[' => true,
16718 b'(' => false,
16719 _ => return None,
16720 };
16721 let upper_inc = match bytes[bytes.len() - 1] {
16722 b']' => true,
16723 b')' => false,
16724 _ => return None,
16725 };
16726 let inner = &s[1..s.len() - 1];
16727 let (lo_text, up_text) = inner.split_once(',')?;
16728 let lower = if lo_text.is_empty() {
16729 None
16730 } else {
16731 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16732 };
16733 let upper = if up_text.is_empty() {
16734 None
16735 } else {
16736 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16737 };
16738 Some(Value::Range {
16739 kind,
16740 lower,
16741 upper,
16742 lower_inc,
16743 upper_inc,
16744 empty: false,
16745 })
16746}
16747
16748fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16751 let text = text.trim().trim_matches('"');
16752 use spg_storage::RangeKind as K;
16753 match kind {
16754 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16755 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16756 K::Num => {
16757 let dot = text.find('.');
16760 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16761 let digits: alloc::string::String = text
16762 .chars()
16763 .filter(|c| *c == '-' || c.is_ascii_digit())
16764 .collect();
16765 let scaled: i128 = digits.parse().ok()?;
16766 Some(Value::Numeric { scaled, scale })
16767 }
16768 K::Ts | K::TsTz => {
16769 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16774 }
16775 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16776 }
16777}
16778
16779pub fn format_range_text(v: &Value) -> alloc::string::String {
16783 format_range_str(v)
16784}
16785
16786fn format_range_str(v: &Value) -> alloc::string::String {
16787 let Value::Range {
16788 lower,
16789 upper,
16790 lower_inc,
16791 upper_inc,
16792 empty,
16793 ..
16794 } = v
16795 else {
16796 return alloc::string::String::new();
16797 };
16798 if *empty {
16799 return "empty".into();
16800 }
16801 let mut out = alloc::string::String::new();
16802 out.push(if *lower_inc { '[' } else { '(' });
16803 if let Some(l) = lower {
16804 out.push_str(&format_range_element(l));
16805 }
16806 out.push(',');
16807 if let Some(u) = upper {
16808 out.push_str(&format_range_element(u));
16809 }
16810 out.push(if *upper_inc { ']' } else { ')' });
16811 out
16812}
16813
16814fn format_range_element(v: &Value) -> alloc::string::String {
16815 match v {
16816 Value::Int(n) => alloc::format!("{n}"),
16817 Value::BigInt(n) => alloc::format!("{n}"),
16818 Value::Date(d) => crate::eval::format_date(*d),
16819 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16820 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16821 other => alloc::format!("{other:?}"),
16822 }
16823}
16824
16825fn parse_money_str(s: &str) -> Option<i64> {
16836 let s = s.trim();
16837 let (neg, rest) = match s.strip_prefix('-') {
16838 Some(r) => (true, r.trim_start()),
16839 None => (false, s),
16840 };
16841 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16842 let (int_part, frac_part) = match rest.split_once('.') {
16843 Some((i, f)) => (i, Some(f)),
16844 None => (rest, None),
16845 };
16846 if int_part.is_empty() {
16847 return None;
16848 }
16849 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16851 for b in int_part.bytes() {
16852 match b {
16853 b',' => {}
16854 b'0'..=b'9' => int_digits.push(b as char),
16855 _ => return None,
16856 }
16857 }
16858 if int_digits.is_empty() {
16859 return None;
16860 }
16861 let dollars: i64 = int_digits.parse().ok()?;
16862 let cents: i64 = match frac_part {
16863 None => 0,
16864 Some(f) => {
16865 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16866 return None;
16867 }
16868 let padded = if f.len() == 1 {
16869 alloc::format!("{f}0")
16870 } else {
16871 f.to_string()
16872 };
16873 padded.parse().ok()?
16874 }
16875 };
16876 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16877 Some(if neg { -total } else { total })
16878}
16879
16880fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16891 let s = s.trim();
16892 let bytes = s.as_bytes();
16896 let sign_pos = bytes
16897 .iter()
16898 .enumerate()
16899 .rev()
16900 .find(|&(_, &b)| b == b'+' || b == b'-')
16901 .map(|(i, _)| i)?;
16902 if sign_pos == 0 {
16903 return None; }
16905 let time_part = &s[..sign_pos];
16906 let offset_part = &s[sign_pos..];
16907 let us = parse_time_str(time_part)?;
16908 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16909 let offset_body = &offset_part[1..];
16910 let (hh_str, mm_str) = match offset_body.split_once(':') {
16911 Some((h, m)) => (h, m),
16912 None => (offset_body, "0"),
16913 };
16914 let hh: i32 = hh_str.parse().ok()?;
16915 let mm: i32 = mm_str.parse().ok()?;
16916 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16917 return None;
16918 }
16919 let total = sign * (hh * 3600 + mm * 60);
16920 if total.abs() > 50_400 {
16921 return None;
16922 }
16923 Some((us, total))
16924}
16925
16926fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16931 if n == 0 || (1901..=2155).contains(&n) {
16932 return Ok(Value::Year(n as u16));
16935 }
16936 Err(EngineError::Eval(EvalError::TypeMismatch {
16937 detail: alloc::format!(
16938 "year value out of range: {n} (column `{col_name}`; \
16939 MySQL accepts 0 or 1901..=2155)"
16940 ),
16941 }))
16942}
16943
16944fn parse_time_str(s: &str) -> Option<i64> {
16956 let s = s.trim();
16957 let (hms, frac) = match s.split_once('.') {
16958 Some((h, f)) => (h, Some(f)),
16959 None => (s, None),
16960 };
16961 let mut parts = hms.split(':');
16962 let hh: u32 = parts.next()?.parse().ok()?;
16963 let mm: u32 = parts.next()?.parse().ok()?;
16964 let ss: u32 = parts.next()?.parse().ok()?;
16965 if parts.next().is_some() {
16966 return None;
16967 }
16968 if hh > 23 || mm > 59 || ss > 59 {
16969 return None;
16970 }
16971 let frac_us: i64 = match frac {
16972 None => 0,
16973 Some(f) => {
16974 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16975 return None;
16976 }
16977 let mut padded = alloc::string::String::with_capacity(6);
16979 padded.push_str(f);
16980 while padded.len() < 6 {
16981 padded.push('0');
16982 }
16983 padded.parse().ok()?
16984 }
16985 };
16986 Some(
16987 i64::from(hh) * 3_600_000_000
16988 + i64::from(mm) * 60_000_000
16989 + i64::from(ss) * 1_000_000
16990 + frac_us,
16991 )
16992}
16993
16994const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16995 match t {
16996 ColumnTypeName::SmallInt => DataType::SmallInt,
16997 ColumnTypeName::Int => DataType::Int,
16998 ColumnTypeName::BigInt => DataType::BigInt,
16999 ColumnTypeName::Float => DataType::Float,
17000 ColumnTypeName::Text => DataType::Text,
17001 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
17002 ColumnTypeName::Char(n) => DataType::Char(n),
17003 ColumnTypeName::Bool => DataType::Bool,
17004 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
17005 dim,
17006 encoding: match encoding {
17007 SqlVecEncoding::F32 => VecEncoding::F32,
17008 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
17009 SqlVecEncoding::F16 => VecEncoding::F16,
17010 },
17011 },
17012 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
17013 ColumnTypeName::Date => DataType::Date,
17014 ColumnTypeName::Timestamp => DataType::Timestamp,
17015 ColumnTypeName::Timestamptz => DataType::Timestamptz,
17016 ColumnTypeName::Json => DataType::Json,
17017 ColumnTypeName::Jsonb => DataType::Jsonb,
17018 ColumnTypeName::Bytes => DataType::Bytes,
17019 ColumnTypeName::TextArray => DataType::TextArray,
17020 ColumnTypeName::IntArray => DataType::IntArray,
17021 ColumnTypeName::BigIntArray => DataType::BigIntArray,
17022 ColumnTypeName::TsVector => DataType::TsVector,
17023 ColumnTypeName::TsQuery => DataType::TsQuery,
17024 ColumnTypeName::Uuid => DataType::Uuid,
17025 ColumnTypeName::Time => DataType::Time,
17026 ColumnTypeName::Year => DataType::Year,
17027 ColumnTypeName::TimeTz => DataType::TimeTz,
17028 ColumnTypeName::Money => DataType::Money,
17029 ColumnTypeName::Range(k) => DataType::Range(match k {
17030 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
17031 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
17032 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
17033 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
17034 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
17035 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
17036 }),
17037 ColumnTypeName::Hstore => DataType::Hstore,
17038 ColumnTypeName::IntArray2D => DataType::IntArray2D,
17039 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
17040 ColumnTypeName::TextArray2D => DataType::TextArray2D,
17041 }
17042}
17043
17044fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
17048 match expr {
17049 Expr::Literal(l) => Ok(literal_to_value(l)),
17050 Expr::Cast { expr, target } => {
17051 let inner_value = literal_expr_to_value(*expr)?;
17052 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
17053 }
17054 Expr::Unary {
17055 op: UnOp::Neg,
17056 expr,
17057 } => match *expr {
17058 Expr::Literal(Literal::Integer(n)) => {
17059 let neg = n.checked_neg().ok_or_else(|| {
17062 EngineError::Unsupported("integer literal overflow on negation".into())
17063 })?;
17064 Ok(int_value_for(neg))
17065 }
17066 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
17067 other => Err(EngineError::Unsupported(alloc::format!(
17068 "unary minus over non-literal expression: {other:?}"
17069 ))),
17070 },
17071 Expr::Array(items) => {
17079 let mut materialised: alloc::vec::Vec<Value> =
17080 alloc::vec::Vec::with_capacity(items.len());
17081 for elem in items {
17082 materialised.push(literal_expr_to_value(elem)?);
17083 }
17084 Ok(array_literal_widen(materialised))
17085 }
17086 other => {
17099 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17100 let ctx = EvalContext::new(&empty_schema, None);
17101 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17102 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17103 }
17104 }
17105}
17106
17107fn literal_to_value(l: Literal) -> Value {
17108 match l {
17109 Literal::Integer(n) => int_value_for(n),
17110 Literal::Float(x) => Value::Float(x),
17111 Literal::String(s) => Value::Text(s),
17112 Literal::Bool(b) => Value::Bool(b),
17113 Literal::Null => Value::Null,
17114 Literal::Vector(v) => Value::Vector(v),
17115 Literal::TextArray(items) => Value::TextArray(items),
17116 Literal::IntArray(items) => Value::IntArray(items),
17117 Literal::BigIntArray(items) => Value::BigIntArray(items),
17118 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17119 }
17120}
17121
17122fn int_value_for(n: i64) -> Value {
17126 if let Ok(small) = i32::try_from(n) {
17127 Value::Int(small)
17128 } else {
17129 Value::BigInt(n)
17130 }
17131}
17132
17133#[allow(clippy::too_many_lines)]
17139fn check_unsigned_range(
17144 v: &Value,
17145 schema: &ColumnSchema,
17146 position: usize,
17147) -> Result<(), EngineError> {
17148 if !schema.is_unsigned {
17149 return Ok(());
17150 }
17151 let n = match v {
17152 Value::SmallInt(x) => i64::from(*x),
17153 Value::Int(x) => i64::from(*x),
17154 Value::BigInt(x) => *x,
17155 _ => return Ok(()), };
17157 if n < 0 {
17158 return Err(EngineError::Unsupported(alloc::format!(
17159 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17160 schema.name
17161 )));
17162 }
17163 Ok(())
17164}
17165
17166fn coerce_value(
17167 v: Value,
17168 expected: DataType,
17169 col_name: &str,
17170 position: usize,
17171) -> Result<Value, EngineError> {
17172 if v.is_null() {
17173 return Ok(Value::Null);
17174 }
17175 let actual = v.data_type().expect("non-null");
17176 if actual == expected {
17177 return Ok(v);
17178 }
17179 let coerced = match (v, expected) {
17180 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17181 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17182 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17183 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17184 i128::from(n),
17185 precision,
17186 scale,
17187 col_name,
17188 )?),
17189 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17190 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17191 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17192 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17193 i128::from(n),
17194 precision,
17195 scale,
17196 col_name,
17197 )?),
17198 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17199 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17200 #[allow(clippy::cast_precision_loss)]
17201 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17202 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17203 i128::from(n),
17204 precision,
17205 scale,
17206 col_name,
17207 )?),
17208 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17209 Some(numeric_from_float(x, precision, scale, col_name)?)
17210 }
17211 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17222 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17223 return Err(EngineError::Eval(EvalError::TypeMismatch {
17224 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17225 }));
17226 };
17227 Some(numeric_rescale(
17228 mantissa, src_scale, precision, scale, col_name,
17229 )?)
17230 }
17231 (Value::Text(s), DataType::Date) => {
17233 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17234 EngineError::Eval(EvalError::TypeMismatch {
17235 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17236 })
17237 })?;
17238 Some(Value::Date(d))
17239 }
17240 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17247 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17248 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17249 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17250 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17251 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17252 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17253 _ => None,
17254 },
17255 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17264 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17265 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17266 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17270 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17271 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17279 (Value::Text(s), DataType::Bytes) => {
17286 let bytes = decode_bytea_literal(&s).map_err(|e| {
17287 EngineError::Eval(EvalError::TypeMismatch {
17288 detail: alloc::format!(
17289 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17290 ),
17291 })
17292 })?;
17293 Some(Value::Bytes(bytes))
17294 }
17295 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17299 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17307 Some(b) => Some(Value::Uuid(b)),
17308 None => {
17309 return Err(EngineError::Eval(EvalError::TypeMismatch {
17310 detail: alloc::format!(
17311 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17312 ),
17313 }));
17314 }
17315 },
17316 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17321 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17327 Some(us) => Some(Value::Time(us)),
17328 None => {
17329 return Err(EngineError::Eval(EvalError::TypeMismatch {
17330 detail: alloc::format!(
17331 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17332 ),
17333 }));
17334 }
17335 },
17336 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17338 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17343 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17344 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17345 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17349 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17350 Err(_) => {
17351 return Err(EngineError::Eval(EvalError::TypeMismatch {
17352 detail: alloc::format!(
17353 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17354 ),
17355 }));
17356 }
17357 },
17358 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17360 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17364 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17365 None => {
17366 return Err(EngineError::Eval(EvalError::TypeMismatch {
17367 detail: alloc::format!(
17368 "invalid input syntax for type time with time zone: \
17369 {s:?} (column `{col_name}`)"
17370 ),
17371 }));
17372 }
17373 },
17374 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17376 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17377 }
17378 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17382 Some(c) => Some(Value::Money(c)),
17383 None => {
17384 return Err(EngineError::Eval(EvalError::TypeMismatch {
17385 detail: alloc::format!(
17386 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17387 ),
17388 }));
17389 }
17390 },
17391 (Value::SmallInt(n), DataType::Money) => {
17395 Some(Value::Money(i64::from(n).saturating_mul(100)))
17396 }
17397 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17398 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17399 (Value::Float(x), DataType::Money) => {
17400 let scaled = x * 100.0;
17403 let cents = if scaled >= 0.0 {
17404 (scaled + 0.5) as i64
17405 } else {
17406 (scaled - 0.5) as i64
17407 };
17408 Some(Value::Money(cents))
17409 }
17410 (Value::Numeric { scaled, scale }, DataType::Money) => {
17411 let cents = if scale == 2 {
17414 scaled
17415 } else if scale < 2 {
17416 let mult = 10_i128.pow(u32::from(2 - scale));
17417 scaled.saturating_mul(mult)
17418 } else {
17419 let div = 10_i128.pow(u32::from(scale - 2));
17420 let half = div / 2;
17421 let bias = if scaled >= 0 { half } else { -half };
17422 (scaled + bias) / div
17423 };
17424 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17425 }
17426 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17428 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17432 Some(v) => Some(v),
17433 None => {
17434 return Err(EngineError::Eval(EvalError::TypeMismatch {
17435 detail: alloc::format!(
17436 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17437 ),
17438 }));
17439 }
17440 },
17441 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17443 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17445 Some(pairs) => Some(Value::Hstore(pairs)),
17446 None => {
17447 return Err(EngineError::Eval(EvalError::TypeMismatch {
17448 detail: alloc::format!(
17449 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17450 ),
17451 }));
17452 }
17453 },
17454 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17456 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17459 Ok(m) => Some(Value::IntArray2D(m)),
17460 Err(e) => {
17461 return Err(EngineError::Eval(EvalError::TypeMismatch {
17462 detail: alloc::format!(
17463 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17464 ),
17465 }));
17466 }
17467 },
17468 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17469 Ok(m) => Some(Value::BigIntArray2D(m)),
17470 Err(e) => {
17471 return Err(EngineError::Eval(EvalError::TypeMismatch {
17472 detail: alloc::format!(
17473 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17474 ),
17475 }));
17476 }
17477 },
17478 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17479 Ok(m) => Some(Value::TextArray2D(m)),
17480 Err(e) => {
17481 return Err(EngineError::Eval(EvalError::TypeMismatch {
17482 detail: alloc::format!(
17483 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17484 ),
17485 }));
17486 }
17487 },
17488 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17490 (Value::BigIntArray2D(rows), DataType::Text) => {
17491 Some(Value::Text(format_bigint_2d_text(&rows)))
17492 }
17493 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17494 (Value::Text(s), DataType::TextArray) => {
17499 let arr = decode_text_array_literal(&s).map_err(|e| {
17500 EngineError::Eval(EvalError::TypeMismatch {
17501 detail: alloc::format!(
17502 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17503 ),
17504 })
17505 })?;
17506 Some(Value::TextArray(arr))
17507 }
17508 (Value::Text(s), DataType::IntArray) => {
17514 let arr = decode_text_array_literal(&s).map_err(|e| {
17515 EngineError::Eval(EvalError::TypeMismatch {
17516 detail: alloc::format!(
17517 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17518 ),
17519 })
17520 })?;
17521 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17522 for elem in arr {
17523 match elem {
17524 None => out.push(None),
17525 Some(t) => {
17526 let n: i32 = t.parse().map_err(|_| {
17527 EngineError::Eval(EvalError::TypeMismatch {
17528 detail: alloc::format!(
17529 "cannot parse {t:?} as INT element for `{col_name}`"
17530 ),
17531 })
17532 })?;
17533 out.push(Some(n));
17534 }
17535 }
17536 }
17537 Some(Value::IntArray(out))
17538 }
17539 (Value::Text(s), DataType::BigIntArray) => {
17540 let arr = decode_text_array_literal(&s).map_err(|e| {
17541 EngineError::Eval(EvalError::TypeMismatch {
17542 detail: alloc::format!(
17543 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17544 ),
17545 })
17546 })?;
17547 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17548 for elem in arr {
17549 match elem {
17550 None => out.push(None),
17551 Some(t) => {
17552 let n: i64 = t.parse().map_err(|_| {
17553 EngineError::Eval(EvalError::TypeMismatch {
17554 detail: alloc::format!(
17555 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17556 ),
17557 })
17558 })?;
17559 out.push(Some(n));
17560 }
17561 }
17562 }
17563 Some(Value::BigIntArray(out))
17564 }
17565 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17569 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17578 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17579 EngineError::Eval(EvalError::TypeMismatch {
17580 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17581 })
17582 })?;
17583 if parsed.len() != dim as usize {
17584 return Err(EngineError::Eval(EvalError::TypeMismatch {
17585 detail: alloc::format!(
17586 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17587 parsed.len()
17588 ),
17589 }));
17590 }
17591 Some(match encoding {
17592 VecEncoding::F32 => Value::Vector(parsed),
17593 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17594 VecEncoding::F16 => {
17595 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17596 }
17597 })
17598 }
17599 (Value::Text(s), DataType::TsVector) => {
17609 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17610 EngineError::Eval(EvalError::TypeMismatch {
17611 detail: alloc::format!(
17612 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17613 ),
17614 })
17615 })?;
17616 Some(Value::TsVector(lexs))
17617 }
17618 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17619 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17620 EngineError::Eval(EvalError::TypeMismatch {
17621 detail: alloc::format!(
17622 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17623 ),
17624 })
17625 })?;
17626 Some(Value::Timestamp(t))
17627 }
17628 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17631 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17632 }
17633 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17637 (Value::Timestamp(t), DataType::Date) => {
17638 let days = t.div_euclid(86_400_000_000);
17639 i32::try_from(days).ok().map(Value::Date)
17640 }
17641 (
17642 Value::Numeric {
17643 scaled,
17644 scale: src_scale,
17645 },
17646 DataType::Numeric { precision, scale },
17647 ) => Some(numeric_rescale(
17648 scaled, src_scale, precision, scale, col_name,
17649 )?),
17650 #[allow(clippy::cast_precision_loss)]
17651 (Value::Numeric { scaled, scale }, DataType::Float) => {
17652 let mut div = 1.0_f64;
17653 for _ in 0..scale {
17654 div *= 10.0;
17655 }
17656 Some(Value::Float((scaled as f64) / div))
17657 }
17658 (Value::Numeric { scaled, scale }, DataType::Int) => {
17659 let truncated = numeric_truncate_to_integer(scaled, scale);
17660 i32::try_from(truncated).ok().map(Value::Int)
17661 }
17662 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17663 let truncated = numeric_truncate_to_integer(scaled, scale);
17664 i64::try_from(truncated).ok().map(Value::BigInt)
17665 }
17666 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17667 let truncated = numeric_truncate_to_integer(scaled, scale);
17668 i16::try_from(truncated).ok().map(Value::SmallInt)
17669 }
17670 (Value::Text(s), DataType::Varchar(max)) => {
17672 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17673 Some(Value::Text(s))
17674 } else {
17675 return Err(EngineError::Unsupported(alloc::format!(
17676 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17677 {} chars",
17678 s.chars().count()
17679 )));
17680 }
17681 }
17682 (
17690 Value::Vector(v),
17691 DataType::Vector {
17692 dim,
17693 encoding: VecEncoding::Sq8,
17694 },
17695 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17696 (
17701 Value::Vector(v),
17702 DataType::Vector {
17703 dim,
17704 encoding: VecEncoding::F16,
17705 },
17706 ) if v.len() == dim as usize => Some(Value::HalfVector(
17707 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17708 )),
17709 (Value::Text(s), DataType::Char(size)) => {
17713 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17714 if len > size {
17715 return Err(EngineError::Unsupported(alloc::format!(
17716 "value for CHAR({size}) column `{col_name}` exceeds length: \
17717 {len} chars"
17718 )));
17719 }
17720 let need = (size - len) as usize;
17721 let mut padded = s;
17722 padded.reserve(need);
17723 for _ in 0..need {
17724 padded.push(' ');
17725 }
17726 Some(Value::Text(padded))
17727 }
17728 _ => None,
17729 };
17730 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17731 column: col_name.into(),
17732 expected,
17733 actual,
17734 position,
17735 }))
17736}
17737
17738fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17744 use core::fmt::Write;
17745 let mut out = alloc::string::String::from("(");
17746 for (i, a) in args.iter().enumerate() {
17747 if i > 0 {
17748 out.push_str(", ");
17749 }
17750 match a.mode {
17751 spg_sql::ast::FunctionArgMode::In => {}
17752 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17753 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17754 }
17755 if let Some(n) = &a.name {
17756 out.push_str(n);
17757 out.push(' ');
17758 }
17759 match &a.ty {
17760 spg_sql::ast::FunctionArgType::Typed(t) => {
17761 let _ = write!(out, "{t}");
17762 }
17763 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17764 }
17765 }
17766 out.push(')');
17767 out
17768}
17769
17770fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
17779 match expr {
17780 spg_sql::ast::Expr::FunctionCall { name, args } => {
17781 name.eq_ignore_ascii_case("unnest") && args.len() == 1
17782 }
17783 _ => false,
17784 }
17785}
17786
17787fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
17791 match expr {
17792 spg_sql::ast::Expr::FunctionCall { name, args }
17793 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
17794 {
17795 Some(&args[0])
17796 }
17797 _ => None,
17798 }
17799}
17800
17801fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
17806 match v {
17807 Value::Null => Ok(Vec::new()),
17808 Value::TextArray(items) => Ok(items
17809 .iter()
17810 .map(|opt| {
17811 opt.as_ref()
17812 .map(|s| Value::Text(s.clone()))
17813 .unwrap_or(Value::Null)
17814 })
17815 .collect()),
17816 Value::IntArray(items) => Ok(items
17817 .iter()
17818 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
17819 .collect()),
17820 Value::BigIntArray(items) => Ok(items
17821 .iter()
17822 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
17823 .collect()),
17824 other => Err(EngineError::Eval(EvalError::TypeMismatch {
17825 detail: alloc::format!(
17826 "unnest() expects an array argument, got {:?}",
17827 other.data_type()
17828 ),
17829 })),
17830 }
17831}
17832
17833#[cfg(test)]
17834mod tests {
17835 use super::*;
17836 use alloc::vec;
17837
17838 fn unwrap_command_ok(r: &QueryResult) -> usize {
17839 match r {
17840 QueryResult::CommandOk { affected, .. } => *affected,
17841 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17842 }
17843 }
17844
17845 #[test]
17846 fn update_seek_positions_engages_on_indexed_eq() {
17847 let mut e = Engine::new();
17848 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
17849 .unwrap();
17850 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
17851 for i in 0..100 {
17852 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
17853 .unwrap();
17854 }
17855 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
17856 .expect("parse");
17857 let Statement::Update(u) = stmt else {
17858 panic!("expected Update, got {stmt:?}");
17859 };
17860 let w = u.where_.as_ref().expect("where");
17861 let table = e.catalog().get("b").unwrap();
17862 let schema_cols = table.schema().columns.clone();
17863 let Expr::Binary { lhs, op, rhs } = w else {
17865 panic!("WHERE not Binary: {w:?}");
17866 };
17867 assert_eq!(*op, BinOp::Eq, "op not Eq");
17868 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
17869 assert!(
17870 pair.is_some(),
17871 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
17872 );
17873 let (col_pos, value) = pair.unwrap();
17874 assert!(
17875 table.index_on(col_pos).is_some(),
17876 "no index on col {col_pos}"
17877 );
17878 assert!(
17879 IndexKey::from_value(&value).is_some(),
17880 "IndexKey::from_value None for {value:?}"
17881 );
17882 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
17883 assert_eq!(positions, Some(vec![42]), "seek did not engage");
17884 }
17885
17886 #[test]
17887 fn create_table_registers_schema() {
17888 let mut e = Engine::new();
17889 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17890 .unwrap();
17891 assert_eq!(e.catalog().table_count(), 1);
17892 let t = e.catalog().get("foo").unwrap();
17893 assert_eq!(t.schema().columns.len(), 2);
17894 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17895 assert!(!t.schema().columns[0].nullable);
17896 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17897 }
17898
17899 #[test]
17900 fn create_table_vector_default_is_f32_encoded() {
17901 let mut e = Engine::new();
17902 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17903 let t = e.catalog().get("t").unwrap();
17904 assert_eq!(
17905 t.schema().columns[0].ty,
17906 DataType::Vector {
17907 dim: 8,
17908 encoding: VecEncoding::F32,
17909 },
17910 );
17911 }
17912
17913 #[test]
17914 fn create_table_vector_using_sq8_succeeds() {
17915 let mut e = Engine::new();
17919 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17920 let t = e.catalog().get("t").unwrap();
17921 assert_eq!(
17922 t.schema().columns[0].ty,
17923 DataType::Vector {
17924 dim: 8,
17925 encoding: VecEncoding::Sq8,
17926 },
17927 );
17928 }
17929
17930 #[test]
17931 fn insert_into_sq8_column_quantises_f32_payload() {
17932 let mut e = Engine::new();
17939 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17940 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17941 .unwrap();
17942 let t = e.catalog().get("t").unwrap();
17943 assert_eq!(t.rows().len(), 1);
17944 match &t.rows()[0].values[0] {
17945 Value::Sq8Vector(q) => {
17946 assert_eq!(q.bytes.len(), 4);
17947 assert!((q.min - 0.0).abs() < 1e-6);
17949 assert!((q.max - 1.0).abs() < 1e-6);
17950 }
17951 other => panic!("expected Sq8Vector cell, got {other:?}"),
17952 }
17953 }
17954
17955 #[test]
17956 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17957 let mut e = Engine::new();
17964 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17965 .unwrap();
17966 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17967 .unwrap();
17968 let t = e.catalog().get("t").unwrap();
17969 assert_eq!(t.rows().len(), 1);
17970 match &t.rows()[0].values[0] {
17971 Value::HalfVector(h) => {
17972 assert_eq!(h.dim(), 4);
17973 let back = h.to_f32_vec();
17974 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17975 for (g, e) in back.iter().zip(expected.iter()) {
17976 assert!(
17977 (g - e).abs() < 1e-6,
17978 "{g} vs {e} should be exact on f16 grid"
17979 );
17980 }
17981 }
17982 other => panic!("expected HalfVector cell, got {other:?}"),
17983 }
17984 }
17985
17986 #[test]
17987 fn alter_index_rebuild_in_place_succeeds() {
17988 let mut e = Engine::new();
17993 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17994 .unwrap();
17995 for i in 0..8_i32 {
17996 #[allow(clippy::cast_precision_loss)]
17997 let base = (i as f32) * 0.1;
17998 e.execute(&alloc::format!(
17999 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
18000 b1 = base + 0.01,
18001 b2 = base + 0.02,
18002 ))
18003 .unwrap();
18004 }
18005 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18006 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
18007 assert_eq!(
18009 e.catalog().get("t").unwrap().schema().columns[1].ty,
18010 DataType::Vector {
18011 dim: 3,
18012 encoding: VecEncoding::F32,
18013 },
18014 );
18015 }
18016
18017 #[test]
18018 fn alter_index_rebuild_with_encoding_switches_cell_type() {
18019 let mut e = Engine::new();
18024 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
18025 .unwrap();
18026 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
18027 .unwrap();
18028 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
18029 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
18030 .unwrap();
18031 let t = e.catalog().get("t").unwrap();
18032 assert_eq!(
18033 t.schema().columns[1].ty,
18034 DataType::Vector {
18035 dim: 4,
18036 encoding: VecEncoding::Sq8,
18037 },
18038 );
18039 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
18040 }
18041
18042 #[test]
18043 fn alter_index_rebuild_unknown_index_errors() {
18044 let mut e = Engine::new();
18045 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
18046 assert!(
18047 matches!(
18048 &err,
18049 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
18050 ),
18051 "got: {err}"
18052 );
18053 }
18054
18055 #[test]
18056 fn alter_index_rebuild_on_btree_index_errors() {
18057 let mut e = Engine::new();
18060 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18061 e.execute("INSERT INTO t VALUES (1)").unwrap();
18062 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
18063 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
18064 assert!(
18065 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
18066 "got: {err}"
18067 );
18068 }
18069
18070 #[test]
18071 fn prepared_insert_substitutes_placeholders() {
18072 let mut e = Engine::new();
18078 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18079 .unwrap();
18080 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
18081 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
18082 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
18083 .unwrap();
18084 }
18085 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
18087 let QueryResult::Rows { rows, .. } = rows_result else {
18088 panic!("expected Rows")
18089 };
18090 assert_eq!(rows.len(), 3);
18091 }
18092
18093 #[test]
18094 fn prepared_select_with_placeholder_filters_rows() {
18095 let mut e = Engine::new();
18096 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18097 .unwrap();
18098 for i in 0..10_i32 {
18099 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18100 .unwrap();
18101 }
18102 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18103 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18104 else {
18105 panic!("expected Rows")
18106 };
18107 assert_eq!(rows.len(), 1);
18109 assert_eq!(rows[0].values[0], Value::Int(5));
18110 }
18111
18112 #[test]
18113 fn prepared_too_few_params_errors() {
18114 let mut e = Engine::new();
18115 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18116 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18117 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18118 assert!(
18119 matches!(
18120 &err,
18121 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18122 ),
18123 "got: {err}"
18124 );
18125 }
18126
18127 #[test]
18128 fn bytea_cast_round_trips_text_input() {
18129 let e = Engine::new();
18132 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18133 let QueryResult::Rows { rows, .. } = r else {
18134 panic!("expected Rows")
18135 };
18136 assert_eq!(rows.len(), 1);
18137 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18138 }
18139
18140 #[test]
18141 fn bytea_cast_pg_escape_hex_form() {
18142 let e = Engine::new();
18146 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18147 let QueryResult::Rows { rows, .. } = r else {
18148 panic!("expected Rows")
18149 };
18150 assert_eq!(
18151 rows[0].values[0],
18152 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18153 );
18154 }
18155
18156 #[test]
18157 fn bytea_cast_chains_through_octet_length() {
18158 let e = Engine::new();
18162 let r = e
18163 .execute_readonly("SELECT octet_length('hello'::bytea)")
18164 .unwrap();
18165 let QueryResult::Rows { rows, .. } = r else {
18166 panic!("expected Rows")
18167 };
18168 match &rows[0].values[0] {
18169 Value::Int(n) => assert_eq!(*n, 5),
18170 Value::BigInt(n) => assert_eq!(*n, 5),
18171 other => panic!("expected integer length, got {other:?}"),
18172 }
18173 }
18174
18175 #[test]
18176 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18177 let mut e = Engine::new();
18183 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18184 .unwrap();
18185 for i in 0..10_i32 {
18186 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18187 .unwrap();
18188 }
18189 let snapshot = e.clone_snapshot();
18190 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18191 let QueryResult::Rows { rows, .. } =
18192 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18193 .unwrap()
18194 else {
18195 panic!("expected Rows")
18196 };
18197 assert_eq!(rows.len(), 1);
18198 assert_eq!(rows[0].values[0], Value::Int(5));
18199 }
18200
18201 #[test]
18202 fn readonly_prepared_on_snapshot_rejects_writes() {
18203 let mut e = Engine::new();
18207 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18208 let snapshot = e.clone_snapshot();
18209 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18210 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18211 .unwrap_err();
18212 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18213 }
18214
18215 #[test]
18216 fn readonly_prepared_on_snapshot_frozen_view() {
18217 let mut e = Engine::new();
18223 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18224 e.execute("INSERT INTO t VALUES (1)").unwrap();
18225 let snapshot = e.clone_snapshot();
18226 e.execute("INSERT INTO t VALUES (2)").unwrap();
18227 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18228 let QueryResult::Rows { rows, .. } =
18229 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18230 .unwrap()
18231 else {
18232 panic!("expected Rows")
18233 };
18234 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18235 }
18236
18237 #[test]
18238 fn describe_prepared_on_snapshot_resolves_columns() {
18239 let mut e = Engine::new();
18244 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18245 .unwrap();
18246 let snapshot = e.clone_snapshot();
18247 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18248 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18249 assert_eq!(cols.len(), 2);
18250 assert_eq!(cols[0].name, "id");
18251 assert_eq!(cols[0].ty, DataType::Int);
18252 assert_eq!(cols[1].name, "name");
18253 assert_eq!(cols[1].ty, DataType::Text);
18254 }
18255
18256 #[test]
18257 fn insert_into_half_column_dim_mismatch_errors() {
18258 let mut e = Engine::new();
18259 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18260 .unwrap();
18261 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18262 assert!(matches!(
18263 &err,
18264 EngineError::Storage(StorageError::TypeMismatch { .. })
18265 ));
18266 }
18267
18268 #[test]
18269 fn insert_into_sq8_column_dim_mismatch_errors() {
18270 let mut e = Engine::new();
18275 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18276 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18277 assert!(
18278 matches!(
18279 &err,
18280 EngineError::Storage(StorageError::TypeMismatch { .. })
18281 ),
18282 "got: {err}",
18283 );
18284 }
18285
18286 #[test]
18287 fn create_table_duplicate_errors() {
18288 let mut e = Engine::new();
18289 e.execute("CREATE TABLE foo (a INT)").unwrap();
18290 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18291 assert!(matches!(
18292 err,
18293 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18294 ));
18295 }
18296
18297 #[test]
18298 fn insert_into_unknown_table_errors() {
18299 let mut e = Engine::new();
18300 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18301 assert!(matches!(
18302 err,
18303 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18304 ));
18305 }
18306
18307 #[test]
18308 fn insert_happy_path_reports_one_affected() {
18309 let mut e = Engine::new();
18310 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18311 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18312 assert_eq!(unwrap_command_ok(&r), 1);
18313 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18314 }
18315
18316 #[test]
18317 fn insert_arity_mismatch_propagates() {
18318 let mut e = Engine::new();
18319 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18320 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18321 assert!(matches!(
18322 err,
18323 EngineError::Storage(StorageError::ArityMismatch { .. })
18324 ));
18325 }
18326
18327 #[test]
18328 fn insert_negative_integer_via_unary_minus() {
18329 let mut e = Engine::new();
18330 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18331 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18332 let rows = e.catalog().get("foo").unwrap().rows();
18333 assert_eq!(rows[0].values[0], Value::Int(-7));
18334 }
18335
18336 #[test]
18337 fn insert_expression_evaluated_against_empty_context() {
18338 let mut e = Engine::new();
18343 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18344 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18345 let rows = e.catalog().get("foo").unwrap().rows();
18346 assert_eq!(rows[0].values[0], Value::Int(3));
18347 }
18348
18349 #[test]
18350 fn select_star_returns_all_rows_in_insertion_order() {
18351 let mut e = Engine::new();
18352 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18353 .unwrap();
18354 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18355 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18356 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18357
18358 let r = e.execute("SELECT * FROM foo").unwrap();
18359 let QueryResult::Rows { columns, rows } = r else {
18360 panic!("expected Rows")
18361 };
18362 assert_eq!(columns.len(), 2);
18363 assert_eq!(columns[0].name, "a");
18364 assert_eq!(rows.len(), 3);
18365 assert_eq!(
18366 rows[1].values,
18367 vec![Value::Int(2), Value::Text("two".into())]
18368 );
18369 }
18370
18371 #[test]
18372 fn select_star_on_empty_table_returns_zero_rows() {
18373 let mut e = Engine::new();
18374 e.execute("CREATE TABLE foo (a INT)").unwrap();
18375 let r = e.execute("SELECT * FROM foo").unwrap();
18376 match r {
18377 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18378 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18379 }
18380 }
18381
18382 fn make_three_row_users(e: &mut Engine) {
18385 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18386 .unwrap();
18387 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18388 .unwrap();
18389 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18390 .unwrap();
18391 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18392 .unwrap();
18393 }
18394
18395 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18396 match r {
18397 QueryResult::Rows { columns, rows } => (columns, rows),
18398 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18399 }
18400 }
18401
18402 #[test]
18403 fn where_filter_passes_only_true_rows() {
18404 let mut e = Engine::new();
18405 make_three_row_users(&mut e);
18406 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18407 let (_, rows) = unwrap_rows(r);
18408 assert_eq!(rows.len(), 2);
18409 assert_eq!(rows[0].values[0], Value::Int(2));
18410 assert_eq!(rows[1].values[0], Value::Int(3));
18411 }
18412
18413 #[test]
18414 fn where_with_null_result_filters_out_row() {
18415 let mut e = Engine::new();
18416 make_three_row_users(&mut e);
18417 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18419 let (_, rows) = unwrap_rows(r);
18420 assert_eq!(rows.len(), 1);
18421 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18422 }
18423
18424 #[test]
18425 fn projection_named_columns() {
18426 let mut e = Engine::new();
18427 make_three_row_users(&mut e);
18428 let r = e.execute("SELECT name, score FROM users").unwrap();
18429 let (cols, rows) = unwrap_rows(r);
18430 assert_eq!(cols.len(), 2);
18431 assert_eq!(cols[0].name, "name");
18432 assert_eq!(cols[1].name, "score");
18433 assert_eq!(rows.len(), 3);
18434 assert_eq!(
18435 rows[0].values,
18436 vec![Value::Text("alice".into()), Value::Int(90)]
18437 );
18438 }
18439
18440 #[test]
18441 fn projection_with_column_alias() {
18442 let mut e = Engine::new();
18443 make_three_row_users(&mut e);
18444 let r = e
18445 .execute("SELECT name AS who FROM users WHERE id = 1")
18446 .unwrap();
18447 let (cols, rows) = unwrap_rows(r);
18448 assert_eq!(cols[0].name, "who");
18449 assert_eq!(rows.len(), 1);
18450 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18451 }
18452
18453 #[test]
18454 fn qualified_column_with_table_alias_resolves() {
18455 let mut e = Engine::new();
18456 make_three_row_users(&mut e);
18457 let r = e
18458 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18459 .unwrap();
18460 let (cols, rows) = unwrap_rows(r);
18461 assert_eq!(cols.len(), 2);
18462 assert_eq!(rows.len(), 2);
18463 }
18464
18465 #[test]
18466 fn qualified_column_with_wrong_alias_errors() {
18467 let mut e = Engine::new();
18468 make_three_row_users(&mut e);
18469 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18470 assert!(matches!(
18471 err,
18472 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18473 ));
18474 }
18475
18476 #[test]
18477 fn select_unknown_column_errors_in_projection() {
18478 let mut e = Engine::new();
18479 make_three_row_users(&mut e);
18480 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18481 assert!(matches!(
18482 err,
18483 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18484 ));
18485 }
18486
18487 #[test]
18488 fn where_unknown_column_errors() {
18489 let mut e = Engine::new();
18490 make_three_row_users(&mut e);
18491 let err = e
18492 .execute("SELECT * FROM users WHERE ghost = 1")
18493 .unwrap_err();
18494 assert!(matches!(
18495 err,
18496 EngineError::Eval(EvalError::ColumnNotFound { .. })
18497 ));
18498 }
18499
18500 #[test]
18501 fn expression_projection_evaluates_and_renders() {
18502 let mut e = Engine::new();
18505 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18506 e.execute("INSERT INTO t VALUES (3)").unwrap();
18507 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18508 assert_eq!(rows.len(), 1);
18509 assert_eq!(rows[0].values[0], Value::Int(3));
18512 }
18513
18514 #[test]
18515 fn select_unknown_table_errors() {
18516 let mut e = Engine::new();
18517 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18518 assert!(matches!(
18519 err,
18520 EngineError::Storage(StorageError::TableNotFound { .. })
18521 ));
18522 }
18523
18524 #[test]
18525 fn invalid_sql_returns_parse_error() {
18526 let mut e = Engine::new();
18529 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18530 assert!(matches!(err, EngineError::Parse(_)));
18531 }
18532
18533 #[test]
18536 fn create_index_registers_on_table() {
18537 let mut e = Engine::new();
18538 make_three_row_users(&mut e);
18539 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18540 let t = e.catalog().get("users").unwrap();
18541 assert_eq!(t.indices().len(), 1);
18542 assert_eq!(t.indices()[0].name, "by_name");
18543 }
18544
18545 #[test]
18546 fn create_index_on_unknown_table_errors() {
18547 let mut e = Engine::new();
18548 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18549 assert!(matches!(
18550 err,
18551 EngineError::Storage(StorageError::TableNotFound { .. })
18552 ));
18553 }
18554
18555 #[test]
18556 fn create_index_on_unknown_column_errors() {
18557 let mut e = Engine::new();
18558 make_three_row_users(&mut e);
18559 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18560 assert!(matches!(
18561 err,
18562 EngineError::Storage(StorageError::ColumnNotFound { .. })
18563 ));
18564 }
18565
18566 #[test]
18567 fn select_eq_uses_index_returns_same_rows_as_scan() {
18568 let mut without = Engine::new();
18572 make_three_row_users(&mut without);
18573 let mut with = Engine::new();
18574 make_three_row_users(&mut with);
18575 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18576
18577 let q = "SELECT * FROM users WHERE id = 2";
18578 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18579 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18580 assert_eq!(no_idx_rows, idx_rows);
18581 assert_eq!(idx_rows.len(), 1);
18582 }
18583
18584 #[test]
18585 fn select_eq_with_no_matching_index_value_returns_empty() {
18586 let mut e = Engine::new();
18587 make_three_row_users(&mut e);
18588 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18589 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18590 assert_eq!(rows.len(), 0);
18591 }
18592
18593 #[test]
18596 fn begin_sets_in_transaction_flag() {
18597 let mut e = Engine::new();
18598 assert!(!e.in_transaction());
18599 e.execute("BEGIN").unwrap();
18600 assert!(e.in_transaction());
18601 }
18602
18603 #[test]
18604 fn double_begin_errors() {
18605 let mut e = Engine::new();
18606 e.execute("BEGIN").unwrap();
18607 let err = e.execute("BEGIN").unwrap_err();
18608 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18609 }
18610
18611 #[test]
18612 fn commit_without_begin_errors() {
18613 let mut e = Engine::new();
18614 let err = e.execute("COMMIT").unwrap_err();
18615 assert_eq!(err, EngineError::NoActiveTransaction);
18616 }
18617
18618 #[test]
18619 fn rollback_without_begin_errors() {
18620 let mut e = Engine::new();
18621 let err = e.execute("ROLLBACK").unwrap_err();
18622 assert_eq!(err, EngineError::NoActiveTransaction);
18623 }
18624
18625 #[test]
18626 fn commit_applies_shadow_to_committed_catalog() {
18627 let mut e = Engine::new();
18628 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18629 e.execute("BEGIN").unwrap();
18630 e.execute("INSERT INTO t VALUES (1)").unwrap();
18631 e.execute("INSERT INTO t VALUES (2)").unwrap();
18632 e.execute("COMMIT").unwrap();
18633 assert!(!e.in_transaction());
18634 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18635 }
18636
18637 #[test]
18638 fn rollback_discards_shadow() {
18639 let mut e = Engine::new();
18640 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18641 e.execute("BEGIN").unwrap();
18642 e.execute("INSERT INTO t VALUES (1)").unwrap();
18643 e.execute("INSERT INTO t VALUES (2)").unwrap();
18644 e.execute("ROLLBACK").unwrap();
18645 assert!(!e.in_transaction());
18646 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18647 }
18648
18649 #[test]
18650 fn select_during_tx_sees_uncommitted_writes_own_session() {
18651 let mut e = Engine::new();
18654 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18655 e.execute("BEGIN").unwrap();
18656 e.execute("INSERT INTO t VALUES (42)").unwrap();
18657 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18658 assert_eq!(rows.len(), 1);
18659 assert_eq!(rows[0].values[0], Value::Int(42));
18660 }
18661
18662 #[test]
18663 fn snapshot_with_no_users_is_bare_catalog_format() {
18664 let mut e = Engine::new();
18665 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18666 let bytes = e.snapshot();
18667 assert_eq!(
18668 &bytes[..8],
18669 b"SPGDB001",
18670 "must be the bare v3.x catalog magic"
18671 );
18672 let e2 = Engine::restore_envelope(&bytes).unwrap();
18673 assert!(e2.users().is_empty());
18674 assert_eq!(e2.catalog().table_count(), 1);
18675 }
18676
18677 #[test]
18678 fn snapshot_with_users_round_trips_both_via_envelope() {
18679 let mut e = Engine::new();
18680 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18681 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18682 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18683 .unwrap();
18684 let bytes = e.snapshot();
18685 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18686 let e2 = Engine::restore_envelope(&bytes).unwrap();
18687 assert_eq!(e2.users().len(), 2);
18688 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18689 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18690 assert_eq!(e2.verify_user("alice", "wrong"), None);
18691 assert_eq!(e2.catalog().table_count(), 1);
18692 }
18693
18694 #[test]
18695 fn ddl_inside_tx_also_rolled_back() {
18696 let mut e = Engine::new();
18697 e.execute("BEGIN").unwrap();
18698 e.execute("CREATE TABLE t (v INT)").unwrap();
18699 e.execute("SELECT * FROM t").unwrap();
18701 e.execute("ROLLBACK").unwrap();
18702 let err = e.execute("SELECT * FROM t").unwrap_err();
18704 assert!(matches!(
18705 err,
18706 EngineError::Storage(StorageError::TableNotFound { .. })
18707 ));
18708 }
18709
18710 #[test]
18713 fn create_publication_lands_in_catalog() {
18714 let mut e = Engine::new();
18715 assert!(e.publications().is_empty());
18716 e.execute("CREATE PUBLICATION pub_a").unwrap();
18717 assert_eq!(e.publications().len(), 1);
18718 assert!(e.publications().contains("pub_a"));
18719 }
18720
18721 #[test]
18722 fn create_publication_duplicate_errors() {
18723 let mut e = Engine::new();
18724 e.execute("CREATE PUBLICATION pub_a").unwrap();
18725 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18726 assert!(
18727 alloc::format!("{err:?}").contains("DuplicateName"),
18728 "got {err:?}"
18729 );
18730 }
18731
18732 #[test]
18733 fn drop_publication_silent_when_absent() {
18734 let mut e = Engine::new();
18735 let r = e.execute("DROP PUBLICATION nope").unwrap();
18738 match r {
18739 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18740 other => panic!("expected CommandOk, got {other:?}"),
18741 }
18742 }
18743
18744 #[test]
18745 fn drop_publication_present_reports_one_affected() {
18746 let mut e = Engine::new();
18747 e.execute("CREATE PUBLICATION pub_a").unwrap();
18748 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18749 match r {
18750 QueryResult::CommandOk {
18751 affected,
18752 modified_catalog,
18753 } => {
18754 assert_eq!(affected, 1);
18755 assert!(modified_catalog);
18756 }
18757 other => panic!("expected CommandOk, got {other:?}"),
18758 }
18759 assert!(e.publications().is_empty());
18760 }
18761
18762 #[test]
18763 fn publications_persist_across_snapshot_restore() {
18764 let mut e = Engine::new();
18769 e.execute("CREATE PUBLICATION pub_a").unwrap();
18770 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18771 .unwrap();
18772 let snap = e.snapshot();
18773 let e2 = Engine::restore_envelope(&snap).unwrap();
18774 assert_eq!(e2.publications().len(), 2);
18775 assert!(e2.publications().contains("pub_a"));
18776 assert!(e2.publications().contains("pub_b"));
18777 }
18778
18779 #[test]
18780 fn create_publication_allowed_inside_transaction() {
18781 let mut e = Engine::new();
18785 e.execute("BEGIN").unwrap();
18786 e.execute("CREATE PUBLICATION pub_a").unwrap();
18787 e.execute("COMMIT").unwrap();
18788 assert!(e.publications().contains("pub_a"));
18789 }
18790
18791 #[test]
18794 fn create_publication_for_table_list_lands_with_scope() {
18795 let mut e = Engine::new();
18796 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18797 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18798 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18799 .unwrap();
18800 let scope = e.publications().get("pub_a").cloned();
18801 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18802 panic!("expected ForTables scope, got {scope:?}")
18803 };
18804 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18805 }
18806
18807 #[test]
18808 fn create_publication_all_tables_except_lands_with_scope() {
18809 let mut e = Engine::new();
18810 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18811 .unwrap();
18812 let scope = e.publications().get("pub_a").cloned();
18813 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18814 panic!("expected AllTablesExcept scope, got {scope:?}")
18815 };
18816 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18817 }
18818
18819 #[test]
18820 fn show_publications_empty_returns_zero_rows() {
18821 let e = Engine::new();
18822 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18823 let QueryResult::Rows { rows, columns } = r else {
18824 panic!()
18825 };
18826 assert!(rows.is_empty());
18827 assert_eq!(columns.len(), 3);
18828 assert_eq!(columns[0].name, "name");
18829 assert_eq!(columns[1].name, "scope");
18830 assert_eq!(columns[2].name, "table_count");
18831 }
18832
18833 #[test]
18834 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18835 let mut e = Engine::new();
18836 e.execute("CREATE PUBLICATION z_pub").unwrap();
18837 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18838 .unwrap();
18839 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18840 .unwrap();
18841 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18842 let QueryResult::Rows { rows, .. } = r else {
18843 panic!()
18844 };
18845 assert_eq!(rows.len(), 3);
18846 let names: Vec<&str> = rows
18848 .iter()
18849 .map(|r| {
18850 if let Value::Text(s) = &r.values[0] {
18851 s.as_str()
18852 } else {
18853 panic!()
18854 }
18855 })
18856 .collect();
18857 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18858 match &rows[0].values[1] {
18860 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18861 other => panic!("expected Text, got {other:?}"),
18862 }
18863 assert_eq!(rows[0].values[2], Value::Int(2));
18864 match &rows[1].values[1] {
18866 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18867 other => panic!("expected Text, got {other:?}"),
18868 }
18869 assert_eq!(rows[1].values[2], Value::Int(1));
18870 match &rows[2].values[1] {
18872 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18873 other => panic!("expected Text, got {other:?}"),
18874 }
18875 assert_eq!(rows[2].values[2], Value::Null);
18876 }
18877
18878 #[test]
18879 fn for_list_scopes_persist_across_snapshot() {
18880 let mut e = Engine::new();
18883 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18884 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18885 .unwrap();
18886 let snap = e.snapshot();
18887 let e2 = Engine::restore_envelope(&snap).unwrap();
18888 assert_eq!(e2.publications().len(), 2);
18889 let p1 = e2.publications().get("p1").cloned();
18890 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18891 panic!("p1 scope lost: {p1:?}")
18892 };
18893 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18894 let p2 = e2.publications().get("p2").cloned();
18895 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18896 panic!("p2 scope lost: {p2:?}")
18897 };
18898 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18899 }
18900
18901 #[test]
18904 fn create_subscription_lands_in_catalog_with_defaults() {
18905 let mut e = Engine::new();
18906 e.execute(
18907 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18908 )
18909 .unwrap();
18910 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18911 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18912 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18913 assert!(s.enabled);
18914 assert_eq!(s.last_received_pos, 0);
18915 }
18916
18917 #[test]
18918 fn create_subscription_duplicate_name_errors() {
18919 let mut e = Engine::new();
18920 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18921 .unwrap();
18922 let err = e
18923 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18924 .unwrap_err();
18925 assert!(
18926 alloc::format!("{err:?}").contains("DuplicateName"),
18927 "got {err:?}"
18928 );
18929 }
18930
18931 #[test]
18932 fn drop_subscription_silent_when_absent() {
18933 let mut e = Engine::new();
18934 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18935 match r {
18936 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18937 other => panic!("expected CommandOk, got {other:?}"),
18938 }
18939 }
18940
18941 #[test]
18942 fn subscription_advance_updates_last_pos_monotone() {
18943 let mut e = Engine::new();
18944 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18945 .unwrap();
18946 assert!(e.subscription_advance("s", 100));
18947 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18948 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18950 assert!(e.subscription_advance("s", 200));
18951 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18952 assert!(!e.subscription_advance("missing", 1));
18953 }
18954
18955 #[test]
18956 fn show_subscriptions_returns_rows_ordered_by_name() {
18957 let mut e = Engine::new();
18958 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18959 .unwrap();
18960 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18961 .unwrap();
18962 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18963 let QueryResult::Rows { rows, columns } = r else {
18964 panic!()
18965 };
18966 assert_eq!(rows.len(), 2);
18967 assert_eq!(columns.len(), 5);
18968 assert_eq!(columns[0].name, "name");
18969 assert_eq!(columns[4].name, "last_received_pos");
18970 let names: Vec<&str> = rows
18972 .iter()
18973 .map(|r| {
18974 if let Value::Text(s) = &r.values[0] {
18975 s.as_str()
18976 } else {
18977 panic!()
18978 }
18979 })
18980 .collect();
18981 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18982 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18984 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18985 assert_eq!(rows[0].values[3], Value::Bool(true));
18986 assert_eq!(rows[0].values[4], Value::BigInt(0));
18987 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18989 }
18990
18991 #[test]
18992 fn subscriptions_persist_across_snapshot_envelope_v4() {
18993 let mut e = Engine::new();
18994 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18995 .unwrap();
18996 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18997 .unwrap();
18998 e.subscription_advance("s2", 42);
18999 let snap = e.snapshot();
19000 let e2 = Engine::restore_envelope(&snap).unwrap();
19001 assert_eq!(e2.subscriptions().len(), 2);
19002 let s1 = e2.subscriptions().get("s1").unwrap();
19003 assert_eq!(s1.conn_str, "h=A");
19004 assert_eq!(
19005 s1.publications,
19006 alloc::vec!["p1".to_string(), "p2".to_string()]
19007 );
19008 assert_eq!(s1.last_received_pos, 0);
19009 let s2 = e2.subscriptions().get("s2").unwrap();
19010 assert_eq!(s2.last_received_pos, 42);
19011 }
19012
19013 #[test]
19014 fn v3_envelope_loads_with_empty_subscriptions() {
19015 let mut e = Engine::new();
19019 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
19020 let catalog = e.catalog.serialize();
19021 let users = crate::users::serialize_users(&e.users);
19022 let pubs = e.publications.serialize();
19023 let mut buf = Vec::new();
19024 buf.extend_from_slice(b"SPGENV01");
19025 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19027 buf.extend_from_slice(&catalog);
19028 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19029 buf.extend_from_slice(&users);
19030 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19031 buf.extend_from_slice(&pubs);
19032 let crc = spg_crypto::crc32::crc32(&buf);
19033 buf.extend_from_slice(&crc.to_le_bytes());
19034
19035 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
19036 assert!(e2.subscriptions().is_empty());
19037 assert!(e2.publications().contains("pub_legacy"));
19038 }
19039
19040 #[test]
19041 fn create_subscription_allowed_inside_transaction() {
19042 let mut e = Engine::new();
19043 e.execute("BEGIN").unwrap();
19044 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
19045 .unwrap();
19046 e.execute("COMMIT").unwrap();
19047 assert!(e.subscriptions().contains("s"));
19048 }
19049
19050 #[test]
19052 fn analyze_populates_histogram_bounds() {
19053 let mut e = Engine::new();
19054 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
19055 .unwrap();
19056 for i in 0..50 {
19057 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
19058 .unwrap();
19059 }
19060 e.execute("ANALYZE t").unwrap();
19061 let stats = e.statistics();
19062 let id_stats = stats.get("t", "id").unwrap();
19063 assert!(id_stats.histogram_bounds.len() >= 2);
19064 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
19065 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
19066 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
19067 assert_eq!(id_stats.n_distinct, 50);
19068 }
19069
19070 #[test]
19071 fn reanalyze_overwrites_prior_stats() {
19072 let mut e = Engine::new();
19073 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19074 for i in 0..10 {
19075 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19076 .unwrap();
19077 }
19078 e.execute("ANALYZE t").unwrap();
19079 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
19080 assert_eq!(n1, 10);
19081 for i in 10..30 {
19082 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19083 .unwrap();
19084 }
19085 e.execute("ANALYZE t").unwrap();
19086 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
19087 assert_eq!(n2, 30);
19088 }
19089
19090 #[test]
19091 fn analyze_unknown_table_errors() {
19092 let mut e = Engine::new();
19093 let err = e.execute("ANALYZE nonexistent").unwrap_err();
19094 assert!(matches!(
19095 err,
19096 EngineError::Storage(StorageError::TableNotFound { .. })
19097 ));
19098 }
19099
19100 #[test]
19101 fn bare_analyze_covers_all_user_tables() {
19102 let mut e = Engine::new();
19103 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19104 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19105 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19106 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19107 let r = e.execute("ANALYZE").unwrap();
19108 match r {
19109 QueryResult::CommandOk {
19110 affected,
19111 modified_catalog,
19112 } => {
19113 assert_eq!(affected, 2);
19114 assert!(modified_catalog);
19115 }
19116 other => panic!("expected CommandOk, got {other:?}"),
19117 }
19118 assert!(e.statistics().get("t1", "id").is_some());
19119 assert!(e.statistics().get("t2", "name").is_some());
19120 }
19121
19122 #[test]
19123 fn select_from_spg_statistic_returns_rows_per_column() {
19124 let mut e = Engine::new();
19125 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19126 .unwrap();
19127 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19128 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19129 e.execute("ANALYZE t").unwrap();
19130 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19131 let QueryResult::Rows { rows, columns } = r else {
19132 panic!()
19133 };
19134 assert_eq!(columns.len(), 6);
19136 assert_eq!(columns[0].name, "table_name");
19137 assert_eq!(columns[4].name, "histogram_bounds");
19138 assert_eq!(columns[5].name, "cold_row_count");
19139 assert_eq!(rows.len(), 2, "one row per column of t");
19140 match (&rows[0].values[0], &rows[0].values[1]) {
19142 (Value::Text(t), Value::Text(c)) => {
19143 assert_eq!(t, "t");
19144 assert_eq!(c, "id");
19146 }
19147 _ => panic!(),
19148 }
19149 }
19150
19151 #[test]
19152 fn analyze_skips_vector_columns() {
19153 let mut e = Engine::new();
19156 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19157 .unwrap();
19158 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19159 e.execute("ANALYZE t").unwrap();
19160 assert!(e.statistics().get("t", "id").is_some());
19161 assert!(e.statistics().get("t", "v").is_none());
19162 }
19163
19164 #[test]
19165 fn statistics_persist_across_envelope_v5_round_trip() {
19166 let mut e = Engine::new();
19167 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19168 for i in 0..20 {
19169 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19170 .unwrap();
19171 }
19172 e.execute("ANALYZE").unwrap();
19173 let snap = e.snapshot();
19174 let e2 = Engine::restore_envelope(&snap).unwrap();
19175 let s = e2.statistics().get("t", "id").unwrap();
19176 assert_eq!(s.n_distinct, 20);
19177 }
19178
19179 #[test]
19182 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19183 let mut e = Engine::new();
19187 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19188 for i in 0..9 {
19189 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19190 .unwrap();
19191 }
19192 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19193 e.execute("INSERT INTO t VALUES (9)").unwrap();
19194 let needs = e.tables_needing_analyze();
19195 assert_eq!(needs, alloc::vec!["t".to_string()]);
19196 }
19197
19198 #[test]
19199 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19200 let mut e = Engine::new();
19206 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19207 for i in 0..1000 {
19208 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19209 .unwrap();
19210 }
19211 e.execute("ANALYZE t").unwrap();
19212 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19213 for i in 1000..1050 {
19214 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19215 .unwrap();
19216 }
19217 assert!(
19218 e.tables_needing_analyze().is_empty(),
19219 "50 inserts < threshold of ~105"
19220 );
19221 for i in 1050..1200 {
19222 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19223 .unwrap();
19224 }
19225 assert_eq!(
19226 e.tables_needing_analyze(),
19227 alloc::vec!["t".to_string()],
19228 "200 inserts > 0.1 × 1200 threshold"
19229 );
19230 }
19231
19232 #[test]
19233 fn auto_analyze_threshold_resets_after_analyze() {
19234 let mut e = Engine::new();
19235 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19236 for i in 0..200 {
19237 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19238 .unwrap();
19239 }
19240 assert!(!e.tables_needing_analyze().is_empty());
19241 e.execute("ANALYZE").unwrap();
19242 assert!(
19243 e.tables_needing_analyze().is_empty(),
19244 "ANALYZE must reset the counter"
19245 );
19246 }
19247
19248 #[test]
19249 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19250 let mut e = Engine::new();
19251 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19252 .unwrap();
19253 for i in 0..50 {
19254 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19255 .unwrap();
19256 }
19257 e.execute("ANALYZE t").unwrap();
19258 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19261 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19262 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19263 }
19264
19265 #[test]
19266 fn v4_envelope_loads_with_empty_statistics() {
19267 let mut e = Engine::new();
19271 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19272 .unwrap();
19273 let catalog = e.catalog.serialize();
19274 let users = crate::users::serialize_users(&e.users);
19275 let pubs = e.publications.serialize();
19276 let subs = e.subscriptions.serialize();
19277 let mut buf = Vec::new();
19278 buf.extend_from_slice(b"SPGENV01");
19279 buf.push(4u8);
19280 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19281 buf.extend_from_slice(&catalog);
19282 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19283 buf.extend_from_slice(&users);
19284 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19285 buf.extend_from_slice(&pubs);
19286 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19287 buf.extend_from_slice(&subs);
19288 let crc = spg_crypto::crc32::crc32(&buf);
19289 buf.extend_from_slice(&crc.to_le_bytes());
19290 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19291 assert!(e2.statistics().is_empty());
19292 }
19293
19294 #[test]
19295 fn v1_v2_envelope_loads_with_empty_publications() {
19296 let mut e = Engine::new();
19303 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19306 .unwrap();
19307
19308 let catalog = e.catalog.serialize();
19310 let users = crate::users::serialize_users(&e.users);
19311 let mut buf = Vec::new();
19312 buf.extend_from_slice(b"SPGENV01");
19313 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19315 buf.extend_from_slice(&catalog);
19316 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19317 buf.extend_from_slice(&users);
19318 let crc = spg_crypto::crc32::crc32(&buf);
19319 buf.extend_from_slice(&crc.to_le_bytes());
19320
19321 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19322 assert!(e2.publications().is_empty());
19323 }
19324}