1#![no_std]
6
7extern crate alloc;
8
9pub mod aggregate;
10pub mod describe;
11pub mod eval;
12pub mod fts;
13pub mod json;
14pub mod memoize;
15pub mod plan_cache;
16pub mod publications;
17pub mod query_stats;
18pub mod reorder;
19pub mod selectivity;
20pub mod statistics;
21pub mod subscriptions;
22pub mod triggers;
23pub mod users;
24
25pub use crate::users::{Role, ScramSecrets, UserError, UserStore};
26
27use alloc::borrow::Cow;
28use alloc::boxed::Box;
29use alloc::collections::BTreeMap;
30use alloc::string::{String, ToString};
31use alloc::vec::Vec;
32use core::fmt;
33
34use spg_sql::ast::{
35 BinOp, ColumnDef, ColumnName, ColumnTypeName, CreateIndexStatement, CreatePublicationStatement,
36 CreateSubscriptionStatement, CreateTableStatement, CreateUserStatement, Expr, FrameBound,
37 FrameKind, FromClause, IndexMethod, InsertStatement, JoinKind, Literal, OrderBy, SelectItem,
38 SelectStatement, Statement, TableRef, UnOp, UnionKind, VecEncoding as SqlVecEncoding,
39 WindowFrame,
40};
41pub use spg_sql::ast::Statement as ParsedStatement;
45use spg_sql::parser::{self, ParseError};
46use spg_storage::{
47 Catalog, ColumnSchema, CompactReport, DataType, IndexKey, IndexKind, Row, StorageError, Table,
48 TableSchema, Value, VecEncoding,
49};
50
51use crate::eval::{EvalContext, EvalError};
52
53#[derive(Debug, Clone, PartialEq)]
55#[non_exhaustive]
56pub enum QueryResult {
57 CommandOk {
66 affected: usize,
67 modified_catalog: bool,
68 },
69 Rows {
71 columns: Vec<ColumnSchema>,
72 rows: Vec<Row>,
73 },
74}
75
76#[derive(Debug, Clone, PartialEq)]
82#[non_exhaustive]
83pub enum EngineError {
84 Parse(ParseError),
85 Storage(StorageError),
86 Eval(EvalError),
87 Unsupported(String),
89 TransactionAlreadyOpen,
91 NoActiveTransaction,
93 WriteRequired,
98 RowLimitExceeded(usize),
101 Cancelled,
107}
108
109impl fmt::Display for EngineError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Self::Parse(e) => write!(f, "parse: {e}"),
113 Self::Storage(e) => write!(f, "storage: {e}"),
114 Self::Eval(e) => write!(f, "eval: {e}"),
115 Self::Unsupported(s) => write!(f, "unsupported: {s}"),
116 Self::TransactionAlreadyOpen => f.write_str("a transaction is already open"),
117 Self::NoActiveTransaction => f.write_str("no active transaction"),
118 Self::WriteRequired => {
119 f.write_str("statement requires a write lock (use execute, not execute_readonly)")
120 }
121 Self::RowLimitExceeded(n) => {
122 write!(f, "query exceeded max_query_rows={n}")
123 }
124 Self::Cancelled => f.write_str("query cancelled (timeout or client request)"),
125 }
126 }
127}
128
129impl From<ParseError> for EngineError {
130 fn from(e: ParseError) -> Self {
131 Self::Parse(e)
132 }
133}
134impl From<StorageError> for EngineError {
135 fn from(e: StorageError) -> Self {
136 Self::Storage(e)
137 }
138}
139impl From<EvalError> for EngineError {
140 fn from(e: EvalError) -> Self {
141 Self::Eval(e)
142 }
143}
144
145pub type ClockFn = fn() -> i64;
154
155pub type SaltFn = fn() -> [u8; 16];
162
163pub type MonotonicNowFn = fn() -> u64;
179
180#[derive(Debug, Clone, Copy)]
181struct Deadline {
182 now_fn: MonotonicNowFn,
183 deadline_us: u64,
185}
186
187#[derive(Debug, Clone, Copy)]
188pub struct CancelToken<'a> {
189 flag: Option<&'a core::sync::atomic::AtomicBool>,
190 deadline: Option<Deadline>,
197}
198
199impl<'a> CancelToken<'a> {
200 #[must_use]
201 pub const fn none() -> Self {
202 Self {
203 flag: None,
204 deadline: None,
205 }
206 }
207
208 #[must_use]
209 pub const fn from_flag(f: &'a core::sync::atomic::AtomicBool) -> Self {
210 Self {
211 flag: Some(f),
212 deadline: None,
213 }
214 }
215
216 #[must_use]
224 pub const fn with_deadline(mut self, now_fn: MonotonicNowFn, deadline_us: u64) -> Self {
225 self.deadline = Some(Deadline {
226 now_fn,
227 deadline_us,
228 });
229 self
230 }
231
232 #[must_use]
233 pub fn is_cancelled(self) -> bool {
234 if self
235 .flag
236 .is_some_and(|f| f.load(core::sync::atomic::Ordering::Relaxed))
237 {
238 return true;
239 }
240 if let Some(d) = self.deadline
244 && (d.now_fn)() >= d.deadline_us
245 {
246 return true;
247 }
248 false
249 }
250
251 #[inline]
255 pub fn check(self) -> Result<(), EngineError> {
256 if self.is_cancelled() {
257 Err(EngineError::Cancelled)
258 } else {
259 Ok(())
260 }
261 }
262}
263
264const ENVELOPE_MAGIC: &[u8; 8] = b"SPGENV01";
322const ENVELOPE_VERSION_V1: u8 = 1;
323const ENVELOPE_VERSION_V2: u8 = 2;
324const ENVELOPE_VERSION_V3: u8 = 3;
325const ENVELOPE_VERSION_V4: u8 = 4;
326const ENVELOPE_VERSION_V5: u8 = 5;
327
328fn build_envelope(catalog: &[u8], users: &[u8], pubs: &[u8], subs: &[u8], stats: &[u8]) -> Vec<u8> {
329 let mut out = Vec::with_capacity(
330 8 + 1
331 + 4
332 + catalog.len()
333 + 4
334 + users.len()
335 + 4
336 + pubs.len()
337 + 4
338 + subs.len()
339 + 4
340 + stats.len()
341 + 4,
342 );
343 out.extend_from_slice(ENVELOPE_MAGIC);
344 out.push(ENVELOPE_VERSION_V5);
345 out.extend_from_slice(
346 &u32::try_from(catalog.len())
347 .expect("≤ 4G catalog")
348 .to_le_bytes(),
349 );
350 out.extend_from_slice(catalog);
351 out.extend_from_slice(
352 &u32::try_from(users.len())
353 .expect("≤ 4G users")
354 .to_le_bytes(),
355 );
356 out.extend_from_slice(users);
357 out.extend_from_slice(
358 &u32::try_from(pubs.len())
359 .expect("≤ 4G publications")
360 .to_le_bytes(),
361 );
362 out.extend_from_slice(pubs);
363 out.extend_from_slice(
364 &u32::try_from(subs.len())
365 .expect("≤ 4G subscriptions")
366 .to_le_bytes(),
367 );
368 out.extend_from_slice(subs);
369 out.extend_from_slice(
370 &u32::try_from(stats.len())
371 .expect("≤ 4G statistics")
372 .to_le_bytes(),
373 );
374 out.extend_from_slice(stats);
375 let crc = spg_crypto::crc32::crc32(&out);
376 out.extend_from_slice(&crc.to_le_bytes());
377 out
378}
379
380enum EnvelopeParse<'a> {
387 Bare,
388 Pair {
389 catalog: &'a [u8],
390 users: &'a [u8],
391 publications: Option<&'a [u8]>,
392 subscriptions: Option<&'a [u8]>,
393 statistics: Option<&'a [u8]>,
394 },
395 CrcMismatch {
396 expected: u32,
397 computed: u32,
398 },
399}
400
401fn split_envelope(buf: &[u8]) -> EnvelopeParse<'_> {
406 if buf.len() < 8 + 1 + 4 || &buf[..8] != ENVELOPE_MAGIC {
407 return EnvelopeParse::Bare;
408 }
409 let version = buf[8];
410 if !matches!(
411 version,
412 ENVELOPE_VERSION_V1
413 | ENVELOPE_VERSION_V2
414 | ENVELOPE_VERSION_V3
415 | ENVELOPE_VERSION_V4
416 | ENVELOPE_VERSION_V5
417 ) {
418 return EnvelopeParse::Bare;
419 }
420 let mut p = 9usize;
421 let Some(cat_len_bytes) = buf.get(p..p + 4) else {
422 return EnvelopeParse::Bare;
423 };
424 let Ok(cat_len_arr) = cat_len_bytes.try_into() else {
425 return EnvelopeParse::Bare;
426 };
427 let cat_len = u32::from_le_bytes(cat_len_arr) as usize;
428 p += 4;
429 if p + cat_len + 4 > buf.len() {
430 return EnvelopeParse::Bare;
431 }
432 let catalog = &buf[p..p + cat_len];
433 p += cat_len;
434 let Some(user_len_bytes) = buf.get(p..p + 4) else {
435 return EnvelopeParse::Bare;
436 };
437 let Ok(user_len_arr) = user_len_bytes.try_into() else {
438 return EnvelopeParse::Bare;
439 };
440 let user_len = u32::from_le_bytes(user_len_arr) as usize;
441 p += 4;
442 if p + user_len > buf.len() {
443 return EnvelopeParse::Bare;
444 }
445 let users = &buf[p..p + user_len];
446 p += user_len;
447 let publications = if matches!(
448 version,
449 ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
450 ) {
451 let Some(pubs_len_bytes) = buf.get(p..p + 4) else {
453 return EnvelopeParse::Bare;
454 };
455 let Ok(pubs_len_arr) = pubs_len_bytes.try_into() else {
456 return EnvelopeParse::Bare;
457 };
458 let pubs_len = u32::from_le_bytes(pubs_len_arr) as usize;
459 p += 4;
460 if p + pubs_len > buf.len() {
461 return EnvelopeParse::Bare;
462 }
463 let pubs_slice = &buf[p..p + pubs_len];
464 p += pubs_len;
465 Some(pubs_slice)
466 } else {
467 None
468 };
469 let subscriptions = if matches!(version, ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5) {
470 let Some(subs_len_bytes) = buf.get(p..p + 4) else {
472 return EnvelopeParse::Bare;
473 };
474 let Ok(subs_len_arr) = subs_len_bytes.try_into() else {
475 return EnvelopeParse::Bare;
476 };
477 let subs_len = u32::from_le_bytes(subs_len_arr) as usize;
478 p += 4;
479 if p + subs_len > buf.len() {
480 return EnvelopeParse::Bare;
481 }
482 let subs_slice = &buf[p..p + subs_len];
483 p += subs_len;
484 Some(subs_slice)
485 } else {
486 None
487 };
488 let statistics = if version == ENVELOPE_VERSION_V5 {
489 let Some(stats_len_bytes) = buf.get(p..p + 4) else {
491 return EnvelopeParse::Bare;
492 };
493 let Ok(stats_len_arr) = stats_len_bytes.try_into() else {
494 return EnvelopeParse::Bare;
495 };
496 let stats_len = u32::from_le_bytes(stats_len_arr) as usize;
497 p += 4;
498 if p + stats_len > buf.len() {
499 return EnvelopeParse::Bare;
500 }
501 let stats_slice = &buf[p..p + stats_len];
502 p += stats_len;
503 Some(stats_slice)
504 } else {
505 None
506 };
507 if matches!(
508 version,
509 ENVELOPE_VERSION_V2 | ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
510 ) {
511 if p + 4 != buf.len() {
512 return EnvelopeParse::Bare;
513 }
514 let Ok(crc_arr) = buf[p..p + 4].try_into() else {
515 return EnvelopeParse::Bare;
516 };
517 let expected = u32::from_le_bytes(crc_arr);
518 let computed = spg_crypto::crc32::crc32(&buf[..p]);
519 if expected != computed {
520 return EnvelopeParse::CrcMismatch { expected, computed };
521 }
522 } else if p != buf.len() {
523 return EnvelopeParse::Bare;
525 }
526 EnvelopeParse::Pair {
527 catalog,
528 users,
529 publications,
530 subscriptions,
531 statistics,
532 }
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
545pub struct TxId(pub u64);
546
547pub const IMPLICIT_TX: TxId = TxId(0);
550
551pub const COMPACTION_TARGET_DEFAULT_BYTES: u64 = 4 * 1024 * 1024;
557
558#[derive(Debug, Default, Clone)]
563struct TxState {
564 catalog: Catalog,
569 savepoints: Vec<(String, Catalog)>,
575}
576
577#[derive(Debug, Clone)]
591pub struct CatalogSnapshot {
592 catalog: Catalog,
593 statistics: statistics::Statistics,
594 clock: Option<ClockFn>,
595 max_query_rows: Option<usize>,
596}
597
598#[derive(Debug, Default)]
599pub struct Engine {
600 catalog: Catalog,
603 tx_catalogs: BTreeMap<TxId, TxState>,
608 current_tx: Option<TxId>,
613 next_tx_id: u64,
616 clock: Option<ClockFn>,
619 salt_fn: Option<SaltFn>,
623 max_query_rows: Option<usize>,
629 users: UserStore,
635 publications: publications::Publications,
639 subscriptions: subscriptions::Subscriptions,
643 statistics: statistics::Statistics,
647 plan_cache: plan_cache::PlanCache,
651 query_stats: query_stats::QueryStats,
655 activity_provider: Option<ActivityProvider>,
662 audit_chain_provider: Option<AuditChainProvider>,
667 audit_verifier: Option<AuditVerifier>,
668 slow_query_threshold_us: Option<u64>,
674 slow_query_logger: Option<SlowQueryLogger>,
675 session_params: BTreeMap<String, String>,
682 trigger_recursion_depth: u32,
690 foreign_key_checks: bool,
699 meta_views_materialised: bool,
708 pending_foreign_keys: Vec<(alloc::string::String, spg_sql::ast::ForeignKeyConstraint)>,
709}
710
711const MAX_TRIGGER_RECURSION: u32 = 16;
715
716pub type SlowQueryLogger = fn(&str, u64);
720
721fn render_create_table(name: &str, columns: &[ColumnSchema]) -> String {
726 let mut out = alloc::format!("CREATE TABLE {name} (");
727 for (i, col) in columns.iter().enumerate() {
728 if i > 0 {
729 out.push_str(", ");
730 }
731 out.push_str(&col.name);
732 out.push(' ');
733 out.push_str(&render_data_type(col.ty));
734 if !col.nullable {
735 out.push_str(" NOT NULL");
736 }
737 if col.auto_increment {
738 out.push_str(" AUTO_INCREMENT");
739 }
740 }
741 out.push(')');
742 out
743}
744
745fn render_data_type(ty: DataType) -> String {
746 match ty {
747 DataType::SmallInt => "SMALLINT".into(),
748 DataType::Int => "INT".into(),
749 DataType::BigInt => "BIGINT".into(),
750 DataType::Float => "FLOAT".into(),
751 DataType::Text => "TEXT".into(),
752 DataType::Varchar(n) => alloc::format!("VARCHAR({n})"),
753 DataType::Char(n) => alloc::format!("CHAR({n})"),
754 DataType::Bool => "BOOL".into(),
755 DataType::Vector { dim, encoding } => match encoding {
756 spg_storage::VecEncoding::F32 => alloc::format!("VECTOR({dim})"),
757 spg_storage::VecEncoding::Sq8 => alloc::format!("VECTOR({dim}) USING SQ8"),
758 spg_storage::VecEncoding::F16 => alloc::format!("VECTOR({dim}) USING HALF"),
759 },
760 DataType::Numeric { precision, scale } => {
761 alloc::format!("NUMERIC({precision},{scale})")
762 }
763 DataType::Date => "DATE".into(),
764 DataType::Timestamp => "TIMESTAMP".into(),
765 DataType::Interval => "INTERVAL".into(),
766 DataType::Json => "JSON".into(),
767 DataType::Jsonb => "JSONB".into(),
768 DataType::Timestamptz => "TIMESTAMPTZ".into(),
769 DataType::Bytes => "BYTEA".into(),
770 DataType::TextArray => "TEXT[]".into(),
771 DataType::IntArray => "INT[]".into(),
772 DataType::BigIntArray => "BIGINT[]".into(),
773 DataType::TsVector => "TSVECTOR".into(),
774 DataType::TsQuery => "TSQUERY".into(),
775 DataType::Uuid => "UUID".into(),
776 DataType::Time => "TIME".into(),
777 DataType::Year => "YEAR".into(),
778 DataType::TimeTz => "TIMETZ".into(),
779 DataType::Money => "MONEY".into(),
780 DataType::Range(k) => k.keyword().into(),
781 DataType::Hstore => "HSTORE".into(),
782 DataType::IntArray2D => "INT[][]".into(),
783 DataType::BigIntArray2D => "BIGINT[][]".into(),
784 DataType::TextArray2D => "TEXT[][]".into(),
785 }
786}
787
788#[derive(Debug, Clone)]
792pub struct ActivityRow {
793 pub pid: u32,
794 pub user: String,
795 pub started_at_us: i64,
796 pub current_sql: String,
797 pub wait_event: String,
798 pub elapsed_us: i64,
799 pub in_transaction: bool,
800 pub application_name: String,
804}
805
806pub type ActivityProvider = fn() -> Vec<ActivityRow>;
809
810#[derive(Debug, Clone)]
813pub struct AuditRow {
814 pub seq: i64,
815 pub ts_ms: i64,
816 pub prev_hash_hex: String,
817 pub entry_hash_hex: String,
818 pub sql: String,
819}
820
821pub type AuditChainProvider = fn() -> Vec<AuditRow>;
826pub type AuditVerifier = fn() -> (i64, i64);
827
828impl Engine {
829 pub fn new() -> Self {
830 Self {
831 catalog: Catalog::new(),
832 tx_catalogs: BTreeMap::new(),
833 current_tx: None,
834 next_tx_id: 1,
835 clock: None,
836 salt_fn: None,
837 max_query_rows: None,
838 users: UserStore::new(),
839 publications: publications::Publications::new(),
840 subscriptions: subscriptions::Subscriptions::new(),
841 statistics: statistics::Statistics::new(),
842 plan_cache: plan_cache::PlanCache::new(),
843 query_stats: query_stats::QueryStats::new(),
844 activity_provider: None,
845 audit_chain_provider: None,
846 audit_verifier: None,
847 slow_query_threshold_us: None,
848 slow_query_logger: None,
849 session_params: BTreeMap::new(),
850 trigger_recursion_depth: 0,
851 foreign_key_checks: true,
852 meta_views_materialised: false,
853 pending_foreign_keys: Vec::new(),
854 }
855 }
856
857 #[must_use]
866 pub fn clone_snapshot(&self) -> CatalogSnapshot {
867 CatalogSnapshot {
868 catalog: self.active_catalog().clone(),
869 statistics: self.statistics.clone(),
870 clock: self.clock,
871 max_query_rows: self.max_query_rows,
872 }
873 }
874
875 pub fn execute_readonly_on_snapshot(
883 snapshot: &CatalogSnapshot,
884 sql: &str,
885 ) -> Result<QueryResult, EngineError> {
886 Self::execute_readonly_on_snapshot_with_cancel(snapshot, sql, CancelToken::none())
887 }
888
889 pub fn execute_readonly_on_snapshot_with_cancel(
896 snapshot: &CatalogSnapshot,
897 sql: &str,
898 cancel: CancelToken<'_>,
899 ) -> Result<QueryResult, EngineError> {
900 let transient = Engine {
901 catalog: snapshot.catalog.clone(),
902 statistics: snapshot.statistics.clone(),
903 clock: snapshot.clock,
904 max_query_rows: snapshot.max_query_rows,
905 ..Engine::default()
906 };
907 transient.execute_readonly_with_cancel(sql, cancel)
908 }
909
910 pub fn execute_readonly_prepared_on_snapshot(
926 snapshot: &CatalogSnapshot,
927 stmt: Statement,
928 params: &[Value],
929 ) -> Result<QueryResult, EngineError> {
930 Self::execute_readonly_prepared_on_snapshot_with_cancel(
931 snapshot,
932 stmt,
933 params,
934 CancelToken::none(),
935 )
936 }
937
938 pub fn execute_readonly_prepared_on_snapshot_with_cancel(
941 snapshot: &CatalogSnapshot,
942 mut stmt: Statement,
943 params: &[Value],
944 cancel: CancelToken<'_>,
945 ) -> Result<QueryResult, EngineError> {
946 cancel.check()?;
947 substitute_placeholders(&mut stmt, params)?;
948 let transient = Engine {
949 catalog: snapshot.catalog.clone(),
950 statistics: snapshot.statistics.clone(),
951 clock: snapshot.clock,
952 max_query_rows: snapshot.max_query_rows,
953 ..Engine::default()
954 };
955 transient.execute_readonly_stmt_with_cancel(stmt, cancel)
956 }
957
958 pub fn describe_prepared_on_snapshot(
964 snapshot: &CatalogSnapshot,
965 stmt: &Statement,
966 ) -> (Vec<u32>, Vec<ColumnSchema>) {
967 describe::describe_prepared(stmt, &snapshot.catalog)
968 }
969
970 #[must_use]
978 pub fn is_readonly_sql(sql: &str) -> bool {
979 parser::parse_statement(sql)
980 .as_ref()
981 .map(spg_sql::ast::Statement::is_readonly)
982 .unwrap_or(false)
983 }
984
985 pub fn prepare_on_snapshot(
1000 snapshot: &CatalogSnapshot,
1001 sql: &str,
1002 ) -> Result<Statement, ParseError> {
1003 let mut stmt = parser::parse_statement(sql)?;
1004 let now_micros = snapshot.clock.map(|f| f());
1005 rewrite_clock_calls(&mut stmt, now_micros);
1006 if let Statement::Select(s) = &mut stmt {
1007 expand_group_by_all(s);
1008 resolve_order_by_position(s);
1009 reorder::reorder_joins(s, &snapshot.catalog, &snapshot.statistics);
1010 }
1011 Ok(stmt)
1012 }
1013
1014 pub fn restore(catalog: Catalog) -> Self {
1017 Self {
1018 catalog,
1019 tx_catalogs: BTreeMap::new(),
1020 current_tx: None,
1021 next_tx_id: 1,
1022 clock: None,
1023 salt_fn: None,
1024 max_query_rows: None,
1025 users: UserStore::new(),
1026 publications: publications::Publications::new(),
1027 subscriptions: subscriptions::Subscriptions::new(),
1028 statistics: statistics::Statistics::new(),
1029 plan_cache: plan_cache::PlanCache::new(),
1030 query_stats: query_stats::QueryStats::new(),
1031 activity_provider: None,
1032 audit_chain_provider: None,
1033 audit_verifier: None,
1034 slow_query_threshold_us: None,
1035 slow_query_logger: None,
1036 session_params: BTreeMap::new(),
1037 trigger_recursion_depth: 0,
1038 foreign_key_checks: true,
1039 meta_views_materialised: false,
1040 pending_foreign_keys: Vec::new(),
1041 }
1042 }
1043
1044 pub fn restore_envelope(buf: &[u8]) -> Result<Self, EngineError> {
1051 match split_envelope(buf) {
1052 EnvelopeParse::Pair {
1053 catalog: catalog_bytes,
1054 users: user_bytes,
1055 publications: pub_bytes,
1056 subscriptions: sub_bytes,
1057 statistics: stats_bytes,
1058 } => {
1059 let catalog = Catalog::deserialize(catalog_bytes).map_err(EngineError::Storage)?;
1060 let users = users::deserialize_users(user_bytes)
1061 .map_err(|e| EngineError::Unsupported(alloc::format!("users restore: {e}")))?;
1062 let publications = match pub_bytes {
1063 Some(b) => publications::Publications::deserialize(b).map_err(|e| {
1064 EngineError::Unsupported(alloc::format!("publications restore: {e:?}"))
1065 })?,
1066 None => publications::Publications::new(),
1067 };
1068 let subscriptions = match sub_bytes {
1069 Some(b) => subscriptions::Subscriptions::deserialize(b).map_err(|e| {
1070 EngineError::Unsupported(alloc::format!("subscriptions restore: {e:?}"))
1071 })?,
1072 None => subscriptions::Subscriptions::new(),
1073 };
1074 let statistics = match stats_bytes {
1075 Some(b) => statistics::Statistics::deserialize(b).map_err(|e| {
1076 EngineError::Unsupported(alloc::format!("statistics restore: {e:?}"))
1077 })?,
1078 None => statistics::Statistics::new(),
1079 };
1080 Ok(Self {
1081 catalog,
1082 tx_catalogs: BTreeMap::new(),
1083 current_tx: None,
1084 next_tx_id: 1,
1085 clock: None,
1086 salt_fn: None,
1087 max_query_rows: None,
1088 users,
1089 publications,
1090 subscriptions,
1091 statistics,
1092 plan_cache: plan_cache::PlanCache::new(),
1093 query_stats: query_stats::QueryStats::new(),
1094 activity_provider: None,
1095 audit_chain_provider: None,
1096 audit_verifier: None,
1097 slow_query_threshold_us: None,
1098 slow_query_logger: None,
1099 session_params: BTreeMap::new(),
1100 trigger_recursion_depth: 0,
1101 foreign_key_checks: true,
1102 meta_views_materialised: false,
1103 pending_foreign_keys: Vec::new(),
1104 })
1105 }
1106 EnvelopeParse::CrcMismatch { expected, computed } => {
1107 Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1108 "snapshot envelope CRC32 mismatch (expected={expected:#010x}, computed={computed:#010x})"
1109 ))))
1110 }
1111 EnvelopeParse::Bare => {
1112 let catalog = Catalog::deserialize(buf).map_err(EngineError::Storage)?;
1113 Ok(Self::restore(catalog))
1114 }
1115 }
1116 }
1117
1118 pub const fn users(&self) -> &UserStore {
1119 &self.users
1120 }
1121
1122 pub fn create_user(
1126 &mut self,
1127 name: &str,
1128 password: &str,
1129 role: Role,
1130 salt: [u8; 16],
1131 ) -> Result<(), UserError> {
1132 self.users.create(name, password, role, salt)?;
1133 let scram_salt = self.salt_fn.map_or_else(
1139 || {
1140 let mut s = [0u8; users::SCRAM_SALT_LEN];
1141 let digest = spg_crypto::hash(name.as_bytes());
1142 s.copy_from_slice(&digest[16..32]);
1145 s
1146 },
1147 |f| f(),
1148 );
1149 self.users
1150 .enable_scram(name, password, scram_salt, users::SCRAM_DEFAULT_ITERS)?;
1151 Ok(())
1152 }
1153
1154 pub fn drop_user(&mut self, name: &str) -> Result<(), UserError> {
1155 self.users.drop(name)
1156 }
1157
1158 pub fn verify_user(&self, name: &str, password: &str) -> Option<Role> {
1159 self.users.verify(name, password)
1160 }
1161
1162 #[must_use]
1165 pub const fn with_clock(mut self, clock: ClockFn) -> Self {
1166 self.clock = Some(clock);
1167 self
1168 }
1169
1170 #[must_use]
1173 pub const fn with_salt_fn(mut self, f: SaltFn) -> Self {
1174 self.salt_fn = Some(f);
1175 self
1176 }
1177
1178 #[must_use]
1184 pub const fn with_max_query_rows(mut self, n: usize) -> Self {
1185 self.max_query_rows = Some(n);
1186 self
1187 }
1188
1189 pub const fn catalog(&self) -> &Catalog {
1193 &self.catalog
1194 }
1195
1196 pub fn snapshot(&self) -> Vec<u8> {
1204 if self.users.is_empty()
1205 && self.publications.is_empty()
1206 && self.subscriptions.is_empty()
1207 && self.statistics.is_empty()
1208 {
1209 self.catalog.serialize()
1210 } else {
1211 build_envelope(
1212 &self.catalog.serialize(),
1213 &users::serialize_users(&self.users),
1214 &self.publications.serialize(),
1215 &self.subscriptions.serialize(),
1216 &self.statistics.serialize(),
1217 )
1218 }
1219 }
1220
1221 pub fn in_transaction(&self) -> bool {
1226 !self.tx_catalogs.is_empty()
1227 }
1228
1229 pub fn alloc_tx_id(&mut self) -> TxId {
1238 let id = TxId(self.next_tx_id);
1239 self.next_tx_id = self.next_tx_id.saturating_add(1);
1240 id
1241 }
1242
1243 pub fn replace_catalog(&mut self, catalog: Catalog) {
1263 self.catalog = catalog;
1264 }
1265
1266 pub fn freeze_oldest_to_cold(
1274 &mut self,
1275 table_name: &str,
1276 index_name: &str,
1277 max_rows: usize,
1278 ) -> Result<spg_storage::FreezeReport, EngineError> {
1279 let report = self
1280 .active_catalog_mut()
1281 .freeze_oldest_to_cold(table_name, index_name, max_rows)
1282 .map_err(EngineError::Storage)?;
1283 if let Some(t) = self.active_catalog_mut().get_mut(table_name) {
1284 t.mark_cold_row_count_stale();
1285 }
1286 Ok(report)
1287 }
1288
1289 pub fn receive_cold_segment(
1303 &mut self,
1304 segment_id: u32,
1305 bytes: Vec<u8>,
1306 ) -> Result<(), EngineError> {
1307 let mut new_cat = self.catalog.clone();
1308 match new_cat.load_segment_bytes_at(segment_id, bytes) {
1309 Ok(()) => {
1310 self.replace_catalog(new_cat);
1311 Ok(())
1312 }
1313 Err(StorageError::Corrupt(msg)) if msg.contains("already occupied") => Ok(()),
1314 Err(e) => Err(EngineError::Storage(e)),
1315 }
1316 }
1317
1318 pub fn compact_cold_segments_with_target(
1332 &mut self,
1333 target_segment_bytes: u64,
1334 ) -> Result<Vec<(String, String, CompactReport)>, EngineError> {
1335 let table_names = self.active_catalog().table_names();
1336 let mut reports: Vec<(String, String, CompactReport)> = Vec::new();
1337 for tname in table_names {
1338 if is_internal_table_name(&tname) {
1339 continue;
1340 }
1341 let idx_names: Vec<String> = {
1342 let Some(t) = self.active_catalog().get(&tname) else {
1343 continue;
1344 };
1345 t.indices()
1346 .iter()
1347 .filter(|i| matches!(i.kind, IndexKind::BTree(_)))
1348 .map(|i| i.name.clone())
1349 .collect()
1350 };
1351 for iname in idx_names {
1352 let report = self
1353 .active_catalog_mut()
1354 .compact_cold_segments(&tname, &iname, target_segment_bytes)
1355 .map_err(EngineError::Storage)?;
1356 if report.merged_segment_id.is_some() {
1357 if let Some(t) = self.active_catalog_mut().get_mut(&tname) {
1358 t.mark_cold_row_count_stale();
1359 }
1360 reports.push((tname.clone(), iname, report));
1361 }
1362 }
1363 }
1364 Ok(reports)
1365 }
1366
1367 fn active_catalog(&self) -> &Catalog {
1368 match self.current_tx {
1369 Some(t) => self
1370 .tx_catalogs
1371 .get(&t)
1372 .map_or(&self.catalog, |s| &s.catalog),
1373 None => &self.catalog,
1374 }
1375 }
1376
1377 fn resolve_plpgsql_block_subqueries(
1399 &self,
1400 block: &mut spg_sql::ast::PlPgSqlBlock,
1401 cancel: CancelToken<'_>,
1402 ) -> Result<(), EngineError> {
1403 for d in &mut block.declarations {
1404 if let Some(e) = &mut d.default {
1405 self.resolve_expr_subqueries(e, cancel)?;
1406 }
1407 }
1408 self.resolve_plpgsql_stmts_subqueries(&mut block.statements, cancel)
1409 }
1410
1411 fn resolve_plpgsql_stmts_subqueries(
1412 &self,
1413 stmts: &mut [spg_sql::ast::PlPgSqlStmt],
1414 cancel: CancelToken<'_>,
1415 ) -> Result<(), EngineError> {
1416 use spg_sql::ast::PlPgSqlStmt;
1417 for stmt in stmts {
1418 match stmt {
1419 PlPgSqlStmt::Assign { value, .. } => {
1420 self.resolve_expr_subqueries(value, cancel)?;
1421 }
1422 PlPgSqlStmt::Return(spg_sql::ast::ReturnTarget::Expr(e)) => {
1423 self.resolve_expr_subqueries(e, cancel)?;
1424 }
1425 PlPgSqlStmt::Return(_) => {}
1426 PlPgSqlStmt::If {
1427 branches,
1428 else_branch,
1429 } => {
1430 for (cond, body) in branches.iter_mut() {
1431 self.resolve_expr_subqueries(cond, cancel)?;
1432 self.resolve_plpgsql_stmts_subqueries(body, cancel)?;
1433 }
1434 self.resolve_plpgsql_stmts_subqueries(else_branch, cancel)?;
1435 }
1436 PlPgSqlStmt::Raise { args, .. } => {
1437 for a in args {
1438 self.resolve_expr_subqueries(a, cancel)?;
1439 }
1440 }
1441 PlPgSqlStmt::EmbeddedSql(_) => {
1442 }
1446 PlPgSqlStmt::SelectInto { body, .. } => {
1447 self.resolve_select_subqueries(body, cancel)?;
1453 }
1454 }
1455 }
1456 Ok(())
1457 }
1458
1459 fn exec_do_block(
1460 &mut self,
1461 body: spg_sql::ast::PlPgSqlBlock,
1462 ) -> Result<QueryResult, EngineError> {
1463 let mut body = body;
1472 self.resolve_plpgsql_block_subqueries(&mut body, CancelToken::none())?;
1473 let dts = self
1474 .session_param("default_text_search_config")
1475 .map(String::from);
1476 let engine_cell = core::cell::RefCell::new(&mut *self);
1489 let resolver_fn =
1490 |stmt: &spg_sql::ast::Statement| -> Result<Value, triggers::TriggerError> {
1491 let mut eng = engine_cell.borrow_mut();
1492 let r = eng
1493 .execute_stmt_with_cancel(stmt.clone(), CancelToken::none())
1494 .map_err(|e| triggers::TriggerError::EvalFailed {
1495 function: "DO".into(),
1496 cause: eval::EvalError::TypeMismatch {
1497 detail: alloc::format!("SELECT … INTO failed: {e}"),
1498 },
1499 })?;
1500 match r {
1501 QueryResult::Rows { rows, .. } => match rows.into_iter().next() {
1502 Some(row) => Ok(row.values.into_iter().next().unwrap_or(Value::Null)),
1503 None => Ok(Value::Null),
1504 },
1505 _ => Err(triggers::TriggerError::EvalFailed {
1506 function: "DO".into(),
1507 cause: eval::EvalError::TypeMismatch {
1508 detail: "SELECT … INTO body must be a SELECT".into(),
1509 },
1510 }),
1511 }
1512 };
1513 let collected =
1514 triggers::execute_do_block_top_level(&body, dts.as_deref(), Some(&resolver_fn))
1515 .map_err(|e| {
1516 EngineError::Storage(StorageError::Corrupt(alloc::format!("DO: {e}")))
1517 })?;
1518 for stmt in collected {
1524 self.execute_stmt_with_cancel(stmt, CancelToken::none())?;
1528 }
1529 Ok(QueryResult::CommandOk {
1530 affected: 0,
1531 modified_catalog: !self.in_transaction(),
1532 })
1533 }
1534
1535 fn snapshot_row_triggers(
1536 &self,
1537 table: &str,
1538 event: &str,
1539 timing: &str,
1540 ) -> Vec<spg_storage::FunctionDef> {
1541 let cat = self.active_catalog();
1542 cat.triggers()
1543 .iter()
1544 .filter(|t| {
1545 t.enabled
1548 && t.table == table
1549 && t.timing.eq_ignore_ascii_case(timing)
1550 && t.for_each.eq_ignore_ascii_case("row")
1551 && t.events.iter().any(|e| e.eq_ignore_ascii_case(event))
1552 })
1553 .filter_map(|t| cat.functions().get(&t.function).cloned())
1554 .collect()
1555 }
1556
1557 fn snapshot_update_row_triggers(
1562 &self,
1563 table: &str,
1564 timing: &str,
1565 ) -> Vec<(spg_storage::FunctionDef, Vec<String>)> {
1566 let cat = self.active_catalog();
1567 cat.triggers()
1568 .iter()
1569 .filter(|t| {
1570 t.enabled
1572 && t.table == table
1573 && t.timing.eq_ignore_ascii_case(timing)
1574 && t.for_each.eq_ignore_ascii_case("row")
1575 && t.events.iter().any(|e| e.eq_ignore_ascii_case("UPDATE"))
1576 })
1577 .filter_map(|t| {
1578 cat.functions()
1579 .get(&t.function)
1580 .cloned()
1581 .map(|fd| (fd, t.update_columns.clone()))
1582 })
1583 .collect()
1584 }
1585
1586 fn execute_deferred_trigger_stmts(
1595 &mut self,
1596 deferred: Vec<triggers::DeferredEmbeddedStmt>,
1597 cancel: CancelToken<'_>,
1598 ) -> Result<(), EngineError> {
1599 for d in deferred {
1600 if self.trigger_recursion_depth >= MAX_TRIGGER_RECURSION {
1601 return Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1602 "trigger embedded SQL recursion depth {} exceeded (trigger function \
1603 {:?} would push past the {} cap — check for trigger cycles)",
1604 self.trigger_recursion_depth,
1605 d.function,
1606 MAX_TRIGGER_RECURSION,
1607 ))));
1608 }
1609 self.trigger_recursion_depth += 1;
1610 let res = self.execute_stmt_with_cancel(d.stmt, cancel);
1611 self.trigger_recursion_depth -= 1;
1612 res?;
1613 }
1614 Ok(())
1615 }
1616
1617 fn active_catalog_mut(&mut self) -> &mut Catalog {
1618 let tx = self.current_tx;
1619 match tx {
1620 Some(t) => match self.tx_catalogs.get_mut(&t) {
1621 Some(s) => &mut s.catalog,
1622 None => &mut self.catalog,
1623 },
1624 None => &mut self.catalog,
1625 }
1626 }
1627
1628 pub fn execute_readonly(&self, sql: &str) -> Result<QueryResult, EngineError> {
1640 self.execute_readonly_with_cancel(sql, CancelToken::none())
1641 }
1642
1643 pub fn execute_readonly_with_cancel(
1649 &self,
1650 sql: &str,
1651 cancel: CancelToken<'_>,
1652 ) -> Result<QueryResult, EngineError> {
1653 cancel.check()?;
1654 let mut stmt = parser::parse_statement(sql)?;
1655 let now_micros = self.clock.map(|f| f());
1656 rewrite_clock_calls(&mut stmt, now_micros);
1657 if let Statement::Select(s) = &mut stmt {
1658 resolve_order_by_position(s);
1659 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1661 }
1662 self.execute_readonly_stmt_with_cancel(stmt, cancel)
1663 }
1664
1665 fn execute_readonly_stmt_with_cancel(
1675 &self,
1676 stmt: Statement,
1677 cancel: CancelToken<'_>,
1678 ) -> Result<QueryResult, EngineError> {
1679 let result = match stmt {
1680 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1681 Statement::ShowTables => Ok(self.exec_show_tables()),
1682 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1683 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1684 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1685 Statement::ShowStatus => Ok(self.exec_show_status()),
1686 Statement::ShowVariables => Ok(self.exec_show_variables()),
1687 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1688 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1689 Statement::ShowUsers => Ok(self.exec_show_users()),
1690 Statement::ShowPublications => Ok(self.exec_show_publications()),
1691 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1692 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1693 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1694 )),
1695 Statement::Explain(e) => self.exec_explain(&e, cancel),
1696 _ => Err(EngineError::WriteRequired),
1697 };
1698 self.enforce_row_limit(result)
1699 }
1700
1701 fn enforce_row_limit(
1705 &self,
1706 result: Result<QueryResult, EngineError>,
1707 ) -> Result<QueryResult, EngineError> {
1708 if let (Ok(QueryResult::Rows { rows, .. }), Some(cap)) = (&result, self.max_query_rows)
1709 && rows.len() > cap
1710 {
1711 return Err(EngineError::RowLimitExceeded(cap));
1712 }
1713 result
1714 }
1715
1716 pub fn execute(&mut self, sql: &str) -> Result<QueryResult, EngineError> {
1717 self.execute_in_with_cancel(sql, IMPLICIT_TX, CancelToken::none())
1718 }
1719
1720 pub fn execute_with_cancel(
1725 &mut self,
1726 sql: &str,
1727 cancel: CancelToken<'_>,
1728 ) -> Result<QueryResult, EngineError> {
1729 self.execute_in_with_cancel(sql, IMPLICIT_TX, cancel)
1730 }
1731
1732 pub fn execute_in(&mut self, sql: &str, tx_id: TxId) -> Result<QueryResult, EngineError> {
1739 self.execute_in_with_cancel(sql, tx_id, CancelToken::none())
1740 }
1741
1742 pub fn execute_in_with_cancel(
1748 &mut self,
1749 sql: &str,
1750 tx_id: TxId,
1751 cancel: CancelToken<'_>,
1752 ) -> Result<QueryResult, EngineError> {
1753 let saved = self.current_tx;
1754 self.current_tx = Some(tx_id);
1755 let result = self.execute_inner_with_cancel(sql, cancel);
1756 self.current_tx = saved;
1757 result
1758 }
1759
1760 pub fn prepare(&self, sql: &str) -> Result<Statement, ParseError> {
1772 let mut stmt = parser::parse_statement(sql)?;
1773 let now_micros = self.clock.map(|f| f());
1774 rewrite_clock_calls(&mut stmt, now_micros);
1775 if let Statement::Select(s) = &mut stmt {
1776 expand_group_by_all(s);
1780 resolve_order_by_position(s);
1781 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1784 }
1785 Ok(stmt)
1786 }
1787
1788 pub fn prepare_cached(&mut self, sql: &str) -> Result<Statement, ParseError> {
1800 let current_version = self.statistics.version();
1803 if let Some(plan) = self.plan_cache.get(sql) {
1804 if plan.statistics_version == current_version {
1805 return Ok(plan.stmt.clone());
1806 }
1807 }
1809 self.plan_cache.evict(sql);
1810 let stmt = self.prepare(sql)?;
1811 let source_tables = plan_cache::collect_source_tables(&stmt);
1812 let plan = plan_cache::PreparedPlan {
1813 stmt: stmt.clone(),
1814 statistics_version: current_version,
1815 source_tables,
1816 describe_columns: alloc::vec::Vec::new(),
1817 };
1818 self.plan_cache.insert(String::from(sql), plan);
1819 Ok(stmt)
1820 }
1821
1822 pub fn plan_cache(&self) -> &plan_cache::PlanCache {
1824 &self.plan_cache
1825 }
1826
1827 pub fn plan_cache_mut(&mut self) -> &mut plan_cache::PlanCache {
1829 &mut self.plan_cache
1830 }
1831
1832 pub fn describe_prepared(&self, stmt: &Statement) -> (Vec<u32>, Vec<ColumnSchema>) {
1838 describe::describe_prepared(stmt, self.active_catalog())
1839 }
1840
1841 pub fn execute_prepared(
1851 &mut self,
1852 stmt: Statement,
1853 params: &[Value],
1854 ) -> Result<QueryResult, EngineError> {
1855 self.execute_prepared_with_cancel(stmt, params, CancelToken::none())
1856 }
1857
1858 pub fn execute_prepared_with_cancel(
1863 &mut self,
1864 mut stmt: Statement,
1865 params: &[Value],
1866 cancel: CancelToken<'_>,
1867 ) -> Result<QueryResult, EngineError> {
1868 substitute_placeholders(&mut stmt, params)?;
1869 let saved = self.current_tx;
1880 self.current_tx = Some(IMPLICIT_TX);
1881 let result = self.execute_stmt_with_cancel(stmt, cancel);
1882 self.current_tx = saved;
1883 result
1884 }
1885
1886 fn execute_inner_with_cancel(
1887 &mut self,
1888 sql: &str,
1889 cancel: CancelToken<'_>,
1890 ) -> Result<QueryResult, EngineError> {
1891 cancel.check()?;
1892 let stmt = self.prepare(sql)?;
1893 let start_us = self.clock.map(|f| f());
1897 let result = self.execute_stmt_with_cancel(stmt, cancel);
1898 if let (Some(t0), Ok(_)) = (start_us, &result) {
1899 let now = self.clock.map_or(t0, |f| f());
1900 let elapsed = now.saturating_sub(t0).max(0) as u64;
1901 self.query_stats.record(sql, elapsed, now as u64);
1902 if let (Some(threshold), Some(logger)) =
1905 (self.slow_query_threshold_us, self.slow_query_logger)
1906 && elapsed >= threshold
1907 {
1908 logger(sql, elapsed);
1909 }
1910 }
1911 result
1912 }
1913
1914 fn execute_stmt_with_cancel(
1915 &mut self,
1916 stmt: Statement,
1917 cancel: CancelToken<'_>,
1918 ) -> Result<QueryResult, EngineError> {
1919 cancel.check()?;
1920 let mut stmt = stmt;
1934 self.pre_resolve_sequence_calls_in_statement(&mut stmt)?;
1943 let result = match stmt {
1944 Statement::CreateTable(s) => self.exec_create_table(s),
1945 Statement::CreateExtension(_) => Ok(QueryResult::CommandOk {
1949 affected: 0,
1950 modified_catalog: false,
1951 }),
1952 Statement::DoBlock(body) => self.exec_do_block(body),
1962 Statement::Empty => Ok(QueryResult::CommandOk {
1966 affected: 0,
1967 modified_catalog: false,
1968 }),
1969 Statement::DropTable { names, if_exists } => self.exec_drop_table(names, if_exists),
1970 Statement::DropIndex { name, if_exists } => self.exec_drop_index(name, if_exists),
1971 Statement::CreateIndex(s) => self.exec_create_index(s),
1972 Statement::Insert(s) => self.exec_insert(s),
1973 Statement::Update(s) => self.exec_update_cancel(&s, cancel),
1974 Statement::Delete(s) => self.exec_delete_cancel(&s, cancel),
1975 Statement::Merge(s) => self.exec_merge_cancel(&s, cancel),
1976 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1977 Statement::Begin => self.exec_begin(),
1978 Statement::Commit => self.exec_commit(),
1979 Statement::Rollback => self.exec_rollback(),
1980 Statement::Savepoint(name) => self.exec_savepoint(name),
1981 Statement::RollbackToSavepoint(name) => self.exec_rollback_to_savepoint(&name),
1982 Statement::ReleaseSavepoint(name) => self.exec_release_savepoint(&name),
1983 Statement::ShowTables => Ok(self.exec_show_tables()),
1984 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1985 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1986 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1987 Statement::ShowStatus => Ok(self.exec_show_status()),
1988 Statement::ShowVariables => Ok(self.exec_show_variables()),
1989 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1990 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1991 Statement::ShowUsers => Ok(self.exec_show_users()),
1992 Statement::ShowPublications => Ok(self.exec_show_publications()),
1993 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1994 Statement::CreateUser(s) => self.exec_create_user(&s),
1995 Statement::DropUser(name) => self.exec_drop_user(&name),
1996 Statement::Explain(e) => self.exec_explain(&e, cancel),
1997 Statement::AlterIndex(s) => self.exec_alter_index(s),
1998 Statement::AlterTable(s) => self.exec_alter_table(s),
1999 Statement::CreatePublication(s) => self.exec_create_publication(s),
2000 Statement::DropPublication(name) => self.exec_drop_publication(&name),
2001 Statement::CreateSubscription(s) => self.exec_create_subscription(s),
2002 Statement::DropSubscription(name) => self.exec_drop_subscription(&name),
2003 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
2010 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
2011 )),
2012 Statement::Analyze(target) => self.exec_analyze(target.as_deref()),
2014 Statement::CompactColdSegments => self.exec_compact_cold_segments(),
2016 Statement::SetParameter { name, value } => {
2021 self.set_session_param(name, value);
2022 Ok(QueryResult::CommandOk {
2023 affected: 0,
2024 modified_catalog: false,
2025 })
2026 }
2027 Statement::SetParameterList(pairs) => {
2033 for (name, value) in pairs {
2034 self.set_session_param(name, value);
2035 }
2036 Ok(QueryResult::CommandOk {
2037 affected: 0,
2038 modified_catalog: false,
2039 })
2040 }
2041 Statement::CreateFunction(s) => self.exec_create_function(s),
2045 Statement::CreateTrigger(s) => self.exec_create_trigger(s),
2046 Statement::DropTrigger {
2047 name,
2048 table,
2049 if_exists,
2050 } => self.exec_drop_trigger(&name, &table, if_exists),
2051 Statement::DropFunction { name, if_exists } => {
2052 self.exec_drop_function(&name, if_exists)
2053 }
2054 Statement::CreateSequence(s) => self.exec_create_sequence(s),
2055 Statement::AlterSequence(s) => self.exec_alter_sequence(s),
2056 Statement::DropSequence { names, if_exists } => {
2057 self.exec_drop_sequence(&names, if_exists)
2058 }
2059 Statement::CreateView(s) => self.exec_create_view(s),
2060 Statement::DropView { names, if_exists } => self.exec_drop_view(&names, if_exists),
2061 Statement::CreateMaterializedView(s) => self.exec_create_materialized_view(s),
2062 Statement::RefreshMaterializedView { name, with_data } => {
2063 self.exec_refresh_materialized_view(&name, with_data)
2064 }
2065 Statement::DropMaterializedView { names, if_exists } => {
2066 self.exec_drop_materialized_view(&names, if_exists)
2067 }
2068 Statement::CreateType(s) => self.exec_create_type(s),
2069 Statement::DropType { names, if_exists } => self.exec_drop_type(&names, if_exists),
2070 Statement::CreateDomain(s) => self.exec_create_domain(s),
2071 Statement::DropDomain { names, if_exists } => self.exec_drop_domain(&names, if_exists),
2072 Statement::CreateSchema {
2073 name,
2074 if_not_exists,
2075 } => self.exec_create_schema(name, if_not_exists),
2076 Statement::DropSchema { names, if_exists } => self.exec_drop_schema(&names, if_exists),
2077 Statement::ResetParameter(target) => {
2078 match target {
2079 None => self.session_params.clear(),
2080 Some(name) => {
2081 self.session_params.remove(&name.to_ascii_lowercase());
2082 }
2083 }
2084 Ok(QueryResult::CommandOk {
2085 affected: 0,
2086 modified_catalog: false,
2087 })
2088 }
2089 };
2090 self.enforce_row_limit(result)
2091 }
2092
2093 fn exec_create_publication(
2101 &mut self,
2102 s: CreatePublicationStatement,
2103 ) -> Result<QueryResult, EngineError> {
2104 self.publications
2110 .create(s.name, s.scope)
2111 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE PUBLICATION: {e:?}")))?;
2112 Ok(QueryResult::CommandOk {
2113 affected: 1,
2114 modified_catalog: true,
2115 })
2116 }
2117
2118 fn exec_drop_publication(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2123 let removed = self.publications.drop(name);
2124 Ok(QueryResult::CommandOk {
2125 affected: usize::from(removed),
2126 modified_catalog: removed,
2127 })
2128 }
2129
2130 pub const fn publications(&self) -> &publications::Publications {
2135 &self.publications
2136 }
2137
2138 fn exec_create_subscription(
2143 &mut self,
2144 s: CreateSubscriptionStatement,
2145 ) -> Result<QueryResult, EngineError> {
2146 let sub = subscriptions::Subscription {
2150 conn_str: s.conn_str,
2151 publications: s.publications,
2152 enabled: true,
2153 last_received_pos: 0,
2154 };
2155 self.subscriptions
2156 .create(s.name, sub)
2157 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE SUBSCRIPTION: {e:?}")))?;
2158 Ok(QueryResult::CommandOk {
2159 affected: 1,
2160 modified_catalog: true,
2161 })
2162 }
2163
2164 fn exec_drop_subscription(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2172 let removed = self.subscriptions.drop(name);
2173 Ok(QueryResult::CommandOk {
2174 affected: usize::from(removed),
2175 modified_catalog: removed,
2176 })
2177 }
2178
2179 pub const fn subscriptions(&self) -> &subscriptions::Subscriptions {
2184 &self.subscriptions
2185 }
2186
2187 pub fn subscription_advance(&mut self, name: &str, pos: u64) -> bool {
2193 self.subscriptions.update_last_received_pos(name, pos)
2194 }
2195
2196 fn exec_show_subscriptions(&self) -> QueryResult {
2202 let columns = alloc::vec![
2203 ColumnSchema::new("name", DataType::Text, false),
2204 ColumnSchema::new("conn_str", DataType::Text, false),
2205 ColumnSchema::new("publications", DataType::Text, false),
2206 ColumnSchema::new("enabled", DataType::Bool, false),
2207 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2208 ];
2209 let rows: Vec<Row> = self
2210 .subscriptions
2211 .iter()
2212 .map(|(name, sub)| {
2213 Row::new(alloc::vec![
2214 Value::Text(name.clone()),
2215 Value::Text(sub.conn_str.clone()),
2216 Value::Text(sub.publications.join(", ")),
2217 Value::Bool(sub.enabled),
2218 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2219 ])
2220 })
2221 .collect();
2222 QueryResult::Rows { columns, rows }
2223 }
2224
2225 fn exec_spg_statistic(&self) -> QueryResult {
2230 let columns = alloc::vec![
2231 ColumnSchema::new("table_name", DataType::Text, false),
2232 ColumnSchema::new("column_name", DataType::Text, false),
2233 ColumnSchema::new("null_frac", DataType::Float, false),
2234 ColumnSchema::new("n_distinct", DataType::BigInt, false),
2235 ColumnSchema::new("histogram_bounds", DataType::Text, false),
2236 ColumnSchema::new("cold_row_count", DataType::BigInt, false),
2241 ];
2242 let rows: Vec<Row> = self
2243 .statistics
2244 .iter()
2245 .map(|((t, c), s)| {
2246 let cold = self
2247 .catalog
2248 .get(t)
2249 .map_or(0, |table| table.cold_row_count());
2250 Row::new(alloc::vec![
2251 Value::Text(t.clone()),
2252 Value::Text(c.clone()),
2253 Value::Float(f64::from(s.null_frac)),
2254 Value::BigInt(i64::try_from(s.n_distinct).unwrap_or(i64::MAX)),
2255 Value::Text(render_histogram_bounds(&s.histogram_bounds)),
2256 Value::BigInt(i64::try_from(cold).unwrap_or(i64::MAX)),
2257 ])
2258 })
2259 .collect();
2260 QueryResult::Rows { columns, rows }
2261 }
2262
2263 fn exec_spg_stat_replication(&self) -> QueryResult {
2270 let columns = alloc::vec![
2271 ColumnSchema::new("name", DataType::Text, false),
2272 ColumnSchema::new("conn_str", DataType::Text, false),
2273 ColumnSchema::new("publications", DataType::Text, false),
2274 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2275 ColumnSchema::new("enabled", DataType::Bool, false),
2276 ];
2277 let rows: Vec<Row> = self
2278 .subscriptions
2279 .iter()
2280 .map(|(name, sub)| {
2281 Row::new(alloc::vec![
2282 Value::Text(name.clone()),
2283 Value::Text(sub.conn_str.clone()),
2284 Value::Text(sub.publications.join(",")),
2285 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2286 Value::Bool(sub.enabled),
2287 ])
2288 })
2289 .collect();
2290 QueryResult::Rows { columns, rows }
2291 }
2292
2293 fn exec_spg_stat_segment(&self) -> QueryResult {
2305 let columns = alloc::vec![
2306 ColumnSchema::new("segment_id", DataType::BigInt, false),
2307 ColumnSchema::new("table_name", DataType::Text, false),
2308 ColumnSchema::new("num_rows", DataType::BigInt, false),
2309 ColumnSchema::new("num_pages", DataType::BigInt, false),
2310 ColumnSchema::new("total_bytes", DataType::BigInt, false),
2311 ];
2312 let mut segment_owners: alloc::collections::BTreeMap<u32, String> = BTreeMap::new();
2318 for tname in self.catalog.table_names() {
2319 if is_internal_table_name(&tname) {
2320 continue;
2321 }
2322 let Some(t) = self.catalog.get(&tname) else {
2323 continue;
2324 };
2325 for idx in t.indices() {
2326 if let spg_storage::IndexKind::BTree(map) = &idx.kind {
2327 for (_, locs) in map.iter() {
2328 for loc in locs {
2329 if let spg_storage::RowLocator::Cold { segment_id, .. } = loc {
2330 segment_owners
2331 .entry(*segment_id)
2332 .or_insert_with(|| tname.clone());
2333 }
2334 }
2335 }
2336 }
2337 }
2338 }
2339 let rows: Vec<Row> = self
2340 .catalog
2341 .cold_segment_ids_global()
2342 .iter()
2343 .filter_map(|&id| {
2344 let seg = self.catalog.cold_segment(id)?;
2345 let meta = seg.meta();
2346 let owner = segment_owners.get(&id).cloned().unwrap_or_default();
2347 Some(Row::new(alloc::vec![
2348 Value::BigInt(i64::from(id)),
2349 Value::Text(owner),
2350 Value::BigInt(i64::try_from(meta.num_rows).unwrap_or(i64::MAX)),
2351 Value::BigInt(i64::from(meta.num_pages)),
2352 Value::BigInt(i64::try_from(meta.total_bytes).unwrap_or(i64::MAX)),
2353 ]))
2354 })
2355 .collect();
2356 QueryResult::Rows { columns, rows }
2357 }
2358
2359 fn exec_spg_stat_query(&self) -> QueryResult {
2365 let columns = alloc::vec![
2366 ColumnSchema::new("sql", DataType::Text, false),
2367 ColumnSchema::new("exec_count", DataType::BigInt, false),
2368 ColumnSchema::new("total_us", DataType::BigInt, false),
2369 ColumnSchema::new("mean_us", DataType::BigInt, false),
2370 ColumnSchema::new("max_us", DataType::BigInt, false),
2371 ColumnSchema::new("last_seen_us", DataType::BigInt, false),
2372 ];
2373 let rows: Vec<Row> = self
2374 .query_stats
2375 .snapshot()
2376 .into_iter()
2377 .map(|(sql, s)| {
2378 let mean = if s.exec_count == 0 {
2379 0
2380 } else {
2381 s.total_us / s.exec_count
2382 };
2383 Row::new(alloc::vec![
2384 Value::Text(sql),
2385 Value::BigInt(i64::try_from(s.exec_count).unwrap_or(i64::MAX)),
2386 Value::BigInt(i64::try_from(s.total_us).unwrap_or(i64::MAX)),
2387 Value::BigInt(i64::try_from(mean).unwrap_or(i64::MAX)),
2388 Value::BigInt(i64::try_from(s.max_us).unwrap_or(i64::MAX)),
2389 Value::BigInt(i64::try_from(s.last_seen_us).unwrap_or(i64::MAX)),
2390 ])
2391 })
2392 .collect();
2393 QueryResult::Rows { columns, rows }
2394 }
2395
2396 #[must_use]
2401 pub const fn with_activity_provider(mut self, f: ActivityProvider) -> Self {
2402 self.activity_provider = Some(f);
2403 self
2404 }
2405
2406 #[must_use]
2408 pub const fn with_audit_providers(
2409 mut self,
2410 chain: AuditChainProvider,
2411 verify: AuditVerifier,
2412 ) -> Self {
2413 self.audit_chain_provider = Some(chain);
2414 self.audit_verifier = Some(verify);
2415 self
2416 }
2417
2418 #[must_use]
2423 pub const fn with_slow_query_log(mut self, threshold_us: u64, logger: SlowQueryLogger) -> Self {
2424 self.slow_query_threshold_us = Some(threshold_us);
2425 self.slow_query_logger = Some(logger);
2426 self
2427 }
2428
2429 pub fn set_plan_cache_max(&mut self, n: usize) {
2433 self.plan_cache.set_max_entries(n);
2434 }
2435
2436 fn exec_spg_stat_activity(&self) -> QueryResult {
2441 let columns = alloc::vec![
2442 ColumnSchema::new("pid", DataType::Int, false),
2443 ColumnSchema::new("user", DataType::Text, false),
2444 ColumnSchema::new("started_at_us", DataType::BigInt, false),
2445 ColumnSchema::new("current_sql", DataType::Text, false),
2446 ColumnSchema::new("wait_event", DataType::Text, false),
2447 ColumnSchema::new("elapsed_us", DataType::BigInt, false),
2448 ColumnSchema::new("in_transaction", DataType::Bool, false),
2449 ColumnSchema::new("application_name", DataType::Text, false),
2450 ];
2451 let rows: Vec<Row> = self
2452 .activity_provider
2453 .map(|f| f())
2454 .unwrap_or_default()
2455 .into_iter()
2456 .map(|r| {
2457 Row::new(alloc::vec![
2458 Value::Int(i32::try_from(r.pid).unwrap_or(i32::MAX)),
2459 Value::Text(r.user),
2460 Value::BigInt(r.started_at_us),
2461 Value::Text(r.current_sql),
2462 Value::Text(r.wait_event),
2463 Value::BigInt(r.elapsed_us),
2464 Value::Bool(r.in_transaction),
2465 Value::Text(r.application_name),
2466 ])
2467 })
2468 .collect();
2469 QueryResult::Rows { columns, rows }
2470 }
2471
2472 fn exec_spg_table_ddl(&self) -> QueryResult {
2476 let columns = alloc::vec![
2477 ColumnSchema::new("table_name", DataType::Text, false),
2478 ColumnSchema::new("ddl", DataType::Text, false),
2479 ];
2480 let rows: Vec<Row> = self
2481 .catalog
2482 .table_names()
2483 .into_iter()
2484 .filter(|n| !is_internal_table_name(n))
2485 .filter_map(|name| {
2486 let table = self.catalog.get(&name)?;
2487 let ddl = render_create_table(&name, &table.schema().columns);
2488 Some(Row::new(alloc::vec![Value::Text(name), Value::Text(ddl),]))
2489 })
2490 .collect();
2491 QueryResult::Rows { columns, rows }
2492 }
2493
2494 fn exec_spg_role_ddl(&self) -> QueryResult {
2498 let columns = alloc::vec![
2499 ColumnSchema::new("role_name", DataType::Text, false),
2500 ColumnSchema::new("ddl", DataType::Text, false),
2501 ];
2502 let rows: Vec<Row> = self
2503 .users
2504 .iter()
2505 .map(|(name, rec)| {
2506 let ddl = alloc::format!(
2507 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}'",
2508 rec.role.as_str(),
2509 );
2510 Row::new(alloc::vec![
2511 Value::Text(String::from(name)),
2512 Value::Text(ddl)
2513 ])
2514 })
2515 .collect();
2516 QueryResult::Rows { columns, rows }
2517 }
2518
2519 fn exec_spg_database_ddl(&self) -> QueryResult {
2525 let columns = alloc::vec![ColumnSchema::new("ddl", DataType::Text, false)];
2526 let mut out = String::new();
2527 for (name, rec) in self.users.iter() {
2528 out.push_str(&alloc::format!(
2529 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}';\n",
2530 rec.role.as_str(),
2531 ));
2532 }
2533 for name in self.catalog.table_names() {
2534 if is_internal_table_name(&name) {
2535 continue;
2536 }
2537 if let Some(table) = self.catalog.get(&name) {
2538 out.push_str(&render_create_table(&name, &table.schema().columns));
2539 out.push_str(";\n");
2540 }
2541 }
2542 QueryResult::Rows {
2543 columns,
2544 rows: alloc::vec![Row::new(alloc::vec![Value::Text(out)])],
2545 }
2546 }
2547
2548 fn exec_spg_audit_chain(&self) -> QueryResult {
2552 let columns = alloc::vec![
2553 ColumnSchema::new("seq", DataType::BigInt, false),
2554 ColumnSchema::new("ts_ms", DataType::BigInt, false),
2555 ColumnSchema::new("prev_hash", DataType::Text, false),
2556 ColumnSchema::new("entry_hash", DataType::Text, false),
2557 ColumnSchema::new("sql", DataType::Text, false),
2558 ];
2559 let rows: Vec<Row> = self
2560 .audit_chain_provider
2561 .map(|f| f())
2562 .unwrap_or_default()
2563 .into_iter()
2564 .map(|r| {
2565 Row::new(alloc::vec![
2566 Value::BigInt(r.seq),
2567 Value::BigInt(r.ts_ms),
2568 Value::Text(r.prev_hash_hex),
2569 Value::Text(r.entry_hash_hex),
2570 Value::Text(r.sql),
2571 ])
2572 })
2573 .collect();
2574 QueryResult::Rows { columns, rows }
2575 }
2576
2577 fn exec_spg_audit_verify(&self) -> QueryResult {
2583 let columns = alloc::vec![
2584 ColumnSchema::new("verified_count", DataType::BigInt, false),
2585 ColumnSchema::new("broken_at_seq", DataType::BigInt, false),
2586 ];
2587 let (verified, broken) = self.audit_verifier.map(|f| f()).unwrap_or((0, -1));
2588 let row = Row::new(alloc::vec![Value::BigInt(verified), Value::BigInt(broken),]);
2589 QueryResult::Rows {
2590 columns,
2591 rows: alloc::vec![row],
2592 }
2593 }
2594
2595 pub fn query_stats(&self) -> &query_stats::QueryStats {
2597 &self.query_stats
2598 }
2599
2600 pub fn query_stats_mut(&mut self) -> &mut query_stats::QueryStats {
2602 &mut self.query_stats
2603 }
2604
2605 pub const fn statistics(&self) -> &statistics::Statistics {
2609 &self.statistics
2610 }
2611
2612 pub fn tables_needing_analyze(&self) -> Vec<String> {
2625 const MIN_ROWS: u64 = 100;
2626 let mut out = Vec::new();
2627 for name in self.catalog.table_names() {
2628 if is_internal_table_name(&name) {
2629 continue;
2630 }
2631 let Some(table) = self.catalog.get(&name) else {
2632 continue;
2633 };
2634 let row_count = table.rows().len() as u64;
2635 let modified = self.statistics.modified_since_last_analyze(&name);
2636 let base = row_count.max(MIN_ROWS);
2641 let threshold = base.saturating_add(9) / 10;
2642 if modified >= threshold {
2643 out.push(name);
2644 }
2645 }
2646 out
2647 }
2648
2649 fn exec_analyze(&mut self, target: Option<&str>) -> Result<QueryResult, EngineError> {
2660 let names: Vec<String> = if let Some(name) = target {
2661 if self.catalog.get(name).is_none() {
2663 return Err(EngineError::Storage(StorageError::TableNotFound {
2664 name: name.to_string(),
2665 }));
2666 }
2667 alloc::vec![name.to_string()]
2668 } else {
2669 self.catalog
2670 .table_names()
2671 .into_iter()
2672 .filter(|n| !is_internal_table_name(n))
2673 .collect()
2674 };
2675 let mut analysed = 0usize;
2676 for table_name in &names {
2677 self.analyze_one_table(table_name)?;
2678 analysed += 1;
2679 }
2680 if analysed > 0 {
2686 self.statistics.bump_version();
2687 if target.is_some() {
2688 for t in &names {
2689 self.plan_cache.evict_referencing(t);
2690 }
2691 } else {
2692 self.plan_cache.clear();
2693 }
2694 }
2695 Ok(QueryResult::CommandOk {
2696 affected: analysed,
2697 modified_catalog: true,
2698 })
2699 }
2700
2701 fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
2714 let normalised = match value {
2715 spg_sql::ast::SetValue::String(s) => s,
2716 spg_sql::ast::SetValue::Ident(s) => s,
2717 spg_sql::ast::SetValue::Number(s) => s,
2718 spg_sql::ast::SetValue::Default => String::new(),
2719 };
2720 let key = name.to_ascii_lowercase();
2721 let value_off = matches!(
2732 normalised.to_ascii_lowercase().as_str(),
2733 "0" | "off" | "false"
2734 );
2735 let value_on = matches!(
2736 normalised.to_ascii_lowercase().as_str(),
2737 "1" | "on" | "true"
2738 );
2739 if key == "foreign_key_checks"
2740 || key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
2741 {
2742 if value_off || key == "session_replication_role" {
2743 self.foreign_key_checks = false;
2744 } else if value_on
2745 || (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
2746 {
2747 self.foreign_key_checks = true;
2748 let _ = self.drain_pending_foreign_keys();
2752 }
2753 }
2754 self.session_params.insert(key, normalised);
2755 }
2756
2757 fn drain_pending_foreign_keys(&mut self) -> Result<(), EngineError> {
2764 let pending = core::mem::take(&mut self.pending_foreign_keys);
2765 for (child, fk) in pending {
2766 let cols_snapshot = match self.active_catalog().get(&child) {
2770 Some(t) => t.schema().columns.clone(),
2771 None => continue,
2772 };
2773 let storage_fk =
2774 resolve_foreign_key(&child, &cols_snapshot, fk, self.active_catalog())?;
2775 let table = self
2776 .active_catalog_mut()
2777 .get_mut(&child)
2778 .expect("checked above");
2779 table.schema_mut().foreign_keys.push(storage_fk);
2780 }
2781 Ok(())
2782 }
2783
2784 #[must_use]
2788 pub fn session_param(&self, name: &str) -> Option<&str> {
2789 self.session_params
2790 .get(&name.to_ascii_lowercase())
2791 .map(String::as_str)
2792 }
2793
2794 fn ev_ctx<'a>(
2799 &'a self,
2800 columns: &'a [ColumnSchema],
2801 alias: Option<&'a str>,
2802 ) -> EvalContext<'a> {
2803 EvalContext::new(columns, alias)
2804 .with_default_text_search_config(self.session_param("default_text_search_config"))
2805 }
2806
2807 fn exec_compact_cold_segments(&mut self) -> Result<QueryResult, EngineError> {
2811 let target = COMPACTION_TARGET_DEFAULT_BYTES;
2812 let reports = self.compact_cold_segments_with_target(target)?;
2813 let columns = alloc::vec![
2814 ColumnSchema::new("table_name", DataType::Text, false),
2815 ColumnSchema::new("index_name", DataType::Text, false),
2816 ColumnSchema::new("sources_merged", DataType::BigInt, false),
2817 ColumnSchema::new("merged_segment_id", DataType::BigInt, false),
2818 ColumnSchema::new("merged_rows", DataType::BigInt, false),
2819 ColumnSchema::new("deleted_rows_pruned", DataType::BigInt, false),
2820 ColumnSchema::new("bytes_reclaimed_estimate", DataType::BigInt, false),
2821 ];
2822 let rows: Vec<Row> = reports
2823 .into_iter()
2824 .map(|(tname, iname, report)| {
2825 Row::new(alloc::vec![
2826 Value::Text(tname),
2827 Value::Text(iname),
2828 Value::BigInt(i64::try_from(report.sources.len()).unwrap_or(i64::MAX)),
2829 Value::BigInt(i64::from(report.merged_segment_id.unwrap_or(0))),
2830 Value::BigInt(i64::try_from(report.merged_rows).unwrap_or(i64::MAX)),
2831 Value::BigInt(i64::try_from(report.deleted_rows_pruned).unwrap_or(i64::MAX),),
2832 Value::BigInt(
2833 i64::try_from(report.bytes_reclaimed_estimate).unwrap_or(i64::MAX),
2834 ),
2835 ])
2836 })
2837 .collect();
2838 Ok(QueryResult::Rows { columns, rows })
2839 }
2840
2841 fn analyze_one_table(&mut self, table_name: &str) -> Result<(), EngineError> {
2846 let table = self.catalog.get(table_name).ok_or_else(|| {
2847 EngineError::Storage(StorageError::TableNotFound {
2848 name: table_name.to_string(),
2849 })
2850 })?;
2851 let schema = table.schema().clone();
2852 let row_count = table.rows().len();
2853 self.statistics.clear_table(table_name);
2858 for (col_pos, col_schema) in schema.columns.iter().enumerate() {
2859 if matches!(col_schema.ty, DataType::Vector { .. }) {
2862 continue;
2863 }
2864 let mut non_null_values: Vec<Value> = Vec::with_capacity(row_count);
2865 let mut nulls: u64 = 0;
2866 for row in table.rows() {
2867 match row.values.get(col_pos) {
2868 Some(Value::Null) | None => nulls += 1,
2869 Some(v) => non_null_values.push(v.clone()),
2870 }
2871 }
2872 non_null_values.sort_by(|a, b| sort_values_for_histogram(a, b));
2877 let non_null: Vec<String> = non_null_values.iter().map(canonical_value_repr).collect();
2878 let null_frac = if row_count == 0 {
2879 0.0
2880 } else {
2881 #[allow(clippy::cast_precision_loss)]
2882 let f = nulls as f32 / row_count as f32;
2883 f
2884 };
2885 let n_distinct = statistics::estimate_n_distinct(&non_null);
2886 let histogram_bounds = statistics::build_histogram(&non_null);
2887 self.statistics.set(
2888 table_name.to_string(),
2889 col_schema.name.clone(),
2890 statistics::ColumnStats {
2891 null_frac,
2892 n_distinct,
2893 histogram_bounds,
2894 },
2895 );
2896 }
2897 self.statistics.reset_modified(table_name);
2898 let cold_count = {
2904 let table = self
2905 .active_catalog()
2906 .get(table_name)
2907 .expect("table still present");
2908 table.count_cold_locators()
2909 };
2910 let table_mut = self
2911 .active_catalog_mut()
2912 .get_mut(table_name)
2913 .expect("table still present");
2914 table_mut.set_cold_row_count(cold_count);
2915 Ok(())
2916 }
2917
2918 fn exec_show_publications(&self) -> QueryResult {
2930 let columns = alloc::vec![
2931 ColumnSchema::new("name", DataType::Text, false),
2932 ColumnSchema::new("scope", DataType::Text, false),
2933 ColumnSchema::new("table_count", DataType::Int, true),
2934 ];
2935 let rows: Vec<Row> = self
2936 .publications
2937 .iter()
2938 .map(|(name, scope)| {
2939 let (scope_str, count_val) = match scope {
2940 spg_sql::ast::PublicationScope::AllTables => {
2941 ("FOR ALL TABLES".to_string(), Value::Null)
2942 }
2943 spg_sql::ast::PublicationScope::ForTables(ts) => (
2944 alloc::format!("FOR TABLE {}", ts.join(", ")),
2945 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2946 ),
2947 spg_sql::ast::PublicationScope::AllTablesExcept(ts) => (
2948 alloc::format!("FOR ALL TABLES EXCEPT {}", ts.join(", ")),
2949 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2950 ),
2951 };
2952 Row::new(alloc::vec![
2953 Value::Text(name.clone()),
2954 Value::Text(scope_str),
2955 count_val,
2956 ])
2957 })
2958 .collect();
2959 QueryResult::Rows { columns, rows }
2960 }
2961
2962 fn exec_show_users(&self) -> QueryResult {
2964 let columns = alloc::vec![
2965 ColumnSchema::new("name", DataType::Text, false),
2966 ColumnSchema::new("role", DataType::Text, false),
2967 ];
2968 let rows: Vec<Row> = self
2969 .users
2970 .iter()
2971 .map(|(name, rec)| {
2972 Row::new(alloc::vec![
2973 Value::Text(name.to_string()),
2974 Value::Text(rec.role.as_str().to_string()),
2975 ])
2976 })
2977 .collect();
2978 QueryResult::Rows { columns, rows }
2979 }
2980
2981 fn exec_create_user(&mut self, s: &CreateUserStatement) -> Result<QueryResult, EngineError> {
2982 if self.in_transaction() {
2983 return Err(EngineError::Unsupported(
2984 "CREATE USER is not allowed inside a transaction".into(),
2985 ));
2986 }
2987 let role = users::Role::parse(&s.role).ok_or_else(|| {
2988 EngineError::Unsupported(alloc::format!("invalid role: {:?}", s.role))
2989 })?;
2990 let salt = self.salt_fn.map_or_else(
2994 || {
2995 let mut s_bytes = [0u8; 16];
2996 let digest = spg_crypto::hash(s.name.as_bytes());
2997 s_bytes.copy_from_slice(&digest[..16]);
2998 s_bytes
2999 },
3000 |f| f(),
3001 );
3002 self.users
3003 .create(&s.name, &s.password, role, salt)
3004 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE USER: {e}")))?;
3005 Ok(QueryResult::CommandOk {
3006 affected: 1,
3007 modified_catalog: true,
3008 })
3009 }
3010
3011 fn exec_drop_user(&mut self, name: &str) -> Result<QueryResult, EngineError> {
3012 if self.in_transaction() {
3013 return Err(EngineError::Unsupported(
3014 "DROP USER is not allowed inside a transaction".into(),
3015 ));
3016 }
3017 self.users
3018 .drop(name)
3019 .map_err(|e| EngineError::Unsupported(alloc::format!("DROP USER: {e}")))?;
3020 Ok(QueryResult::CommandOk {
3021 affected: 1,
3022 modified_catalog: true,
3023 })
3024 }
3025
3026 fn exec_create_function(
3032 &mut self,
3033 s: spg_sql::ast::CreateFunctionStatement,
3034 ) -> Result<QueryResult, EngineError> {
3035 let args_repr = render_function_args(&s.args);
3036 let returns = match &s.returns {
3037 spg_sql::ast::FunctionReturn::Trigger => alloc::string::String::from("TRIGGER"),
3038 spg_sql::ast::FunctionReturn::Void => alloc::string::String::from("VOID"),
3039 spg_sql::ast::FunctionReturn::Type(t) => alloc::format!("{t}"),
3040 spg_sql::ast::FunctionReturn::Other(s) => s.clone(),
3041 };
3042 let body_text = match &s.body {
3043 spg_sql::ast::FunctionBody::PlPgSql(b) => alloc::format!("{b}"),
3044 spg_sql::ast::FunctionBody::Raw(s) => s.clone(),
3045 };
3046 let def = spg_storage::FunctionDef {
3047 name: s.name.clone(),
3048 args_repr,
3049 returns,
3050 language: s.language.clone(),
3051 body: body_text,
3052 };
3053 self.active_catalog_mut()
3054 .create_function(def, s.or_replace)
3055 .map_err(EngineError::Storage)?;
3056 Ok(QueryResult::CommandOk {
3057 affected: 0,
3058 modified_catalog: true,
3059 })
3060 }
3061
3062 fn exec_create_trigger(
3067 &mut self,
3068 s: spg_sql::ast::CreateTriggerStatement,
3069 ) -> Result<QueryResult, EngineError> {
3070 let timing = match s.timing {
3071 spg_sql::ast::TriggerTiming::Before => "BEFORE",
3072 spg_sql::ast::TriggerTiming::After => "AFTER",
3073 spg_sql::ast::TriggerTiming::InsteadOf => "INSTEAD OF",
3074 };
3075 let events: Vec<alloc::string::String> = s
3076 .events
3077 .iter()
3078 .map(|e| match e {
3079 spg_sql::ast::TriggerEvent::Insert => alloc::string::String::from("INSERT"),
3080 spg_sql::ast::TriggerEvent::Update => alloc::string::String::from("UPDATE"),
3081 spg_sql::ast::TriggerEvent::Delete => alloc::string::String::from("DELETE"),
3082 spg_sql::ast::TriggerEvent::Truncate => alloc::string::String::from("TRUNCATE"),
3083 })
3084 .collect();
3085 let for_each = match s.for_each {
3086 spg_sql::ast::TriggerForEach::Row => "ROW",
3087 spg_sql::ast::TriggerForEach::Statement => "STATEMENT",
3088 };
3089 let def = spg_storage::TriggerDef {
3090 name: s.name.clone(),
3091 table: s.table.clone(),
3092 timing: alloc::string::String::from(timing),
3093 events,
3094 for_each: alloc::string::String::from(for_each),
3095 function: s.function.clone(),
3096 update_columns: s.update_columns.clone(),
3097 enabled: true,
3100 };
3101 self.active_catalog_mut()
3102 .create_trigger(def, s.or_replace)
3103 .map_err(EngineError::Storage)?;
3104 Ok(QueryResult::CommandOk {
3105 affected: 0,
3106 modified_catalog: true,
3107 })
3108 }
3109
3110 fn exec_drop_trigger(
3111 &mut self,
3112 name: &str,
3113 table: &str,
3114 if_exists: bool,
3115 ) -> Result<QueryResult, EngineError> {
3116 let removed = self.active_catalog_mut().drop_trigger(name, table);
3117 if !removed && !if_exists {
3118 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3119 alloc::format!("trigger {name:?} on {table:?} does not exist"),
3120 )));
3121 }
3122 Ok(QueryResult::CommandOk {
3123 affected: usize::from(removed),
3124 modified_catalog: removed,
3125 })
3126 }
3127
3128 fn exec_drop_function(
3129 &mut self,
3130 name: &str,
3131 if_exists: bool,
3132 ) -> Result<QueryResult, EngineError> {
3133 let removed = self.active_catalog_mut().drop_function(name);
3134 if !removed && !if_exists {
3135 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3136 alloc::format!("function {name:?} does not exist"),
3137 )));
3138 }
3139 Ok(QueryResult::CommandOk {
3140 affected: usize::from(removed),
3141 modified_catalog: removed,
3142 })
3143 }
3144
3145 fn exec_create_sequence(
3149 &mut self,
3150 s: spg_sql::ast::CreateSequenceStatement,
3151 ) -> Result<QueryResult, EngineError> {
3152 use spg_sql::ast::{SeqBound, SequenceDataType as AstDt};
3153 use spg_storage::{SequenceDataType, SequenceDef};
3154 let dt = match s.data_type {
3155 None => SequenceDataType::BigInt,
3156 Some(AstDt::SmallInt) => SequenceDataType::SmallInt,
3157 Some(AstDt::Int) => SequenceDataType::Int,
3158 Some(AstDt::BigInt) => SequenceDataType::BigInt,
3159 };
3160 let increment = s.options.increment.unwrap_or(1);
3161 if increment == 0 {
3162 return Err(EngineError::Unsupported(
3163 "INCREMENT must not be zero".into(),
3164 ));
3165 }
3166 let (def_min, def_max) = dt.default_bounds(increment > 0);
3167 let min_value = match s.options.min_value {
3168 None | Some(SeqBound::NoBound) => def_min,
3169 Some(SeqBound::Value(n)) => n,
3170 };
3171 let max_value = match s.options.max_value {
3172 None | Some(SeqBound::NoBound) => def_max,
3173 Some(SeqBound::Value(n)) => n,
3174 };
3175 if min_value > max_value {
3176 return Err(EngineError::Unsupported(alloc::format!(
3177 "MINVALUE ({min_value}) must be <= MAXVALUE ({max_value})"
3178 )));
3179 }
3180 let start = s
3181 .options
3182 .start
3183 .unwrap_or(if increment > 0 { min_value } else { max_value });
3184 if start < min_value || start > max_value {
3185 return Err(EngineError::Unsupported(alloc::format!(
3186 "START WITH ({start}) is outside MINVALUE..MAXVALUE ({min_value}..{max_value})"
3187 )));
3188 }
3189 let cache = s.options.cache.unwrap_or(1);
3190 if cache < 1 {
3191 return Err(EngineError::Unsupported("CACHE must be >= 1".into()));
3192 }
3193 let cycle = s.options.cycle.unwrap_or(false);
3194 let owned_by = match s.options.owned_by {
3195 None | Some(spg_sql::ast::SequenceOwnedBy::None) => None,
3196 Some(spg_sql::ast::SequenceOwnedBy::Column { table, column }) => Some((table, column)),
3197 };
3198 let def = SequenceDef {
3199 name: s.name.clone(),
3200 data_type: dt,
3201 start,
3202 increment,
3203 min_value,
3204 max_value,
3205 cache,
3206 cycle,
3207 owned_by,
3208 last_value: start,
3209 is_called: false,
3210 };
3211 self.active_catalog_mut()
3212 .create_sequence(def, s.if_not_exists)
3213 .map_err(EngineError::Storage)?;
3214 Ok(QueryResult::CommandOk {
3215 affected: 0,
3216 modified_catalog: !self.in_transaction(),
3217 })
3218 }
3219
3220 fn exec_alter_sequence(
3223 &mut self,
3224 s: spg_sql::ast::AlterSequenceStatement,
3225 ) -> Result<QueryResult, EngineError> {
3226 use spg_sql::ast::SeqBound;
3227 let cat = self.active_catalog_mut();
3228 if !cat.sequences().contains_key(&s.name) {
3229 if s.if_exists {
3230 return Ok(QueryResult::CommandOk {
3231 affected: 0,
3232 modified_catalog: false,
3233 });
3234 }
3235 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3236 alloc::format!("sequence {:?} does not exist", s.name),
3237 )));
3238 }
3239 let min_value = match s.options.min_value {
3240 None => None,
3241 Some(SeqBound::NoBound) => None, Some(SeqBound::Value(n)) => Some(n),
3243 };
3244 let max_value = match s.options.max_value {
3245 None => None,
3246 Some(SeqBound::NoBound) => None,
3247 Some(SeqBound::Value(n)) => Some(n),
3248 };
3249 let owned_by = s.options.owned_by.map(|ob| match ob {
3250 spg_sql::ast::SequenceOwnedBy::None => None,
3251 spg_sql::ast::SequenceOwnedBy::Column { table, column } => Some((table, column)),
3252 });
3253 cat.alter_sequence(
3254 &s.name,
3255 s.options.increment,
3256 min_value,
3257 max_value,
3258 s.options.start,
3259 s.options.restart,
3260 s.options.cache,
3261 s.options.cycle,
3262 owned_by,
3263 )
3264 .map_err(EngineError::Storage)?;
3265 Ok(QueryResult::CommandOk {
3266 affected: 0,
3267 modified_catalog: !self.in_transaction(),
3268 })
3269 }
3270
3271 fn pre_resolve_sequence_calls_in_statement(
3276 &mut self,
3277 stmt: &mut Statement,
3278 ) -> Result<(), EngineError> {
3279 match stmt {
3280 Statement::Select(s) => self.pre_resolve_sequence_calls_in_select(s),
3281 Statement::Insert(s) => {
3282 for tuple in &mut s.rows {
3283 for cell in tuple.iter_mut() {
3284 self.resolve_sequence_calls_in_expr(cell)?;
3285 }
3286 }
3287 Ok(())
3288 }
3289 Statement::Update(s) => {
3290 for (_col, expr) in &mut s.assignments {
3291 self.resolve_sequence_calls_in_expr(expr)?;
3292 }
3293 if let Some(w) = &mut s.where_ {
3294 self.resolve_sequence_calls_in_expr(w)?;
3295 }
3296 Ok(())
3297 }
3298 Statement::Delete(s) => {
3299 if let Some(w) = &mut s.where_ {
3300 self.resolve_sequence_calls_in_expr(w)?;
3301 }
3302 Ok(())
3303 }
3304 _ => Ok(()),
3305 }
3306 }
3307
3308 fn pre_resolve_sequence_calls_in_select(
3309 &mut self,
3310 s: &mut spg_sql::ast::SelectStatement,
3311 ) -> Result<(), EngineError> {
3312 for item in &mut s.items {
3313 match item {
3314 spg_sql::ast::SelectItem::Expr { expr, .. } => {
3315 self.resolve_sequence_calls_in_expr(expr)?;
3316 }
3317 spg_sql::ast::SelectItem::Wildcard => {}
3318 }
3319 }
3320 if let Some(w) = &mut s.where_ {
3321 self.resolve_sequence_calls_in_expr(w)?;
3322 }
3323 Ok(())
3324 }
3325
3326 #[allow(clippy::too_many_lines)]
3334 fn resolve_sequence_calls_in_expr(&mut self, expr: &mut Expr) -> Result<(), EngineError> {
3335 match expr {
3336 Expr::Literal(_) | Expr::Column(_) | Expr::Placeholder(_) => Ok(()),
3337 Expr::FunctionCall { name, args } => {
3338 for a in args.iter_mut() {
3342 self.resolve_sequence_calls_in_expr(a)?;
3343 }
3344 let lc = name.to_ascii_lowercase();
3345 if lc == "nextval" || lc == "currval" || lc == "setval" {
3346 let v = self.eval_sequence_call(&lc, args)?;
3347 *expr = Expr::Literal(value_to_literal(v));
3348 }
3349 Ok(())
3350 }
3351 Expr::Binary { lhs, rhs, .. } => {
3352 self.resolve_sequence_calls_in_expr(lhs)?;
3353 self.resolve_sequence_calls_in_expr(rhs)
3354 }
3355 Expr::Unary { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3356 Expr::Cast { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3357 Expr::IsNull { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3358 Expr::Like { expr, pattern, .. } => {
3359 self.resolve_sequence_calls_in_expr(expr)?;
3360 self.resolve_sequence_calls_in_expr(pattern)
3361 }
3362 Expr::Extract { source, .. } => self.resolve_sequence_calls_in_expr(source),
3363 Expr::Array(items) => {
3364 for it in items.iter_mut() {
3365 self.resolve_sequence_calls_in_expr(it)?;
3366 }
3367 Ok(())
3368 }
3369 _ => Ok(()),
3374 }
3375 }
3376
3377 fn eval_sequence_call(&mut self, op: &str, args: &[Expr]) -> Result<Value, EngineError> {
3381 if args.is_empty() {
3382 return Err(EngineError::Unsupported(alloc::format!(
3383 "{op}() takes at least one argument"
3384 )));
3385 }
3386 let seq_name = match &args[0] {
3387 Expr::Literal(spg_sql::ast::Literal::String(s)) => {
3388 let trimmed = s
3394 .strip_prefix("public.")
3395 .or_else(|| s.strip_prefix("pg_catalog."))
3396 .unwrap_or(s);
3397 trimmed.to_string()
3398 }
3399 Expr::Cast { expr, .. } => {
3404 if let Expr::Literal(spg_sql::ast::Literal::String(s)) = expr.as_ref() {
3405 let trimmed = s
3406 .strip_prefix("public.")
3407 .or_else(|| s.strip_prefix("pg_catalog."))
3408 .unwrap_or(s);
3409 trimmed.to_string()
3410 } else {
3411 return Err(EngineError::Unsupported(alloc::format!(
3412 "{op}() first argument must be a literal sequence name"
3413 )));
3414 }
3415 }
3416 other => {
3417 return Err(EngineError::Unsupported(alloc::format!(
3418 "{op}() first argument must be a literal sequence name, got {other:?}"
3419 )));
3420 }
3421 };
3422 match op {
3423 "nextval" => {
3424 let v = self
3425 .active_catalog_mut()
3426 .sequence_next_value(&seq_name)
3427 .map_err(EngineError::Storage)?;
3428 Ok(Value::BigInt(v))
3429 }
3430 "currval" => {
3431 let v = self
3432 .active_catalog()
3433 .sequence_current_value(&seq_name)
3434 .map_err(EngineError::Storage)?;
3435 Ok(Value::BigInt(v))
3436 }
3437 "setval" => {
3438 if args.len() < 2 || args.len() > 3 {
3439 return Err(EngineError::Unsupported(alloc::format!(
3440 "setval() takes 2 or 3 arguments, got {}",
3441 args.len()
3442 )));
3443 }
3444 let value = match &args[1] {
3445 Expr::Literal(spg_sql::ast::Literal::Integer(n)) => *n,
3446 other => {
3447 return Err(EngineError::Unsupported(alloc::format!(
3448 "setval() value argument must be a literal integer, got {other:?}"
3449 )));
3450 }
3451 };
3452 let is_called = if args.len() == 3 {
3453 match &args[2] {
3454 Expr::Literal(spg_sql::ast::Literal::Bool(b)) => *b,
3455 other => {
3456 return Err(EngineError::Unsupported(alloc::format!(
3457 "setval() is_called argument must be a literal BOOL, got {other:?}"
3458 )));
3459 }
3460 }
3461 } else {
3462 true
3463 };
3464 let v = self
3465 .active_catalog_mut()
3466 .sequence_set_value(&seq_name, value, is_called)
3467 .map_err(EngineError::Storage)?;
3468 Ok(Value::BigInt(v))
3469 }
3470 other => Err(EngineError::Unsupported(alloc::format!(
3471 "unknown sequence op {other:?}"
3472 ))),
3473 }
3474 }
3475
3476 fn expand_views_in_select(
3485 &self,
3486 stmt: &SelectStatement,
3487 ) -> Result<Option<SelectStatement>, EngineError> {
3488 let cat = self.active_catalog();
3489 let mut referenced: Vec<String> = Vec::new();
3490 if let Some(from) = &stmt.from {
3491 collect_view_refs(&from.primary, cat, &mut referenced);
3492 for j in &from.joins {
3493 collect_view_refs(&j.table, cat, &mut referenced);
3494 }
3495 }
3496 referenced.retain(|n| !stmt.ctes.iter().any(|c| c.name == *n));
3499 if referenced.is_empty() {
3500 return Ok(None);
3501 }
3502 let mut new_ctes: Vec<spg_sql::ast::Cte> = Vec::with_capacity(referenced.len());
3503 for name in &referenced {
3504 let view = cat.views().get(name).ok_or_else(|| {
3505 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3506 "view {name:?} disappeared mid-expansion"
3507 )))
3508 })?;
3509 let parsed = spg_sql::parser::parse_statement(&view.body).map_err(|e| {
3510 EngineError::Unsupported(alloc::format!("view {name:?} body re-parse failed: {e}"))
3511 })?;
3512 let Statement::Select(body) = parsed else {
3513 return Err(EngineError::Unsupported(alloc::format!(
3514 "view {name:?} body is not a SELECT (catalog corruption)"
3515 )));
3516 };
3517 new_ctes.push(spg_sql::ast::Cte {
3518 name: name.clone(),
3519 body,
3520 recursive: false,
3521 column_overrides: view.columns.clone(),
3522 });
3523 }
3524 let mut out = stmt.clone();
3525 new_ctes.extend(out.ctes);
3527 out.ctes = new_ctes;
3528 Ok(Some(out))
3529 }
3530
3531 fn exec_create_view(
3535 &mut self,
3536 s: spg_sql::ast::CreateViewStatement,
3537 ) -> Result<QueryResult, EngineError> {
3538 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body));
3542 let def = spg_storage::ViewDef {
3543 name: s.name.clone(),
3544 columns: s.columns,
3545 body: body_repr,
3546 };
3547 self.active_catalog_mut()
3548 .create_view(def, s.or_replace, s.if_not_exists)
3549 .map_err(EngineError::Storage)?;
3550 Ok(QueryResult::CommandOk {
3551 affected: 0,
3552 modified_catalog: !self.in_transaction(),
3553 })
3554 }
3555
3556 fn exec_create_type(
3561 &mut self,
3562 s: spg_sql::ast::CreateTypeStatement,
3563 ) -> Result<QueryResult, EngineError> {
3564 let cat = self.active_catalog();
3567 if cat.get(&s.name).is_some() {
3568 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3569 alloc::format!("type {:?} would shadow an existing table", s.name),
3570 )));
3571 }
3572 if cat.sequences().contains_key(&s.name) {
3573 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3574 alloc::format!("type {:?} would shadow an existing sequence", s.name),
3575 )));
3576 }
3577 if cat.views().contains_key(&s.name) {
3578 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3579 alloc::format!("type {:?} would shadow an existing view", s.name),
3580 )));
3581 }
3582 let def = match s.kind {
3583 spg_sql::ast::TypeKind::Enum { labels } => {
3584 if labels.is_empty() {
3585 return Err(EngineError::Unsupported(
3586 "CREATE TYPE … AS ENUM requires at least one label".into(),
3587 ));
3588 }
3589 for i in 0..labels.len() {
3591 for j in (i + 1)..labels.len() {
3592 if labels[i] == labels[j] {
3593 return Err(EngineError::Unsupported(alloc::format!(
3594 "CREATE TYPE {:?}: duplicate ENUM label {:?}",
3595 s.name,
3596 labels[i]
3597 )));
3598 }
3599 }
3600 }
3601 spg_storage::EnumDef {
3602 name: s.name.clone(),
3603 labels,
3604 }
3605 }
3606 };
3607 self.active_catalog_mut()
3608 .create_enum_type(def)
3609 .map_err(EngineError::Storage)?;
3610 Ok(QueryResult::CommandOk {
3611 affected: 0,
3612 modified_catalog: !self.in_transaction(),
3613 })
3614 }
3615
3616 fn exec_create_domain(
3621 &mut self,
3622 s: spg_sql::ast::CreateDomainStatement,
3623 ) -> Result<QueryResult, EngineError> {
3624 let cat = self.active_catalog();
3625 if cat.domain_types().contains_key(&s.name) {
3626 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3627 alloc::format!("domain {:?} already exists", s.name),
3628 )));
3629 }
3630 if cat.get(&s.name).is_some()
3631 || cat.sequences().contains_key(&s.name)
3632 || cat.views().contains_key(&s.name)
3633 || cat.enum_types().contains_key(&s.name)
3634 {
3635 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3636 alloc::format!("domain {:?} would shadow an existing object", s.name),
3637 )));
3638 }
3639 let base_type = column_type_to_data_type(s.base_type);
3640 let default = s.default.as_ref().map(|e| alloc::format!("{e}"));
3641 let checks = s
3642 .checks
3643 .iter()
3644 .map(|e| alloc::format!("{e}"))
3645 .collect::<Vec<_>>();
3646 let def = spg_storage::DomainDef {
3647 name: s.name.clone(),
3648 base_type,
3649 nullable: !s.not_null,
3650 default,
3651 checks,
3652 };
3653 self.active_catalog_mut()
3654 .create_domain_type(def)
3655 .map_err(EngineError::Storage)?;
3656 Ok(QueryResult::CommandOk {
3657 affected: 0,
3658 modified_catalog: !self.in_transaction(),
3659 })
3660 }
3661
3662 fn exec_drop_domain(
3664 &mut self,
3665 names: &[String],
3666 if_exists: bool,
3667 ) -> Result<QueryResult, EngineError> {
3668 let mut removed = 0usize;
3669 for name in names {
3670 let was_present = self.active_catalog_mut().drop_domain_type(name);
3671 if was_present {
3672 removed += 1;
3673 } else if !if_exists {
3674 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3675 alloc::format!("domain {name:?} does not exist"),
3676 )));
3677 }
3678 }
3679 Ok(QueryResult::CommandOk {
3680 affected: removed,
3681 modified_catalog: removed > 0 && !self.in_transaction(),
3682 })
3683 }
3684
3685 fn exec_create_schema(
3691 &mut self,
3692 name: String,
3693 if_not_exists: bool,
3694 ) -> Result<QueryResult, EngineError> {
3695 self.active_catalog_mut()
3696 .create_schema(name, if_not_exists)
3697 .map_err(EngineError::Storage)?;
3698 Ok(QueryResult::CommandOk {
3699 affected: 0,
3700 modified_catalog: !self.in_transaction(),
3701 })
3702 }
3703
3704 fn exec_drop_schema(
3708 &mut self,
3709 names: &[String],
3710 if_exists: bool,
3711 ) -> Result<QueryResult, EngineError> {
3712 let mut removed = 0usize;
3713 for name in names {
3714 let was_present = self
3715 .active_catalog_mut()
3716 .drop_schema(name)
3717 .map_err(EngineError::Storage)?;
3718 if was_present {
3719 removed += 1;
3720 } else if !if_exists {
3721 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3722 alloc::format!("schema {name:?} does not exist"),
3723 )));
3724 }
3725 }
3726 Ok(QueryResult::CommandOk {
3727 affected: removed,
3728 modified_catalog: removed > 0 && !self.in_transaction(),
3729 })
3730 }
3731
3732 fn exec_drop_type(
3737 &mut self,
3738 names: &[String],
3739 if_exists: bool,
3740 ) -> Result<QueryResult, EngineError> {
3741 let mut removed = 0usize;
3742 for name in names {
3743 let was_present = self.active_catalog_mut().drop_enum_type(name);
3744 if was_present {
3745 removed += 1;
3746 } else if !if_exists {
3747 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3748 alloc::format!("type {name:?} does not exist"),
3749 )));
3750 }
3751 }
3752 Ok(QueryResult::CommandOk {
3753 affected: removed,
3754 modified_catalog: removed > 0 && !self.in_transaction(),
3755 })
3756 }
3757
3758 fn exec_create_materialized_view(
3763 &mut self,
3764 s: spg_sql::ast::CreateMaterializedViewStatement,
3765 ) -> Result<QueryResult, EngineError> {
3766 let cat = self.active_catalog();
3768 if cat.materialized_views().contains_key(&s.name) || cat.get(&s.name).is_some() {
3769 if s.if_not_exists {
3770 return Ok(QueryResult::CommandOk {
3771 affected: 0,
3772 modified_catalog: false,
3773 });
3774 }
3775 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3776 alloc::format!("materialized view {:?} already exists", s.name),
3777 )));
3778 }
3779 if cat.views().contains_key(&s.name) {
3780 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3781 alloc::format!(
3782 "materialized view {:?} would shadow an existing view",
3783 s.name
3784 ),
3785 )));
3786 }
3787 if cat.sequences().contains_key(&s.name) {
3788 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3789 alloc::format!(
3790 "materialized view {:?} would shadow an existing sequence",
3791 s.name
3792 ),
3793 )));
3794 }
3795 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body.clone()));
3797 let result = self.exec_select_cancel(&s.body, CancelToken::none())?;
3802 let (mut cols, rows) = match result {
3803 QueryResult::Rows { columns, rows } => (columns, rows),
3804 other => {
3805 return Err(EngineError::Unsupported(alloc::format!(
3806 "CREATE MATERIALIZED VIEW body did not return rows: {other:?}"
3807 )));
3808 }
3809 };
3810 if !s.columns.is_empty() {
3812 if s.columns.len() != cols.len() {
3813 return Err(EngineError::Unsupported(alloc::format!(
3814 "CREATE MATERIALIZED VIEW {:?}: column list has {} names but body returns {}",
3815 s.name,
3816 s.columns.len(),
3817 cols.len()
3818 )));
3819 }
3820 for (c, name) in cols.iter_mut().zip(s.columns.iter()) {
3821 c.name.clone_from(name);
3822 }
3823 }
3824 cols = infer_column_types(&cols, &rows);
3827 let schema = spg_storage::TableSchema::new(s.name.clone(), cols);
3828 let cat = self.active_catalog_mut();
3829 cat.create_table(schema).map_err(EngineError::Storage)?;
3830 if s.with_data {
3831 let table = cat
3832 .get_mut(&s.name)
3833 .expect("just-created materialized-view backing table must exist");
3834 for row in rows {
3835 table.insert(row).map_err(EngineError::Storage)?;
3836 }
3837 }
3838 cat.register_materialized_view(s.name.clone(), body_repr);
3839 Ok(QueryResult::CommandOk {
3840 affected: 0,
3841 modified_catalog: !self.in_transaction(),
3842 })
3843 }
3844
3845 fn exec_refresh_materialized_view(
3849 &mut self,
3850 name: &str,
3851 with_data: bool,
3852 ) -> Result<QueryResult, EngineError> {
3853 let source = self
3854 .active_catalog()
3855 .materialized_views()
3856 .get(name)
3857 .cloned()
3858 .ok_or_else(|| {
3859 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3860 "materialized view {name:?} does not exist"
3861 )))
3862 })?;
3863 {
3866 let cat = self.active_catalog_mut();
3867 let table = cat.get_mut(name).ok_or_else(|| {
3868 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3869 "materialized view {name:?} backing table missing"
3870 )))
3871 })?;
3872 table.truncate();
3873 }
3874 if !with_data {
3875 return Ok(QueryResult::CommandOk {
3876 affected: 0,
3877 modified_catalog: !self.in_transaction(),
3878 });
3879 }
3880 let parsed = spg_sql::parser::parse_statement(&source).map_err(|e| {
3881 EngineError::Unsupported(alloc::format!(
3882 "materialized view {name:?} body re-parse failed: {e}"
3883 ))
3884 })?;
3885 let Statement::Select(body) = parsed else {
3886 return Err(EngineError::Unsupported(alloc::format!(
3887 "materialized view {name:?} body is not a SELECT (catalog corruption)"
3888 )));
3889 };
3890 let rows = match self.exec_select_cancel(&body, CancelToken::none())? {
3891 QueryResult::Rows { rows, .. } => rows,
3892 other => {
3893 return Err(EngineError::Unsupported(alloc::format!(
3894 "REFRESH MATERIALIZED VIEW {name:?} body did not return rows: {other:?}"
3895 )));
3896 }
3897 };
3898 let cat = self.active_catalog_mut();
3899 let table = cat.get_mut(name).expect("backing table verified above");
3900 let affected = rows.len();
3901 for row in rows {
3902 table.insert(row).map_err(EngineError::Storage)?;
3903 }
3904 Ok(QueryResult::CommandOk {
3905 affected,
3906 modified_catalog: !self.in_transaction(),
3907 })
3908 }
3909
3910 fn exec_drop_materialized_view(
3913 &mut self,
3914 names: &[String],
3915 if_exists: bool,
3916 ) -> Result<QueryResult, EngineError> {
3917 let mut removed = 0usize;
3918 for name in names {
3919 let was_present = self
3920 .active_catalog_mut()
3921 .drop_materialized_view_source(name);
3922 if was_present {
3923 self.active_catalog_mut().drop_table(name);
3925 removed += 1;
3926 } else if !if_exists {
3927 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3928 alloc::format!("materialized view {name:?} does not exist"),
3929 )));
3930 }
3931 }
3932 Ok(QueryResult::CommandOk {
3933 affected: removed,
3934 modified_catalog: removed > 0 && !self.in_transaction(),
3935 })
3936 }
3937
3938 fn exec_drop_view(
3940 &mut self,
3941 names: &[String],
3942 if_exists: bool,
3943 ) -> Result<QueryResult, EngineError> {
3944 let mut removed = 0usize;
3945 for name in names {
3946 let was_present = self.active_catalog_mut().drop_view(name);
3947 if !was_present && !if_exists {
3948 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3949 alloc::format!("view {name:?} does not exist"),
3950 )));
3951 }
3952 if was_present {
3953 removed += 1;
3954 }
3955 }
3956 Ok(QueryResult::CommandOk {
3957 affected: removed,
3958 modified_catalog: removed > 0 && !self.in_transaction(),
3959 })
3960 }
3961
3962 fn exec_drop_sequence(
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.active_catalog_mut().drop_sequence(name);
3971 if !was_present && !if_exists {
3972 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3973 alloc::format!("sequence {name:?} does not exist"),
3974 )));
3975 }
3976 if was_present {
3977 removed += 1;
3978 }
3979 }
3980 Ok(QueryResult::CommandOk {
3981 affected: removed,
3982 modified_catalog: removed > 0 && !self.in_transaction(),
3983 })
3984 }
3985
3986 fn exec_update_cancel(
3993 &mut self,
3994 stmt: &spg_sql::ast::UpdateStatement,
3995 cancel: CancelToken<'_>,
3996 ) -> Result<QueryResult, EngineError> {
3997 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
4006 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
4007 let trigger_session_cfg: Option<String> = self
4008 .session_params
4009 .get("default_text_search_config")
4010 .cloned();
4011 if let Some(w) = &stmt.where_ {
4019 let schema_cols = self
4020 .active_catalog()
4021 .get(&stmt.table)
4022 .ok_or_else(|| {
4023 EngineError::Storage(StorageError::TableNotFound {
4024 name: stmt.table.clone(),
4025 })
4026 })?
4027 .schema()
4028 .columns
4029 .clone();
4030 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4031 && let Some(idx_name) = self
4032 .active_catalog()
4033 .get(&stmt.table)
4034 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4035 {
4036 let _ = self
4040 .active_catalog_mut()
4041 .promote_cold_row(&stmt.table, &idx_name, &key);
4042 }
4043 }
4044
4045 let ts_cfg: Option<String> = self
4048 .session_param("default_text_search_config")
4049 .map(String::from);
4050 let clock_for_on_update = self.clock;
4054 let table = self
4055 .active_catalog_mut()
4056 .get_mut(&stmt.table)
4057 .ok_or_else(|| {
4058 EngineError::Storage(StorageError::TableNotFound {
4059 name: stmt.table.clone(),
4060 })
4061 })?;
4062 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4063 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
4067 for (col, expr) in &stmt.assignments {
4068 let pos = schema_cols
4069 .iter()
4070 .position(|c| c.name == *col)
4071 .ok_or_else(|| {
4072 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
4073 })?;
4074 targets.push((pos, expr));
4075 }
4076 let mut on_update_overrides: Vec<(usize, String)> = Vec::new();
4084 for (i, col) in schema_cols.iter().enumerate() {
4085 if targets.iter().any(|(p, _)| *p == i) {
4086 continue;
4087 }
4088 if let Some(src) = &col.on_update_runtime {
4089 on_update_overrides.push((i, src.clone()));
4090 }
4091 }
4092 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4093 .with_default_text_search_config(ts_cfg.as_deref());
4094 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
4100 for (i, row) in table.rows().iter().enumerate() {
4101 if i.is_multiple_of(256) {
4105 cancel.check()?;
4106 }
4107 if let Some(w) = &stmt.where_ {
4108 let cond = eval::eval_expr(w, row, &ctx)?;
4109 if !matches!(cond, Value::Bool(true)) {
4110 continue;
4111 }
4112 }
4113 let mut new_vals = row.values.clone();
4114 for (pos, expr) in &targets {
4115 let v = eval::eval_expr(expr, row, &ctx)?;
4116 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
4117 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
4118 new_vals[*pos] = coerced;
4119 }
4120 for (pos, src) in &on_update_overrides {
4123 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4124 new_vals[*pos] = v;
4125 }
4126 planned.push((i, new_vals));
4127 }
4128 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4132 .iter()
4133 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4134 .collect();
4135 let self_fks = table.schema().foreign_keys.clone();
4136 let _ = table;
4141 if !self_fks.is_empty() {
4145 let new_rows: Vec<Vec<Value>> = planned
4146 .iter()
4147 .map(|(_pos, new_vals)| new_vals.clone())
4148 .collect();
4149 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4150 }
4151 {
4155 let new_rows: Vec<Vec<Value>> = planned
4156 .iter()
4157 .map(|(_pos, new_vals)| new_vals.clone())
4158 .collect();
4159 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4160 }
4161 let child_plan =
4165 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4166 for step in &child_plan {
4168 apply_fk_child_step(self.active_catalog_mut(), step)?;
4169 }
4170 let table = self
4172 .active_catalog_mut()
4173 .get_mut(&stmt.table)
4174 .ok_or_else(|| {
4175 EngineError::Storage(StorageError::TableNotFound {
4176 name: stmt.table.clone(),
4177 })
4178 })?;
4179 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4189 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4191 for (pos, new_vals) in &planned {
4192 let old_row = table.rows()[*pos].clone();
4193 let mut new_row = Row::new(new_vals.clone());
4194 let mut skip = false;
4195 for (fd, filter) in &before_update_triggers {
4196 if !filter.is_empty()
4201 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4202 {
4203 continue;
4204 }
4205 let (outcome, deferred) = triggers::fire_row_trigger(
4206 fd,
4207 Some(new_row.clone()),
4208 Some(&old_row),
4209 &stmt.table,
4210 &schema_cols,
4211 &[],
4212 trigger_session_cfg.as_deref(),
4213 false,
4214 )
4215 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4216 deferred_embedded.extend(deferred);
4217 match outcome {
4218 triggers::TriggerOutcome::Row(r) => new_row = r,
4219 triggers::TriggerOutcome::Skip => {
4220 skip = true;
4221 break;
4222 }
4223 }
4224 }
4225 if !skip {
4226 applied_after_before.push((*pos, new_row, old_row));
4227 }
4228 }
4229 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4232 applied_after_before
4233 .iter()
4234 .map(|(_pos, new_row, _old)| new_row.values.clone())
4235 .collect()
4236 } else {
4237 Vec::new()
4238 };
4239 let affected = applied_after_before.len();
4240 for (pos, new_row, old_row) in applied_after_before {
4244 table.update_row(pos, new_row.values.clone())?;
4245 for (fd, filter) in &after_update_triggers {
4246 if !filter.is_empty()
4247 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4248 {
4249 continue;
4250 }
4251 let (_outcome, deferred) = triggers::fire_row_trigger(
4252 fd,
4253 Some(new_row.clone()),
4254 Some(&old_row),
4255 &stmt.table,
4256 &schema_cols,
4257 &[],
4258 trigger_session_cfg.as_deref(),
4259 true,
4260 )
4261 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4262 deferred_embedded.extend(deferred);
4263 }
4264 }
4265 let _ = table;
4266 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4268 if !self.in_transaction() && affected > 0 {
4270 self.statistics
4271 .record_modifications(&stmt.table, affected as u64);
4272 }
4273 if let Some(items) = &stmt.returning {
4275 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4276 }
4277 Ok(QueryResult::CommandOk {
4278 affected,
4279 modified_catalog: !self.in_transaction(),
4280 })
4281 }
4282
4283 fn exec_merge_cancel(
4314 &mut self,
4315 stmt: &spg_sql::ast::MergeStatement,
4316 cancel: CancelToken<'_>,
4317 ) -> Result<QueryResult, EngineError> {
4318 let target_alias = stmt
4319 .target_alias
4320 .clone()
4321 .unwrap_or_else(|| stmt.target.clone());
4322 let source_alias = stmt
4323 .source_alias
4324 .clone()
4325 .unwrap_or_else(|| stmt.source.clone());
4326 let (target_cols, target_rows_snapshot) = {
4327 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4328 EngineError::Storage(StorageError::TableNotFound {
4329 name: stmt.target.clone(),
4330 })
4331 })?;
4332 (
4333 t.schema().columns.clone(),
4334 t.rows().iter().cloned().collect::<Vec<Row>>(),
4335 )
4336 };
4337 let (source_cols, source_rows) = {
4338 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4339 EngineError::Storage(StorageError::TableNotFound {
4340 name: stmt.source.clone(),
4341 })
4342 })?;
4343 (
4344 s.schema().columns.clone(),
4345 s.rows().iter().cloned().collect::<Vec<Row>>(),
4346 )
4347 };
4348 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4350 for col in &target_cols {
4351 combined_schema.push(ColumnSchema::new(
4352 alloc::format!("{target_alias}.{}", col.name),
4353 col.ty,
4354 col.nullable,
4355 ));
4356 }
4357 for col in &source_cols {
4358 combined_schema.push(ColumnSchema::new(
4359 alloc::format!("{source_alias}.{}", col.name),
4360 col.ty,
4361 col.nullable,
4362 ));
4363 }
4364 let combined_ctx = EvalContext::new(&combined_schema, None);
4365 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4369 for col in &target_cols {
4370 source_only_schema.push(ColumnSchema::new(
4371 alloc::format!("{target_alias}.{}", col.name),
4372 col.ty,
4373 col.nullable,
4374 ));
4375 }
4376 for col in &source_cols {
4377 source_only_schema.push(ColumnSchema::new(
4378 alloc::format!("{source_alias}.{}", col.name),
4379 col.ty,
4380 col.nullable,
4381 ));
4382 }
4383 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4384 let target_arity = target_cols.len();
4385 let source_arity = source_cols.len();
4386
4387 let mut delete_indices: Vec<usize> = Vec::new();
4390 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4391 let mut inserts: Vec<Vec<Value>> = Vec::new();
4392 let mut affected: usize = 0;
4393
4394 for (src_idx, src_row) in source_rows.iter().enumerate() {
4395 if src_idx.is_multiple_of(256) {
4396 cancel.check()?;
4397 }
4398 let mut matched_targets: Vec<usize> = Vec::new();
4400 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4401 let mut combined_vals = t_row.values.clone();
4402 combined_vals.extend(src_row.values.iter().cloned());
4403 let combined_row = Row::new(combined_vals);
4404 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4405 if matches!(cond, Value::Bool(true)) {
4406 matched_targets.push(t_idx);
4407 }
4408 }
4409 let is_matched = !matched_targets.is_empty();
4410 let fired_clause = stmt.clauses.iter().find(|c| {
4416 let kind_ok = match c.matched {
4417 spg_sql::ast::MergeMatched::Matched => is_matched,
4418 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4419 };
4420 if !kind_ok {
4421 return false;
4422 }
4423 let Some(cond_expr) = &c.condition else {
4424 return true;
4425 };
4426 let row = if is_matched {
4427 let t = &target_rows_snapshot[matched_targets[0]];
4428 let mut vals = t.values.clone();
4429 vals.extend(src_row.values.iter().cloned());
4430 Row::new(vals)
4431 } else {
4432 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4433 vals.extend(src_row.values.iter().cloned());
4434 Row::new(vals)
4435 };
4436 let ctx_ref = if is_matched {
4437 &combined_ctx
4438 } else {
4439 &source_only_ctx
4440 };
4441 matches!(
4442 eval::eval_expr(cond_expr, &row, ctx_ref),
4443 Ok(Value::Bool(true))
4444 )
4445 });
4446 let Some(clause) = fired_clause else { continue };
4447 match &clause.action {
4448 spg_sql::ast::MergeAction::DoNothing => {}
4449 spg_sql::ast::MergeAction::Delete => {
4450 for &t_idx in &matched_targets {
4451 if !delete_indices.contains(&t_idx) {
4452 delete_indices.push(t_idx);
4453 affected += 1;
4454 }
4455 }
4456 }
4457 spg_sql::ast::MergeAction::Update { assignments } => {
4458 let mut planned_sets: Vec<(usize, &Expr)> =
4460 Vec::with_capacity(assignments.len());
4461 for (col, expr) in assignments {
4462 let pos =
4463 target_cols
4464 .iter()
4465 .position(|c| c.name == *col)
4466 .ok_or_else(|| {
4467 EngineError::Eval(EvalError::ColumnNotFound {
4468 name: col.clone(),
4469 })
4470 })?;
4471 planned_sets.push((pos, expr));
4472 }
4473 for &t_idx in &matched_targets {
4474 let t_row = &target_rows_snapshot[t_idx];
4475 let mut new_values = t_row.values.clone();
4476 let mut combined_vals = t_row.values.clone();
4477 combined_vals.extend(src_row.values.iter().cloned());
4478 let combined_row = Row::new(combined_vals);
4479 for (pos, expr) in &planned_sets {
4480 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4481 let coerced = coerce_value(
4482 raw,
4483 target_cols[*pos].ty,
4484 &target_cols[*pos].name,
4485 *pos,
4486 )?;
4487 new_values[*pos] = coerced;
4488 }
4489 updates.push((t_idx, new_values));
4490 affected += 1;
4491 }
4492 }
4493 spg_sql::ast::MergeAction::Insert { columns, values } => {
4494 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4496 vals.extend(src_row.values.iter().cloned());
4497 let synth_row = Row::new(vals);
4498 let mut new_row_values: Vec<Value> =
4499 (0..target_arity).map(|_| Value::Null).collect();
4500 for (col, expr) in columns.iter().zip(values.iter()) {
4501 let pos =
4502 target_cols
4503 .iter()
4504 .position(|c| c.name == *col)
4505 .ok_or_else(|| {
4506 EngineError::Eval(EvalError::ColumnNotFound {
4507 name: col.clone(),
4508 })
4509 })?;
4510 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4511 let coerced =
4512 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4513 new_row_values[pos] = coerced;
4514 }
4515 inserts.push(new_row_values);
4516 affected += 1;
4517 }
4518 }
4519 }
4520 let _ = source_arity; let table = self
4524 .active_catalog_mut()
4525 .get_mut(&stmt.target)
4526 .ok_or_else(|| {
4527 EngineError::Storage(StorageError::TableNotFound {
4528 name: stmt.target.clone(),
4529 })
4530 })?;
4531 for (idx, new_vals) in &updates {
4535 table
4536 .update_row(*idx, new_vals.clone())
4537 .map_err(EngineError::Storage)?;
4538 }
4539 if !delete_indices.is_empty() {
4540 table.delete_rows(&delete_indices);
4541 }
4542 for vals in inserts {
4543 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4544 }
4545 Ok(QueryResult::CommandOk {
4546 affected,
4547 modified_catalog: affected > 0,
4548 })
4549 }
4550
4551 fn exec_delete_cancel(
4552 &mut self,
4553 stmt: &spg_sql::ast::DeleteStatement,
4554 cancel: CancelToken<'_>,
4555 ) -> Result<QueryResult, EngineError> {
4556 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4560 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4561 let trigger_session_cfg: Option<String> = self
4562 .session_params
4563 .get("default_text_search_config")
4564 .cloned();
4565 let mut cold_shadow_count: usize = 0;
4573 if let Some(w) = &stmt.where_ {
4574 let schema_cols = self
4575 .active_catalog()
4576 .get(&stmt.table)
4577 .ok_or_else(|| {
4578 EngineError::Storage(StorageError::TableNotFound {
4579 name: stmt.table.clone(),
4580 })
4581 })?
4582 .schema()
4583 .columns
4584 .clone();
4585 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4586 && let Some(idx_name) = self
4587 .active_catalog()
4588 .get(&stmt.table)
4589 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4590 {
4591 cold_shadow_count = self
4592 .active_catalog_mut()
4593 .shadow_cold_row(&stmt.table, &idx_name, &key)
4594 .unwrap_or(0);
4595 }
4596 }
4597
4598 let ts_cfg: Option<String> = self
4604 .session_param("default_text_search_config")
4605 .map(String::from);
4606 let table = self
4607 .active_catalog_mut()
4608 .get_mut(&stmt.table)
4609 .ok_or_else(|| {
4610 EngineError::Storage(StorageError::TableNotFound {
4611 name: stmt.table.clone(),
4612 })
4613 })?;
4614 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4615 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4616 .with_default_text_search_config(ts_cfg.as_deref());
4617 let mut positions: Vec<usize> = Vec::new();
4618 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4622 for (i, row) in table.rows().iter().enumerate() {
4623 if i.is_multiple_of(256) {
4624 cancel.check()?;
4625 }
4626 let keep = if let Some(w) = &stmt.where_ {
4627 let cond = eval::eval_expr(w, row, &ctx)?;
4628 !matches!(cond, Value::Bool(true))
4629 } else {
4630 false
4631 };
4632 if !keep {
4633 positions.push(i);
4634 to_delete_rows.push(row.values.clone());
4635 }
4636 }
4637 let _ = table;
4644 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4652 if !before_delete_triggers.is_empty() {
4653 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4654 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4655 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4656 let old_row = Row::new(old_vals.clone());
4657 let mut cancel_this = false;
4658 for fd in &before_delete_triggers {
4659 let (outcome, deferred) = triggers::fire_row_trigger(
4660 fd,
4661 None,
4662 Some(&old_row),
4663 &stmt.table,
4664 &schema_cols,
4665 &[],
4666 trigger_session_cfg.as_deref(),
4667 false,
4668 )
4669 .map_err(|e| {
4670 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4671 })?;
4672 deferred_embedded.extend(deferred);
4673 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4674 cancel_this = true;
4675 break;
4676 }
4677 }
4678 if !cancel_this {
4679 filtered_positions.push(*pos);
4680 filtered_old_rows.push(old_vals.clone());
4681 }
4682 }
4683 positions = filtered_positions;
4684 to_delete_rows = filtered_old_rows;
4685 }
4686 let cascade_plan = plan_fk_parent_deletions(
4687 self.active_catalog(),
4688 &stmt.table,
4689 &positions,
4690 &to_delete_rows,
4691 )?;
4692 for step in &cascade_plan {
4699 apply_fk_child_step(self.active_catalog_mut(), step)?;
4700 }
4701 let table = self
4703 .active_catalog_mut()
4704 .get_mut(&stmt.table)
4705 .ok_or_else(|| {
4706 EngineError::Storage(StorageError::TableNotFound {
4707 name: stmt.table.clone(),
4708 })
4709 })?;
4710 let affected = table.delete_rows(&positions) + cold_shadow_count;
4711 let _ = table;
4712 if !after_delete_triggers.is_empty() {
4717 for old_vals in &to_delete_rows {
4718 let old_row = Row::new(old_vals.clone());
4719 for fd in &after_delete_triggers {
4720 let (_outcome, deferred) = triggers::fire_row_trigger(
4721 fd,
4722 None,
4723 Some(&old_row),
4724 &stmt.table,
4725 &schema_cols,
4726 &[],
4727 trigger_session_cfg.as_deref(),
4728 true,
4729 )
4730 .map_err(|e| {
4731 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4732 })?;
4733 deferred_embedded.extend(deferred);
4734 }
4735 }
4736 }
4737 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4739 if !self.in_transaction() && affected > 0 {
4741 self.statistics
4742 .record_modifications(&stmt.table, affected as u64);
4743 }
4744 if let Some(items) = &stmt.returning {
4750 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4751 }
4752 Ok(QueryResult::CommandOk {
4753 affected,
4754 modified_catalog: !self.in_transaction(),
4755 })
4756 }
4757
4758 #[allow(clippy::format_push_string)]
4768 fn exec_explain(
4769 &self,
4770 e: &spg_sql::ast::ExplainStatement,
4771 cancel: CancelToken<'_>,
4772 ) -> Result<QueryResult, EngineError> {
4773 let mut lines = Vec::<String>::new();
4774 explain_select(&e.inner, self, 0, &mut lines);
4775 if e.suggest {
4776 let suggestions = build_index_suggestions(&e.inner, self);
4785 for s in suggestions {
4786 lines.push(s);
4787 }
4788 } else if e.analyze {
4789 let started = self.clock.map(|f| f());
4806 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4807 let elapsed_micros = match (self.clock, started) {
4808 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4809 _ => None,
4810 };
4811 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4812 rows.len()
4813 } else {
4814 0
4815 };
4816 annotate_explain_lines(&mut lines, row_count, self);
4817 let mut total = alloc::format!("Total: rows={row_count}");
4818 if let Some(us) = elapsed_micros {
4819 total.push_str(&alloc::format!(" elapsed={us}us"));
4820 }
4821 lines.push(total);
4822 }
4823 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
4824 let rows: Vec<Row> = lines
4825 .into_iter()
4826 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
4827 .collect();
4828 Ok(QueryResult::Rows { columns, rows })
4829 }
4830
4831 fn exec_show_tables(&self) -> QueryResult {
4832 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
4833 let rows: Vec<Row> = self
4834 .active_catalog()
4835 .table_names()
4836 .into_iter()
4837 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
4838 .collect();
4839 QueryResult::Rows { columns, rows }
4840 }
4841
4842 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
4847 let t = self.active_catalog().get(name).ok_or_else(|| {
4848 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4849 })?;
4850 let cols: Vec<String> = t
4851 .schema()
4852 .columns
4853 .iter()
4854 .map(|c| {
4855 let ty = render_data_type(c.ty);
4856 let nullable = if c.nullable { "" } else { " NOT NULL" };
4857 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
4858 })
4859 .collect();
4860 let mut body = cols.join(",\n");
4861 for uc in &t.schema().uniqueness_constraints {
4863 let col_names: Vec<String> = uc
4864 .columns
4865 .iter()
4866 .map(|&p| {
4867 t.schema().columns.get(p).map_or_else(
4868 || alloc::format!("col{p}"),
4869 |c| alloc::format!("`{}`", c.name),
4870 )
4871 })
4872 .collect();
4873 let kw = if uc.is_primary_key {
4874 "PRIMARY KEY"
4875 } else {
4876 "UNIQUE KEY"
4877 };
4878 body.push_str(",\n ");
4879 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
4880 }
4881 for fk in &t.schema().foreign_keys {
4883 let local: Vec<String> = fk
4884 .local_columns
4885 .iter()
4886 .map(|&p| {
4887 t.schema().columns.get(p).map_or_else(
4888 || alloc::format!("col{p}"),
4889 |c| alloc::format!("`{}`", c.name),
4890 )
4891 })
4892 .collect();
4893 let parent_cols: Vec<String> =
4894 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
4895 fk.parent_columns
4896 .iter()
4897 .map(|&p| {
4898 parent.schema().columns.get(p).map_or_else(
4899 || alloc::format!("col{p}"),
4900 |c| alloc::format!("`{}`", c.name),
4901 )
4902 })
4903 .collect()
4904 } else {
4905 fk.parent_columns
4906 .iter()
4907 .map(|p| alloc::format!("col{p}"))
4908 .collect()
4909 };
4910 body.push_str(",\n ");
4911 body.push_str(&alloc::format!(
4912 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
4913 local.join(", "),
4914 fk.parent_table,
4915 parent_cols.join(", ")
4916 ));
4917 }
4918 let ddl = alloc::format!(
4919 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
4920 name,
4921 body
4922 );
4923 let columns = alloc::vec![
4924 ColumnSchema::new("Table", DataType::Text, false),
4925 ColumnSchema::new("Create Table", DataType::Text, false),
4926 ];
4927 let rows = alloc::vec![Row::new(alloc::vec![
4928 Value::Text(name.into()),
4929 Value::Text(ddl),
4930 ])];
4931 Ok(QueryResult::Rows { columns, rows })
4932 }
4933
4934 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
4940 let t = self.active_catalog().get(name).ok_or_else(|| {
4941 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4942 })?;
4943 let columns = alloc::vec![
4944 ColumnSchema::new("Table", DataType::Text, false),
4945 ColumnSchema::new("Non_unique", DataType::Int, false),
4946 ColumnSchema::new("Key_name", DataType::Text, false),
4947 ColumnSchema::new("Seq_in_index", DataType::Int, false),
4948 ColumnSchema::new("Column_name", DataType::Text, false),
4949 ColumnSchema::new("Null", DataType::Text, false),
4950 ColumnSchema::new("Index_type", DataType::Text, false),
4951 ];
4952 let mut rows: Vec<Row> = Vec::new();
4953 for idx in t.indices() {
4954 let col = t
4955 .schema()
4956 .columns
4957 .get(idx.column_position)
4958 .map_or("?".into(), |c| c.name.clone());
4959 let nullable = t
4960 .schema()
4961 .columns
4962 .get(idx.column_position)
4963 .map_or(true, |c| c.nullable);
4964 rows.push(Row::new(alloc::vec![
4965 Value::Text(name.into()),
4966 Value::Int(i32::from(!idx.is_unique)),
4967 Value::Text(idx.name.clone()),
4968 Value::Int(1),
4969 Value::Text(col),
4970 Value::Text(if nullable {
4971 "YES".into()
4972 } else {
4973 String::new()
4974 }),
4975 Value::Text("BTREE".into()),
4976 ]));
4977 }
4978 Ok(QueryResult::Rows { columns, rows })
4979 }
4980
4981 fn exec_show_status(&self) -> QueryResult {
4985 let columns = alloc::vec![
4986 ColumnSchema::new("Variable_name", DataType::Text, false),
4987 ColumnSchema::new("Value", DataType::Text, false),
4988 ];
4989 let pairs: &[(&str, &str)] = &[
4990 ("Uptime", "0"),
4991 ("Threads_connected", "1"),
4992 ("Threads_running", "1"),
4993 ("Questions", "0"),
4994 ("Slow_queries", "0"),
4995 ("Opened_tables", "0"),
4996 ("Innodb_buffer_pool_pages_total", "0"),
4997 ];
4998 let rows: Vec<Row> = pairs
4999 .iter()
5000 .map(|(k, v)| {
5001 Row::new(alloc::vec![
5002 Value::Text((*k).into()),
5003 Value::Text((*v).into())
5004 ])
5005 })
5006 .collect();
5007 QueryResult::Rows { columns, rows }
5008 }
5009
5010 fn exec_show_variables(&self) -> QueryResult {
5013 let columns = alloc::vec![
5014 ColumnSchema::new("Variable_name", DataType::Text, false),
5015 ColumnSchema::new("Value", DataType::Text, false),
5016 ];
5017 let mut rows: Vec<Row> = Vec::new();
5018 let canonical: &[(&str, &str)] = &[
5019 ("version", "8.0.35-spg"),
5020 ("version_comment", "SPG dual-stack engine"),
5021 ("character_set_server", "utf8mb4"),
5022 ("collation_server", "utf8mb4_0900_ai_ci"),
5023 ("max_allowed_packet", "67108864"),
5024 ("autocommit", "ON"),
5025 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
5026 ("time_zone", "SYSTEM"),
5027 ("transaction_isolation", "REPEATABLE-READ"),
5028 ];
5029 for &(k, v) in canonical {
5030 rows.push(Row::new(alloc::vec![
5031 Value::Text(k.into()),
5032 Value::Text(v.into()),
5033 ]));
5034 }
5035 for (k, v) in &self.session_params {
5037 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
5038 rows.push(Row::new(alloc::vec![
5039 Value::Text(k.clone()),
5040 Value::Text(v.clone()),
5041 ]));
5042 }
5043 }
5044 QueryResult::Rows { columns, rows }
5045 }
5046
5047 fn exec_show_processlist(&self) -> QueryResult {
5052 let columns = alloc::vec![
5053 ColumnSchema::new("Id", DataType::Int, false),
5054 ColumnSchema::new("User", DataType::Text, false),
5055 ColumnSchema::new("Host", DataType::Text, false),
5056 ColumnSchema::new("db", DataType::Text, true),
5057 ColumnSchema::new("Command", DataType::Text, false),
5058 ColumnSchema::new("Time", DataType::Int, false),
5059 ColumnSchema::new("State", DataType::Text, true),
5060 ColumnSchema::new("Info", DataType::Text, true),
5061 ];
5062 let rows = alloc::vec![Row::new(alloc::vec![
5063 Value::Int(1),
5064 Value::Text("postgres".into()),
5065 Value::Text("localhost".into()),
5066 Value::Text("postgres".into()),
5067 Value::Text("Query".into()),
5068 Value::Int(0),
5069 Value::Text("executing".into()),
5070 Value::Text("SHOW PROCESSLIST".into()),
5071 ])];
5072 QueryResult::Rows { columns, rows }
5073 }
5074
5075 fn exec_show_databases(&self) -> QueryResult {
5082 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
5083 let names = [
5084 "information_schema",
5085 "mysql",
5086 "performance_schema",
5087 "sys",
5088 "postgres",
5089 ];
5090 let rows: Vec<Row> = names
5091 .iter()
5092 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
5093 .collect();
5094 QueryResult::Rows { columns, rows }
5095 }
5096
5097 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
5100 let table =
5101 self.active_catalog()
5102 .get(table_name)
5103 .ok_or_else(|| StorageError::TableNotFound {
5104 name: table_name.into(),
5105 })?;
5106 let columns = alloc::vec![
5107 ColumnSchema::new("name", DataType::Text, false),
5108 ColumnSchema::new("type", DataType::Text, false),
5109 ColumnSchema::new("nullable", DataType::Bool, false),
5110 ];
5111 let rows: Vec<Row> = table
5112 .schema()
5113 .columns
5114 .iter()
5115 .map(|c| {
5116 Row::new(alloc::vec![
5117 Value::Text(c.name.clone()),
5118 Value::Text(alloc::format!("{}", c.ty)),
5119 Value::Bool(c.nullable),
5120 ])
5121 })
5122 .collect();
5123 Ok(QueryResult::Rows { columns, rows })
5124 }
5125
5126 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5127 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5128 if self.tx_catalogs.contains_key(&tx_id) {
5129 return Err(EngineError::TransactionAlreadyOpen);
5130 }
5131 self.tx_catalogs.insert(
5132 tx_id,
5133 TxState {
5134 catalog: self.catalog.clone(),
5135 savepoints: Vec::new(),
5136 },
5137 );
5138 Ok(QueryResult::CommandOk {
5139 affected: 0,
5140 modified_catalog: false,
5141 })
5142 }
5143
5144 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5145 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5146 let state = self
5147 .tx_catalogs
5148 .remove(&tx_id)
5149 .ok_or(EngineError::NoActiveTransaction)?;
5150 self.catalog = state.catalog;
5151 Ok(QueryResult::CommandOk {
5155 affected: 0,
5156 modified_catalog: true,
5157 })
5158 }
5159
5160 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5161 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5162 if self.tx_catalogs.remove(&tx_id).is_none() {
5163 return Err(EngineError::NoActiveTransaction);
5164 }
5165 Ok(QueryResult::CommandOk {
5167 affected: 0,
5168 modified_catalog: false,
5169 })
5170 }
5171
5172 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5173 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5174 let state = self
5175 .tx_catalogs
5176 .get_mut(&tx_id)
5177 .ok_or(EngineError::NoActiveTransaction)?;
5178 state.savepoints.retain(|(n, _)| n != &name);
5182 let snapshot = state.catalog.clone();
5183 state.savepoints.push((name, snapshot));
5184 Ok(QueryResult::CommandOk {
5185 affected: 0,
5186 modified_catalog: false,
5187 })
5188 }
5189
5190 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5191 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5192 let state = self
5193 .tx_catalogs
5194 .get_mut(&tx_id)
5195 .ok_or(EngineError::NoActiveTransaction)?;
5196 let pos = state
5197 .savepoints
5198 .iter()
5199 .rposition(|(n, _)| n == name)
5200 .ok_or_else(|| {
5201 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5202 })?;
5203 let snapshot = state.savepoints[pos].1.clone();
5207 state.savepoints.truncate(pos + 1);
5208 state.catalog = snapshot;
5209 Ok(QueryResult::CommandOk {
5210 affected: 0,
5211 modified_catalog: false,
5212 })
5213 }
5214
5215 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5216 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5217 let state = self
5218 .tx_catalogs
5219 .get_mut(&tx_id)
5220 .ok_or(EngineError::NoActiveTransaction)?;
5221 let pos = state
5222 .savepoints
5223 .iter()
5224 .rposition(|(n, _)| n == name)
5225 .ok_or_else(|| {
5226 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5227 })?;
5228 state.savepoints.truncate(pos);
5231 Ok(QueryResult::CommandOk {
5232 affected: 0,
5233 modified_catalog: false,
5234 })
5235 }
5236
5237 fn exec_alter_table(
5248 &mut self,
5249 s: spg_sql::ast::AlterTableStatement,
5250 ) -> Result<QueryResult, EngineError> {
5251 let table_name = s.name.clone();
5256 for target in s.targets {
5257 self.exec_alter_table_subaction(&table_name, target)?;
5258 }
5259 Ok(QueryResult::CommandOk {
5260 affected: 0,
5261 modified_catalog: !self.in_transaction(),
5262 })
5263 }
5264
5265 fn exec_alter_table_subaction(
5266 &mut self,
5267 table_name_outer: &str,
5268 target: spg_sql::ast::AlterTableTarget,
5269 ) -> Result<(), EngineError> {
5270 struct S<'a> {
5273 name: &'a str,
5274 }
5275 let s = S {
5276 name: table_name_outer,
5277 };
5278 match target {
5279 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5280 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5281 EngineError::Storage(StorageError::TableNotFound {
5282 name: s.name.into(),
5283 })
5284 })?;
5285 table.schema_mut().hot_tier_bytes = Some(n);
5286 }
5287 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5288 let cols_snapshot = self
5293 .active_catalog()
5294 .get(s.name)
5295 .ok_or_else(|| {
5296 EngineError::Storage(StorageError::TableNotFound {
5297 name: s.name.into(),
5298 })
5299 })?
5300 .schema()
5301 .columns
5302 .clone();
5303 let storage_fk =
5304 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5305 let existing_rows: Vec<Vec<Value>> = self
5308 .active_catalog()
5309 .get(s.name)
5310 .expect("checked above")
5311 .rows()
5312 .iter()
5313 .map(|r| r.values.clone())
5314 .collect();
5315 enforce_fk_inserts(
5316 self.active_catalog(),
5317 s.name,
5318 core::slice::from_ref(&storage_fk),
5319 &existing_rows,
5320 )?;
5321 let table = self
5323 .active_catalog_mut()
5324 .get_mut(s.name)
5325 .expect("checked above");
5326 if let Some(name) = &storage_fk.name
5327 && table
5328 .schema()
5329 .foreign_keys
5330 .iter()
5331 .any(|f| f.name.as_ref() == Some(name))
5332 {
5333 return Err(EngineError::Unsupported(alloc::format!(
5334 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5335 )));
5336 }
5337 table.schema_mut().foreign_keys.push(storage_fk);
5338 }
5339 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5340 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5341 EngineError::Storage(StorageError::TableNotFound {
5342 name: s.name.into(),
5343 })
5344 })?;
5345 let fks = &mut table.schema_mut().foreign_keys;
5346 let before = fks.len();
5347 fks.retain(|f| f.name.as_ref() != Some(&name));
5348 if fks.len() == before && !if_exists {
5349 return Err(EngineError::Unsupported(alloc::format!(
5350 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5351 s.name
5352 )));
5353 }
5354 }
5356 spg_sql::ast::AlterTableTarget::AddColumn {
5357 column,
5358 if_not_exists,
5359 } => {
5360 let clock = self.clock;
5365 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5366 EngineError::Storage(StorageError::TableNotFound {
5367 name: s.name.into(),
5368 })
5369 })?;
5370 if table
5371 .schema()
5372 .columns
5373 .iter()
5374 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5375 {
5376 if if_not_exists {
5377 return Ok(());
5378 }
5379 return Err(EngineError::Unsupported(alloc::format!(
5380 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5381 column.name,
5382 s.name
5383 )));
5384 }
5385 let col_name = column.name.clone();
5386 let nullable = column.nullable;
5387 let has_default = column.default.is_some() || column.auto_increment;
5388 let col_schema = column_def_to_schema(column)?;
5389 let row_count = table.row_count();
5390 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5397 resolve_column_default_free(&col_schema, clock)?
5398 } else if nullable || row_count == 0 {
5399 Value::Null
5400 } else {
5401 return Err(EngineError::Unsupported(alloc::format!(
5402 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5403 when the table has existing rows"
5404 )));
5405 };
5406 table.add_column(col_schema, fill_value);
5407 }
5408 spg_sql::ast::AlterTableTarget::AlterColumnType {
5409 column,
5410 new_type,
5411 using,
5412 } => {
5413 let new_data_type = column_type_to_data_type(new_type);
5419 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5420 EngineError::Storage(StorageError::TableNotFound {
5421 name: s.name.into(),
5422 })
5423 })?;
5424 let col_pos = table
5425 .schema()
5426 .columns
5427 .iter()
5428 .position(|c| c.name.eq_ignore_ascii_case(&column))
5429 .ok_or_else(|| {
5430 EngineError::Unsupported(alloc::format!(
5431 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5432 s.name
5433 ))
5434 })?;
5435 let schema_cols = table.schema().columns.clone();
5436 let ctx = eval::EvalContext::new(&schema_cols, None);
5437 let mut new_values: alloc::vec::Vec<Value> =
5438 alloc::vec::Vec::with_capacity(table.row_count());
5439 for row in table.rows().iter() {
5440 let raw = match &using {
5441 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5442 EngineError::Unsupported(alloc::format!(
5443 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5444 ))
5445 })?,
5446 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5447 };
5448 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5449 new_values.push(coerced);
5450 }
5451 table.schema_mut().columns[col_pos].ty = new_data_type;
5452 for (i, v) in new_values.into_iter().enumerate() {
5453 let mut row_values = table
5454 .rows()
5455 .get(i)
5456 .expect("bounds-checked above")
5457 .values
5458 .clone();
5459 row_values[col_pos] = v;
5460 table.update_row(i, row_values)?;
5461 }
5462 }
5463 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5464 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5470 EngineError::Storage(StorageError::TableNotFound {
5471 name: s.name.into(),
5472 })
5473 })?;
5474 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5475 match tc {
5476 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5477 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5478 let positions: Vec<usize> = columns
5479 .iter()
5480 .map(|c| {
5481 table
5482 .schema()
5483 .columns
5484 .iter()
5485 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5486 .ok_or_else(|| {
5487 EngineError::Unsupported(alloc::format!(
5488 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5489 s.name
5490 ))
5491 })
5492 })
5493 .collect::<Result<Vec<_>, _>>()?;
5494 let already = table
5498 .schema()
5499 .uniqueness_constraints
5500 .iter()
5501 .any(|u| u.columns == positions);
5502 if !already {
5503 table.schema_mut().uniqueness_constraints.push(
5504 spg_storage::UniquenessConstraint {
5505 is_primary_key: is_pk,
5506 columns: positions.clone(),
5507 nulls_not_distinct: false,
5508 },
5509 );
5510 if is_pk {
5512 for p in &positions {
5513 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5514 c.nullable = false;
5515 }
5516 }
5517 }
5518 let leading = &columns[0];
5521 let already_idx = table.indices().iter().any(|idx| {
5522 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5523 && table.schema().columns[idx.column_position].name == *leading
5524 });
5525 if !already_idx {
5526 let suffix = if is_pk { "pkey" } else { "key" };
5527 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5528 let _ = table.add_index(idx_name, leading);
5529 }
5530 }
5531 }
5532 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5533 table.schema_mut().checks.push(alloc::format!("{expr}"));
5534 }
5535 spg_sql::ast::TableConstraint::Index { name, columns } => {
5536 let leading = &columns[0];
5542 let already_idx = table.indices().iter().any(|idx| {
5543 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5544 && table.schema().columns[idx.column_position].name == *leading
5545 });
5546 if !already_idx {
5547 let idx_name = name
5548 .clone()
5549 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5550 let _ = table.add_index(idx_name, leading);
5551 }
5552 }
5553 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5554 for (k, col) in columns.iter().enumerate() {
5562 let already_idx = table.indices().iter().any(|idx| {
5563 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5564 && table.schema().columns[idx.column_position].name == *col
5565 });
5566 if already_idx {
5567 continue;
5568 }
5569 let idx_name = match (&name, columns.len(), k) {
5570 (Some(n), 1, _) => n.clone(),
5571 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5572 (None, _, _) => {
5573 alloc::format!("{}_{col}_ftidx", s.name)
5574 }
5575 };
5576 let _ = table.add_gin_fulltext_index(idx_name, col);
5577 }
5578 }
5579 }
5580 }
5581 spg_sql::ast::AlterTableTarget::DropColumn {
5582 column,
5583 if_exists,
5584 cascade,
5585 } => {
5586 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5593 EngineError::Storage(StorageError::TableNotFound {
5594 name: s.name.into(),
5595 })
5596 })?;
5597 let col_pos = match table
5598 .schema()
5599 .columns
5600 .iter()
5601 .position(|c| c.name.eq_ignore_ascii_case(&column))
5602 {
5603 Some(p) => p,
5604 None => {
5605 if if_exists {
5606 return Ok(());
5607 }
5608 return Err(EngineError::Unsupported(alloc::format!(
5609 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5610 s.name
5611 )));
5612 }
5613 };
5614 let dependent_fks: Vec<usize> = table
5617 .schema()
5618 .foreign_keys
5619 .iter()
5620 .enumerate()
5621 .filter_map(|(i, fk)| {
5622 if fk.local_columns.contains(&col_pos) {
5623 Some(i)
5624 } else {
5625 None
5626 }
5627 })
5628 .collect();
5629 if !dependent_fks.is_empty() && !cascade {
5630 return Err(EngineError::Unsupported(alloc::format!(
5631 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5632 use DROP COLUMN ... CASCADE to remove them"
5633 )));
5634 }
5635 if cascade {
5637 let mut sorted = dependent_fks.clone();
5639 sorted.sort();
5640 sorted.reverse();
5641 let fks = &mut table.schema_mut().foreign_keys;
5642 for i in sorted {
5643 fks.remove(i);
5644 }
5645 }
5646 table.drop_column(col_pos);
5649 }
5650 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5651 let table_name = s.name.to_string();
5659 let trigs = self.active_catalog_mut().triggers_mut();
5660 let mut touched = false;
5661 for t in trigs.iter_mut() {
5662 if !t.table.eq_ignore_ascii_case(&table_name) {
5663 continue;
5664 }
5665 match &which {
5666 spg_sql::ast::TriggerSelector::All => {
5667 t.enabled = enabled;
5668 touched = true;
5669 }
5670 spg_sql::ast::TriggerSelector::Named(name) => {
5671 if t.name.eq_ignore_ascii_case(name) {
5672 t.enabled = enabled;
5673 touched = true;
5674 }
5675 }
5676 }
5677 }
5678 if !touched {
5684 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5685 return Err(EngineError::Unsupported(alloc::format!(
5686 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5687 if enabled { "ENABLE" } else { "DISABLE" },
5688 )));
5689 }
5690 }
5691 }
5692 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5693 let old = s.name.to_string();
5700 self.active_catalog_mut()
5701 .rename_table(&old, &new)
5702 .map_err(EngineError::Storage)?;
5703 }
5704 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5705 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5719 EngineError::Storage(StorageError::TableNotFound {
5720 name: s.name.into(),
5721 })
5722 })?;
5723 let col_pos = table
5724 .schema()
5725 .columns
5726 .iter()
5727 .position(|c| c.name.eq_ignore_ascii_case(&old))
5728 .ok_or_else(|| {
5729 EngineError::Unsupported(alloc::format!(
5730 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5731 s.name
5732 ))
5733 })?;
5734 if table
5736 .schema()
5737 .columns
5738 .iter()
5739 .enumerate()
5740 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5741 {
5742 return Err(EngineError::Unsupported(alloc::format!(
5743 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5744 s.name
5745 )));
5746 }
5747 if old.eq_ignore_ascii_case(&new) {
5751 return Ok(());
5752 }
5753 table.rename_column(col_pos, &new);
5754 let n_cols = table.schema().columns.len();
5760 for i in 0..n_cols {
5761 let rt = table.schema().columns[i].runtime_default.clone();
5762 if let Some(src) = rt {
5763 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5764 table.schema_mut().columns[i].runtime_default = Some(rewritten);
5765 }
5766 }
5767 let checks = table.schema().checks.clone();
5769 let mut new_checks = Vec::with_capacity(checks.len());
5770 for chk in checks {
5771 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
5772 }
5773 table.schema_mut().checks = new_checks;
5774 let n_idx = table.indices().len();
5776 for i in 0..n_idx {
5777 let pred = table.indices()[i].partial_predicate.clone();
5778 if let Some(src) = pred {
5779 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5780 table.set_partial_predicate(i, Some(rewritten));
5784 }
5785 }
5786 let table_name = s.name.to_string();
5789 for trig in self.active_catalog_mut().triggers_mut() {
5790 if !trig.table.eq_ignore_ascii_case(&table_name) {
5791 continue;
5792 }
5793 for c in &mut trig.update_columns {
5794 if c.eq_ignore_ascii_case(&old) {
5795 *c = new.clone();
5796 }
5797 }
5798 }
5799 }
5800 }
5801 Ok(())
5802 }
5803
5804 fn exec_alter_index(
5805 &mut self,
5806 stmt: spg_sql::ast::AlterIndexStatement,
5807 ) -> Result<QueryResult, EngineError> {
5808 let spg_sql::ast::AlterIndexStatement {
5812 name: idx_name,
5813 target,
5814 } = stmt;
5815 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
5819 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
5820 return match renamed {
5821 Ok(()) => Ok(QueryResult::CommandOk {
5822 affected: 0,
5823 modified_catalog: !self.in_transaction(),
5824 }),
5825 Err(StorageError::IndexNotFound { .. }) if if_exists => {
5826 Ok(QueryResult::CommandOk {
5827 affected: 0,
5828 modified_catalog: false,
5829 })
5830 }
5831 Err(e) => Err(EngineError::Storage(e)),
5832 };
5833 }
5834 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
5835 unreachable!("Rename branch returned above");
5836 };
5837 let target = encoding.map(|e| match e {
5838 SqlVecEncoding::F32 => VecEncoding::F32,
5839 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
5840 SqlVecEncoding::F16 => VecEncoding::F16,
5841 });
5842 let table_name = {
5847 let cat = self.active_catalog();
5848 let mut found: Option<String> = None;
5849 for tname in cat.table_names() {
5850 if let Some(t) = cat.get(&tname)
5851 && t.indices().iter().any(|i| i.name == idx_name)
5852 {
5853 found = Some(tname);
5854 break;
5855 }
5856 }
5857 found.ok_or_else(|| {
5858 EngineError::Storage(StorageError::IndexNotFound {
5859 name: idx_name.clone(),
5860 })
5861 })?
5862 };
5863 let table = self
5864 .active_catalog_mut()
5865 .get_mut(&table_name)
5866 .expect("table found above");
5867 table.rebuild_nsw_index(&idx_name, target)?;
5868 self.plan_cache.evict_referencing(&table_name);
5871 Ok(QueryResult::CommandOk {
5872 affected: 0,
5873 modified_catalog: !self.in_transaction(),
5874 })
5875 }
5876
5877 fn exec_create_index(
5878 &mut self,
5879 stmt: CreateIndexStatement,
5880 ) -> Result<QueryResult, EngineError> {
5881 let table = self
5882 .active_catalog_mut()
5883 .get_mut(&stmt.table)
5884 .ok_or_else(|| {
5885 EngineError::Storage(StorageError::TableNotFound {
5886 name: stmt.table.clone(),
5887 })
5888 })?;
5889 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
5891 return Ok(QueryResult::CommandOk {
5892 affected: 0,
5893 modified_catalog: false,
5894 });
5895 }
5896 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
5903 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
5907 Vec::new()
5908 } else {
5909 let schema = table.schema();
5910 stmt.included_columns
5911 .iter()
5912 .map(|c| {
5913 schema.column_position(c).ok_or_else(|| {
5914 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
5915 })
5916 })
5917 .collect::<Result<Vec<_>, _>>()?
5918 };
5919 match stmt.method {
5920 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
5921 IndexMethod::Hnsw => {
5922 if !included_positions.is_empty() {
5923 return Err(EngineError::Unsupported(
5924 "INCLUDE columns are not supported on HNSW indexes".into(),
5925 ));
5926 }
5927 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
5928 }
5929 IndexMethod::Brin => {
5931 if !included_positions.is_empty() {
5932 return Err(EngineError::Unsupported(
5933 "INCLUDE columns are not supported on BRIN indexes".into(),
5934 ));
5935 }
5936 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
5937 }
5938 IndexMethod::Gin => {
5946 if !included_positions.is_empty() {
5947 return Err(EngineError::Unsupported(
5948 "INCLUDE columns are not supported on GIN indexes".into(),
5949 ));
5950 }
5951 let col_pos = table
5952 .schema()
5953 .column_position(&stmt.column)
5954 .ok_or_else(|| {
5955 EngineError::Storage(StorageError::ColumnNotFound {
5956 column: stmt.column.clone(),
5957 })
5958 })?;
5959 let col_ty = table.schema().columns[col_pos].ty;
5960 let is_trgm = stmt
5966 .opclass
5967 .as_deref()
5968 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
5969 if is_trgm
5970 && matches!(
5971 col_ty,
5972 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
5973 )
5974 {
5975 table
5976 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
5977 .map_err(EngineError::Storage)?;
5978 } else if col_ty == spg_storage::DataType::TsVector {
5979 table
5980 .add_gin_index(stmt.name.clone(), &stmt.column)
5981 .map_err(EngineError::Storage)?;
5982 } else {
5983 table.add_index(stmt.name.clone(), &stmt.column)?;
5989 }
5990 }
5991 }
5992 if !included_positions.is_empty()
5993 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
5994 {
5995 idx.included_columns = included_positions;
5996 }
5997 if let Some(pred_expr) = &stmt.partial_predicate {
6005 let canonical = pred_expr.to_string();
6006 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6018 idx.partial_predicate = Some(canonical);
6019 }
6020 }
6021 if let Some(key_expr) = &stmt.expression {
6029 if matches!(
6030 stmt.method,
6031 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
6032 ) {
6033 return Err(EngineError::Unsupported(
6034 "Expression keys are not supported on HNSW or BRIN indexes".into(),
6035 ));
6036 }
6037 let canonical = key_expr.to_string();
6038 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6039 idx.expression = Some(canonical);
6040 }
6041 }
6042 if stmt.is_unique {
6051 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
6052 for col_name in &stmt.extra_columns {
6053 let pos = table
6054 .schema()
6055 .columns
6056 .iter()
6057 .position(|c| c.name.eq_ignore_ascii_case(col_name))
6058 .ok_or_else(|| {
6059 EngineError::Unsupported(alloc::format!(
6060 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
6061 stmt.name,
6062 stmt.table
6063 ))
6064 })?;
6065 extra_positions.push(pos);
6066 }
6067 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6068 idx.is_unique = true;
6069 idx.extra_column_positions = extra_positions;
6070 }
6071 let snapshot_indices = table.indices().to_vec();
6076 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
6077 table.rows().iter().cloned().collect();
6078 let snapshot_schema = table.schema().clone();
6079 let idx_ref = snapshot_indices
6080 .iter()
6081 .find(|i| i.name == stmt.name)
6082 .expect("just-added index");
6083 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
6084 }
6085 self.plan_cache.evict_referencing(&table_name);
6088 Ok(QueryResult::CommandOk {
6089 affected: 0,
6090 modified_catalog: !self.in_transaction(),
6091 })
6092 }
6093
6094 fn reconcile_table_if_not_exists(
6103 &mut self,
6104 stmt: CreateTableStatement,
6105 ) -> Result<QueryResult, EngineError> {
6106 let table_name = stmt.name.clone();
6107 let clock = self.clock;
6108 let existing_col_names: alloc::collections::BTreeSet<String> = self
6109 .active_catalog()
6110 .get(&table_name)
6111 .expect("checked above")
6112 .schema()
6113 .columns
6114 .iter()
6115 .map(|c| c.name.to_ascii_lowercase())
6116 .collect();
6117 let row_count = self
6118 .active_catalog()
6119 .get(&table_name)
6120 .expect("checked above")
6121 .row_count();
6122 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6124 .columns
6125 .iter()
6126 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6127 .cloned()
6128 .collect();
6129 for col_def in new_columns {
6130 let col_name = col_def.name.clone();
6131 let nullable = col_def.nullable;
6132 let has_default = col_def.default.is_some() || col_def.auto_increment;
6133 let col_schema = column_def_to_schema(col_def)?;
6134 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6135 resolve_column_default_free(&col_schema, clock)?
6136 } else if nullable || row_count == 0 {
6137 Value::Null
6138 } else {
6139 return Err(EngineError::Unsupported(alloc::format!(
6140 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6141 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6142 )));
6143 };
6144 let table = self
6145 .active_catalog_mut()
6146 .get_mut(&table_name)
6147 .expect("checked above");
6148 table.add_column(col_schema, fill_value);
6149 }
6150 let table_cols_now = self
6154 .active_catalog()
6155 .get(&table_name)
6156 .expect("checked above")
6157 .schema()
6158 .columns
6159 .clone();
6160 for fk in stmt.foreign_keys {
6161 let all_resolved = fk.columns.iter().all(|c| {
6165 table_cols_now
6166 .iter()
6167 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6168 });
6169 if !all_resolved {
6170 continue;
6171 }
6172 let already_present = {
6173 let table = self
6174 .active_catalog()
6175 .get(&table_name)
6176 .expect("checked above");
6177 table.schema().foreign_keys.iter().any(|f| {
6178 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6179 && f.local_columns.len() == fk.columns.len()
6180 })
6181 };
6182 if already_present {
6183 continue;
6184 }
6185 let storage_fk =
6186 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6187 let table = self
6188 .active_catalog_mut()
6189 .get_mut(&table_name)
6190 .expect("checked above");
6191 table.schema_mut().foreign_keys.push(storage_fk);
6192 }
6193 Ok(QueryResult::CommandOk {
6194 affected: 0,
6195 modified_catalog: !self.in_transaction(),
6196 })
6197 }
6198
6199 fn exec_drop_table(
6201 &mut self,
6202 names: Vec<String>,
6203 if_exists: bool,
6204 ) -> Result<QueryResult, EngineError> {
6205 for name in names {
6206 let dropped = self.active_catalog_mut().drop_table(&name);
6207 if !dropped && !if_exists {
6208 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6209 }
6210 }
6211 Ok(QueryResult::CommandOk {
6212 affected: 0,
6213 modified_catalog: !self.in_transaction(),
6214 })
6215 }
6216
6217 fn exec_drop_index(
6219 &mut self,
6220 name: String,
6221 if_exists: bool,
6222 ) -> Result<QueryResult, EngineError> {
6223 let dropped = self.active_catalog_mut().drop_named_index(&name);
6224 if !dropped && !if_exists {
6225 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6226 }
6227 Ok(QueryResult::CommandOk {
6228 affected: 0,
6229 modified_catalog: !self.in_transaction(),
6230 })
6231 }
6232
6233 fn exec_create_table(
6234 &mut self,
6235 stmt: CreateTableStatement,
6236 ) -> Result<QueryResult, EngineError> {
6237 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6238 return Ok(QueryResult::CommandOk {
6257 affected: 0,
6258 modified_catalog: false,
6259 });
6260 }
6261 let table_name = stmt.name.clone();
6262 let inline_pk_columns: Vec<String> = stmt
6266 .columns
6267 .iter()
6268 .filter(|c| c.is_primary_key)
6269 .map(|c| c.name.clone())
6270 .collect();
6271 let cols = stmt
6277 .columns
6278 .into_iter()
6279 .map(column_def_to_schema)
6280 .collect::<Result<Vec<_>, _>>()?;
6281 let mut cols = cols;
6290 for col in cols.iter_mut() {
6291 let Some(name) = col.user_enum_type.take() else {
6292 continue;
6293 };
6294 let cat = self.active_catalog();
6295 if cat.enum_types().contains_key(&name) {
6296 col.user_enum_type = Some(name);
6297 continue;
6298 }
6299 if let Some(dom) = cat.domain_types().get(&name) {
6300 col.ty = dom.base_type;
6301 col.user_domain_type = Some(name);
6302 if !dom.nullable {
6303 col.nullable = false;
6304 }
6305 continue;
6306 }
6307 return Err(EngineError::Unsupported(alloc::format!(
6308 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6309 col.name,
6310 name
6311 )));
6312 }
6313 for tc in &stmt.table_constraints {
6314 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6315 for col_name in columns {
6316 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6317 col.nullable = false;
6318 }
6319 }
6320 }
6321 }
6322 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6329 Vec::with_capacity(stmt.foreign_keys.len());
6330 for fk in stmt.foreign_keys {
6331 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6338 if !self.foreign_key_checks
6339 && needs_parent
6340 && self.active_catalog().get(&fk.parent_table).is_none()
6341 {
6342 self.pending_foreign_keys.push((table_name.clone(), fk));
6343 continue;
6344 }
6345 fks.push(resolve_foreign_key(
6346 &table_name,
6347 &cols,
6348 fk,
6349 self.active_catalog(),
6350 )?);
6351 }
6352 let mut schema = TableSchema::new(table_name.clone(), cols);
6353 schema.foreign_keys = fks;
6354 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6358 let mut check_exprs: Vec<String> = Vec::new();
6359 for tc in &stmt.table_constraints {
6360 let (is_pk, names, nnd) = match tc {
6361 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6362 (true, columns.clone(), false)
6363 }
6364 spg_sql::ast::TableConstraint::Unique {
6365 columns,
6366 nulls_not_distinct,
6367 ..
6368 } => (false, columns.clone(), *nulls_not_distinct),
6369 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6370 check_exprs.push(alloc::format!("{expr}"));
6373 continue;
6374 }
6375 spg_sql::ast::TableConstraint::Index { .. } => continue,
6381 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6385 };
6386 let mut positions = Vec::with_capacity(names.len());
6387 for n in &names {
6388 let pos = schema
6389 .columns
6390 .iter()
6391 .position(|c| c.name == *n)
6392 .ok_or_else(|| {
6393 EngineError::Unsupported(alloc::format!(
6394 "table constraint references unknown column {n:?}"
6395 ))
6396 })?;
6397 positions.push(pos);
6398 }
6399 uc_storage.push(spg_storage::UniquenessConstraint {
6400 is_primary_key: is_pk,
6401 columns: positions,
6402 nulls_not_distinct: nnd,
6403 });
6404 }
6405 schema.uniqueness_constraints = uc_storage.clone();
6406 schema.checks = check_exprs;
6407 self.active_catalog_mut().create_table(schema)?;
6408 let table = self
6412 .active_catalog_mut()
6413 .get_mut(&table_name)
6414 .expect("just created");
6415 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6416 let idx_name = if inline_pk_columns.len() == 1 {
6417 alloc::format!("{table_name}_pkey")
6418 } else {
6419 alloc::format!("{table_name}_pkey_{i}")
6420 };
6421 if let Err(e) = table.add_index(idx_name, col_name) {
6422 return Err(EngineError::Storage(e));
6423 }
6424 }
6425 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6426 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6431 for (k, col) in columns.iter().enumerate() {
6432 let already = table.indices().iter().any(|idx| {
6433 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6434 && table.schema().columns[idx.column_position].name == *col
6435 });
6436 if already {
6437 continue;
6438 }
6439 let idx_name = match (name.as_ref(), columns.len(), k) {
6440 (Some(n), 1, _) => n.clone(),
6441 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6442 (None, _, _) => {
6443 alloc::format!("{table_name}_{col}_ftidx")
6444 }
6445 };
6446 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6447 return Err(EngineError::Storage(e));
6448 }
6449 }
6450 continue;
6451 }
6452 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6456 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6457 ("pkey", columns, None)
6458 }
6459 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6460 spg_sql::ast::TableConstraint::Index { name, columns } => {
6461 ("idx", columns, name.as_ref())
6462 }
6463 spg_sql::ast::TableConstraint::Check { .. } => continue,
6464 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6466 };
6467 let leading = &names[0];
6468 let already = table.indices().iter().any(|idx| {
6471 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6472 && table.schema().columns[idx.column_position].name == *leading
6473 });
6474 if already {
6475 continue;
6476 }
6477 let idx_name = if let Some(n) = explicit_name {
6478 n.clone()
6479 } else if names.len() == 1 {
6480 alloc::format!("{table_name}_{leading}_{suffix}")
6481 } else {
6482 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6483 };
6484 if let Err(e) = table.add_index(idx_name, leading) {
6485 return Err(EngineError::Storage(e));
6486 }
6487 }
6488 Ok(QueryResult::CommandOk {
6489 affected: 0,
6490 modified_catalog: !self.in_transaction(),
6491 })
6492 }
6493
6494 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6495 for tuple in &mut stmt.rows {
6503 for cell in tuple.iter_mut() {
6504 self.resolve_sequence_calls_in_expr(cell)?;
6505 }
6506 }
6507 if let Some(select) = stmt.select_source.clone() {
6512 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6513 let rows = match select_result {
6514 QueryResult::Rows { rows, .. } => rows,
6515 other => {
6516 return Err(EngineError::Unsupported(alloc::format!(
6517 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6518 )));
6519 }
6520 };
6521 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6522 for row in rows {
6523 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6524 for v in row.values {
6525 tuple.push(value_to_literal_expr_permissive(v)?);
6526 }
6527 materialised.push(tuple);
6528 }
6529 let recurse = InsertStatement {
6530 table: stmt.table,
6531 columns: stmt.columns,
6532 rows: materialised,
6533 select_source: None,
6534 on_conflict: stmt.on_conflict,
6535 returning: stmt.returning,
6536 };
6537 return self.exec_insert(recurse);
6538 }
6539 let clock = self.clock;
6543 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6549 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6550 let trigger_session_cfg: Option<alloc::string::String> = self
6551 .session_params
6552 .get("default_text_search_config")
6553 .cloned();
6554 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6560 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6561 EngineError::Storage(StorageError::TableNotFound {
6562 name: stmt.table.clone(),
6563 })
6564 })?;
6565 preview_table.schema().columns.clone()
6566 };
6567 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6568 pre_borrow_column_meta
6569 .iter()
6570 .enumerate()
6571 .filter_map(|(i, col)| {
6572 if let Some(inline) = &col.inline_enum_variants {
6577 return Some((i, inline.clone()));
6578 }
6579 col.user_enum_type.as_ref().and_then(|ename| {
6580 self.active_catalog()
6581 .enum_types()
6582 .get(ename)
6583 .map(|e| (i, e.labels.clone()))
6584 })
6585 })
6586 .collect();
6587 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6592 pre_borrow_column_meta
6593 .iter()
6594 .enumerate()
6595 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6596 .collect();
6597 let table = self
6598 .active_catalog_mut()
6599 .get_mut(&stmt.table)
6600 .ok_or_else(|| {
6601 EngineError::Storage(StorageError::TableNotFound {
6602 name: stmt.table.clone(),
6603 })
6604 })?;
6605 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6611 let schema_cols_len = column_meta.len();
6612 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6616 None => None, Some(cols) => {
6618 let mut map = alloc::vec![None; schema_cols_len];
6619 for (j, name) in cols.iter().enumerate() {
6620 let idx = column_meta
6621 .iter()
6622 .position(|c| c.name == *name)
6623 .ok_or_else(|| {
6624 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6625 })?;
6626 if map[idx].is_some() {
6627 return Err(EngineError::Storage(StorageError::ArityMismatch {
6628 expected: schema_cols_len,
6629 actual: cols.len(),
6630 }));
6631 }
6632 map[idx] = Some(j);
6633 }
6634 for (i, col) in column_meta.iter().enumerate() {
6638 if map[i].is_none()
6639 && !col.nullable
6640 && col.default.is_none()
6641 && col.runtime_default.is_none()
6642 && !col.auto_increment
6643 {
6644 return Err(EngineError::Storage(StorageError::NullInNotNull {
6645 column: col.name.clone(),
6646 }));
6647 }
6648 }
6649 Some(map)
6650 }
6651 };
6652 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6653 let fks = table.schema().foreign_keys.clone();
6659 let mut affected = 0usize;
6660 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6663 for tuple in stmt.rows {
6664 if tuple.len() != expected_tuple_len {
6665 return Err(EngineError::Storage(StorageError::ArityMismatch {
6666 expected: expected_tuple_len,
6667 actual: tuple.len(),
6668 }));
6669 }
6670 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6674 let raw_tuple: Vec<Value> = tuple
6676 .into_iter()
6677 .map(literal_expr_to_value)
6678 .collect::<Result<_, _>>()?;
6679 let mut out = Vec::with_capacity(schema_cols_len);
6680 for (i, col) in column_meta.iter().enumerate() {
6681 let mut raw = match map[i] {
6682 Some(j) => raw_tuple[j].clone(),
6683 None => resolve_column_default_free(col, clock)?,
6684 };
6685 if col.auto_increment && raw.is_null() {
6686 let next = table.next_auto_value(i).ok_or_else(|| {
6687 EngineError::Unsupported(alloc::format!(
6688 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6689 col.name
6690 ))
6691 })?;
6692 raw = Value::BigInt(next);
6693 }
6694 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6695 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6696 let coerced =
6697 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6698 check_unsigned_range(&coerced, col, i)?;
6699 out.push(coerced);
6700 }
6701 out
6702 } else {
6703 let mut out = Vec::with_capacity(schema_cols_len);
6705 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6706 let mut raw = literal_expr_to_value(expr)?;
6707 if col.auto_increment && raw.is_null() {
6708 let next = table.next_auto_value(i).ok_or_else(|| {
6709 EngineError::Unsupported(alloc::format!(
6710 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6711 col.name
6712 ))
6713 })?;
6714 raw = Value::BigInt(next);
6715 }
6716 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6717 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6718 let coerced =
6719 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6720 check_unsigned_range(&coerced, col, i)?;
6721 out.push(coerced);
6722 }
6723 out
6724 };
6725 all_values.push(values);
6726 }
6727 let uniqueness = table.schema().uniqueness_constraints.clone();
6732 let _ = table;
6733 if !fks.is_empty() {
6734 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6735 }
6736 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6738 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
6740 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
6747 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6754 let mut skipped_count = 0usize;
6755 if let Some(clause) = &stmt.on_conflict {
6756 let conflict_cols = resolve_on_conflict_columns(
6757 self.active_catalog(),
6758 &stmt.table,
6759 clause.target_columns.as_slice(),
6760 )?;
6761 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6762 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6763 for values in all_values {
6764 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6765 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6768 let collides_with_table = !has_null_key
6769 && on_conflict_keys_exist(
6770 self.active_catalog(),
6771 &stmt.table,
6772 &conflict_cols,
6773 &key_tuple,
6774 );
6775 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6776 let collides_with_batch =
6777 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6778 let collides = collides_with_table || collides_with_batch;
6779 match (&clause.action, collides) {
6780 (_, false) => {
6781 seen_keys.push(key_tuple_owned);
6782 kept.push(values);
6783 }
6784 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6785 skipped_count += 1;
6786 }
6787 (
6788 spg_sql::ast::OnConflictAction::Update {
6789 assignments,
6790 where_,
6791 },
6792 true,
6793 ) => {
6794 if !collides_with_table {
6795 skipped_count += 1;
6796 continue;
6797 }
6798 let target_pos = lookup_row_position_by_keys(
6799 self.active_catalog(),
6800 &stmt.table,
6801 &conflict_cols,
6802 &key_tuple,
6803 )
6804 .ok_or_else(|| {
6805 EngineError::Unsupported(
6806 "ON CONFLICT DO UPDATE: conflict detected but row \
6807 position could not be resolved (cold-tier row?)"
6808 .into(),
6809 )
6810 })?;
6811 let updated = apply_on_conflict_assignments(
6812 self.active_catalog(),
6813 &stmt.table,
6814 target_pos,
6815 &values,
6816 assignments,
6817 where_.as_ref(),
6818 )?;
6819 if let Some(new_row) = updated {
6820 pending_updates.push((target_pos, new_row));
6821 } else {
6822 skipped_count += 1;
6823 }
6824 }
6825 }
6826 }
6827 all_values = kept;
6828 }
6829 let table = self
6831 .active_catalog_mut()
6832 .get_mut(&stmt.table)
6833 .ok_or_else(|| {
6834 EngineError::Storage(StorageError::TableNotFound {
6835 name: stmt.table.clone(),
6836 })
6837 })?;
6838 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
6842 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
6846 'rowloop: for values in all_values {
6847 let mut row = Row::new(values);
6848 for fd in &before_insert_triggers {
6853 let (outcome, deferred) = triggers::fire_row_trigger(
6854 fd,
6855 Some(row.clone()),
6856 None,
6857 &stmt.table,
6858 &column_meta,
6859 &[],
6860 trigger_session_cfg.as_deref(),
6861 false,
6862 )
6863 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6864 deferred_embedded.extend(deferred);
6865 match outcome {
6866 triggers::TriggerOutcome::Row(r) => row = r,
6867 triggers::TriggerOutcome::Skip => continue 'rowloop,
6868 }
6869 }
6870 if stmt.returning.is_some() {
6871 returning_rows.push(row.values.clone());
6872 }
6873 let inserted = row.clone();
6876 table.insert(row)?;
6877 affected += 1;
6878 for fd in &after_insert_triggers {
6882 let (_outcome, deferred) = triggers::fire_row_trigger(
6883 fd,
6884 Some(inserted.clone()),
6885 None,
6886 &stmt.table,
6887 &column_meta,
6888 &[],
6889 trigger_session_cfg.as_deref(),
6890 true,
6891 )
6892 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6893 deferred_embedded.extend(deferred);
6894 }
6895 }
6896 for (pos, new_row) in pending_updates {
6900 if stmt.returning.is_some() {
6901 returning_rows.push(new_row.clone());
6902 }
6903 table.update_row(pos, new_row)?;
6904 affected += 1;
6905 }
6906 let _ = skipped_count;
6907 let _ = table;
6913 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
6914 if let Some(items) = &stmt.returning {
6918 return self.build_returning_rows(&stmt.table, items, returning_rows);
6919 }
6920 if !self.in_transaction() && affected > 0 {
6925 self.statistics
6926 .record_modifications(&stmt.table, affected as u64);
6927 }
6928 Ok(QueryResult::CommandOk {
6929 affected,
6930 modified_catalog: !self.in_transaction(),
6931 })
6932 }
6933
6934 fn exec_select_as_of_segment(
6947 &self,
6948 stmt: &SelectStatement,
6949 from: &spg_sql::ast::FromClause,
6950 segment_id: u32,
6951 ) -> Result<QueryResult, EngineError> {
6952 if !from.joins.is_empty()
6955 || stmt.group_by.is_some()
6956 || stmt.having.is_some()
6957 || !stmt.unions.is_empty()
6958 || !stmt.order_by.is_empty()
6959 || stmt.offset.is_some()
6960 || stmt.distinct
6961 || aggregate::uses_aggregate(stmt)
6962 {
6963 return Err(EngineError::Unsupported(
6964 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
6965 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
6966 .into(),
6967 ));
6968 }
6969 let table = self
6970 .active_catalog()
6971 .get(&from.primary.name)
6972 .ok_or_else(|| StorageError::TableNotFound {
6973 name: from.primary.name.clone(),
6974 })?;
6975 let schema = table.schema().clone();
6976 let schema_cols = &schema.columns;
6977 let alias = from
6978 .primary
6979 .alias
6980 .as_deref()
6981 .unwrap_or(from.primary.name.as_str());
6982 let ctx = EvalContext::new(schema_cols, Some(alias));
6983 let seg = self
6984 .active_catalog()
6985 .cold_segment(segment_id)
6986 .ok_or_else(|| {
6987 EngineError::Unsupported(alloc::format!(
6988 "AS OF SEGMENT: cold segment {segment_id} not registered"
6989 ))
6990 })?;
6991 let mut out_rows: Vec<Row> = Vec::new();
6992 let mut limit_remaining: Option<usize> =
6993 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
6994 for (_key, body) in seg.scan() {
6995 let (row, _consumed) =
6996 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
6997 if let Some(where_expr) = &stmt.where_ {
6998 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
6999 if !matches!(cond, Value::Bool(true)) {
7000 continue;
7001 }
7002 }
7003 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7005 out_rows.push(projected);
7006 if let Some(rem) = limit_remaining.as_mut() {
7007 if *rem == 0 {
7008 out_rows.pop();
7009 break;
7010 }
7011 *rem -= 1;
7012 }
7013 }
7014 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7016 Ok(QueryResult::Rows {
7017 columns,
7018 rows: out_rows,
7019 })
7020 }
7021
7022 fn eval_expr_simple(
7027 &self,
7028 expr: &Expr,
7029 row: &Row,
7030 ctx: &EvalContext,
7031 ) -> Result<Value, EngineError> {
7032 let cancel = CancelToken::none();
7033 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7034 }
7035
7036 fn build_returning_rows(
7043 &self,
7044 table_name: &str,
7045 items: &[SelectItem],
7046 mutated_rows: Vec<Vec<Value>>,
7047 ) -> Result<QueryResult, EngineError> {
7048 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7049 EngineError::Storage(StorageError::TableNotFound {
7050 name: table_name.into(),
7051 })
7052 })?;
7053 let schema_cols = table.schema().columns.clone();
7054 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7055 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7056 for values in mutated_rows {
7057 let row = Row::new(values);
7058 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7059 out_rows.push(projected);
7060 }
7061 Ok(QueryResult::Rows {
7062 columns,
7063 rows: out_rows,
7064 })
7065 }
7066
7067 fn project_row_simple(
7071 &self,
7072 row: &Row,
7073 items: &[SelectItem],
7074 schema_cols: &[ColumnSchema],
7075 alias: &str,
7076 ) -> Result<Row, EngineError> {
7077 let ctx = EvalContext::new(schema_cols, Some(alias));
7078 let cancel = CancelToken::none();
7079 let mut out_vals = Vec::new();
7080 for item in items {
7081 match item {
7082 SelectItem::Wildcard => {
7083 out_vals.extend(row.values.iter().cloned());
7084 }
7085 SelectItem::Expr { expr, .. } => {
7086 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7087 out_vals.push(v);
7088 }
7089 }
7090 }
7091 Ok(Row::new(out_vals))
7092 }
7093
7094 fn derive_output_columns(
7099 &self,
7100 items: &[SelectItem],
7101 schema_cols: &[ColumnSchema],
7102 _alias: &str,
7103 ) -> Vec<ColumnSchema> {
7104 let mut out = Vec::new();
7105 for item in items {
7106 match item {
7107 SelectItem::Wildcard => {
7108 out.extend(schema_cols.iter().cloned());
7109 }
7110 SelectItem::Expr { alias, .. } => {
7111 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7112 out.push(ColumnSchema::new(name, DataType::Text, true));
7115 }
7116 }
7117 }
7118 out
7119 }
7120
7121 fn exec_select_cancel(
7122 &self,
7123 stmt: &SelectStatement,
7124 cancel: CancelToken<'_>,
7125 ) -> Result<QueryResult, EngineError> {
7126 cancel.check()?;
7127 if !self.active_catalog().views().is_empty() {
7134 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7135 return self.exec_select_cancel(&rewritten, cancel);
7136 }
7137 }
7138 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7147 return self.exec_select_with_meta_views(stmt, cancel);
7148 }
7149 if let Some(from) = &stmt.from
7158 && let Some(seg_id) = from.primary.as_of_segment
7159 {
7160 return self.exec_select_as_of_segment(stmt, from, seg_id);
7161 }
7162 if let Some(from) = &stmt.from
7166 && from.joins.is_empty()
7167 && stmt.where_.is_none()
7168 && stmt.group_by.is_none()
7169 && stmt.having.is_none()
7170 && stmt.unions.is_empty()
7171 && stmt.order_by.is_empty()
7172 && stmt.limit.is_none()
7173 && stmt.offset.is_none()
7174 && !stmt.distinct
7175 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7176 {
7177 let lower = from.primary.name.to_ascii_lowercase();
7178 match lower.as_str() {
7179 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7180 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7182 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7183 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7184 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7185 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7186 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7187 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7188 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7189 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7190 _ => {}
7191 }
7192 }
7193 if !stmt.ctes.is_empty() {
7201 return self.exec_with_ctes(stmt, cancel);
7202 }
7203 let mut stmt_owned;
7210 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7211 stmt_owned = stmt.clone();
7212 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7213 &stmt_owned
7214 } else {
7215 stmt
7216 };
7217 if stmt_ref.unions.is_empty() {
7218 return self.exec_bare_select_cancel(stmt_ref, cancel);
7219 }
7220 let mut head = stmt_ref.clone();
7225 head.unions = Vec::new();
7226 head.order_by = Vec::new();
7227 head.limit = None;
7228 let QueryResult::Rows { columns, mut rows } =
7229 self.exec_bare_select_cancel(&head, cancel)?
7230 else {
7231 unreachable!("bare SELECT cannot return CommandOk")
7232 };
7233 for (kind, peer) in &stmt_ref.unions {
7234 let QueryResult::Rows {
7235 columns: peer_cols,
7236 rows: peer_rows,
7237 } = self.exec_bare_select_cancel(peer, cancel)?
7238 else {
7239 unreachable!("bare SELECT cannot return CommandOk")
7240 };
7241 if peer_cols.len() != columns.len() {
7242 return Err(EngineError::Unsupported(alloc::format!(
7243 "UNION arity mismatch: head has {} columns, peer has {}",
7244 columns.len(),
7245 peer_cols.len()
7246 )));
7247 }
7248 rows.extend(peer_rows);
7249 if matches!(kind, UnionKind::Distinct) {
7250 rows = dedup_rows(rows);
7251 }
7252 }
7253 if !stmt.order_by.is_empty() {
7256 let synth_ctx = EvalContext::new(&columns, None);
7257 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7258 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7259 for r in rows {
7260 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7261 tagged.push((keys, r));
7262 }
7263 sort_by_keys(&mut tagged, &descs);
7264 rows = tagged.into_iter().map(|(_, r)| r).collect();
7265 }
7266 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7267 Ok(QueryResult::Rows { columns, rows })
7268 }
7269
7270 #[allow(clippy::too_many_lines)]
7271 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7279 &self,
7280 stmt: &SelectStatement,
7281 primary: &TableRef,
7282 cancel: CancelToken<'_>,
7283 ) -> Result<QueryResult, EngineError> {
7284 let expr = primary
7285 .unnest_expr
7286 .as_deref()
7287 .expect("caller guards unnest_expr.is_some()");
7288 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7291 let ctx = EvalContext::new(&empty_schema, None);
7292 let dummy_row = Row::new(alloc::vec::Vec::new());
7293 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7296 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7297 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7298 Value::TextArray(items) => {
7299 let rows = items
7300 .into_iter()
7301 .map(|item| {
7302 Row::new(alloc::vec![match item {
7303 Some(s) => Value::Text(s),
7304 None => Value::Null,
7305 }])
7306 })
7307 .collect();
7308 (DataType::Text, rows)
7309 }
7310 Value::IntArray(items) => {
7311 let rows = items
7312 .into_iter()
7313 .map(|item| {
7314 Row::new(alloc::vec![match item {
7315 Some(n) => Value::Int(n),
7316 None => Value::Null,
7317 }])
7318 })
7319 .collect();
7320 (DataType::Int, rows)
7321 }
7322 Value::BigIntArray(items) => {
7323 let rows = items
7324 .into_iter()
7325 .map(|item| {
7326 Row::new(alloc::vec![match item {
7327 Some(n) => Value::BigInt(n),
7328 None => Value::Null,
7329 }])
7330 })
7331 .collect();
7332 (DataType::BigInt, rows)
7333 }
7334 other => {
7335 return Err(EngineError::Unsupported(alloc::format!(
7336 "unnest() expects an array argument, got {:?}",
7337 other.data_type()
7338 )));
7339 }
7340 };
7341 let alias = primary
7342 .alias
7343 .clone()
7344 .unwrap_or_else(|| "unnest".to_string());
7345 let col_name = primary
7351 .unnest_column_aliases
7352 .first()
7353 .cloned()
7354 .unwrap_or_else(|| alias.clone());
7355 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7356 let schema_cols = alloc::vec![col_schema.clone()];
7357 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7358 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7360 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7361 for row in rows {
7362 cancel.check()?;
7363 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7364 if matches!(v, Value::Bool(true)) {
7365 out.push(row);
7366 }
7367 }
7368 out
7369 } else {
7370 rows
7371 };
7372 if aggregate::uses_aggregate(stmt) {
7378 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7379 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7380 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7381 return Ok(QueryResult::Rows {
7382 columns: agg.columns,
7383 rows: agg.rows,
7384 });
7385 }
7386 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7388 let mut projected_rows: alloc::vec::Vec<Row> =
7389 alloc::vec::Vec::with_capacity(filtered.len());
7390 for row in &filtered {
7391 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7392 for p in &projection {
7393 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7394 }
7395 projected_rows.push(Row::new(vals));
7396 }
7397 let columns: alloc::vec::Vec<ColumnSchema> = projection
7400 .iter()
7401 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7402 .collect();
7403 if !stmt.order_by.is_empty() {
7406 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7407 .iter()
7408 .enumerate()
7409 .map(|(i, r)| -> Result<_, EngineError> {
7410 let keys: Result<Vec<Value>, EngineError> = stmt
7411 .order_by
7412 .iter()
7413 .map(|ob| {
7414 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7415 })
7416 .collect();
7417 Ok((i, keys?))
7418 })
7419 .collect::<Result<_, _>>()?;
7420 indexed.sort_by(|a, b| {
7421 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7422 let mut cmp = value_cmp(ka, kb);
7423 if stmt.order_by[idx].desc {
7424 cmp = cmp.reverse();
7425 }
7426 if cmp != core::cmp::Ordering::Equal {
7427 return cmp;
7428 }
7429 }
7430 core::cmp::Ordering::Equal
7431 });
7432 projected_rows = indexed
7433 .into_iter()
7434 .map(|(i, _)| projected_rows[i].clone())
7435 .collect();
7436 }
7437 if let Some(offset) = stmt.offset_literal() {
7439 let off = (offset as usize).min(projected_rows.len());
7440 projected_rows.drain(..off);
7441 }
7442 if let Some(limit) = stmt.limit_literal() {
7443 projected_rows.truncate(limit as usize);
7444 }
7445 Ok(QueryResult::Rows {
7446 columns,
7447 rows: projected_rows,
7448 })
7449 }
7450
7451 fn exec_select_generate_series(
7462 &self,
7463 stmt: &SelectStatement,
7464 primary: &TableRef,
7465 cancel: CancelToken<'_>,
7466 ) -> Result<QueryResult, EngineError> {
7467 let args = primary
7468 .generate_series_args
7469 .as_ref()
7470 .expect("caller guards generate_series_args.is_some()");
7471 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7472 let ctx = EvalContext::new(&empty_schema, None);
7473 let dummy_row = Row::new(alloc::vec::Vec::new());
7474 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7475 for a in args {
7476 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7477 }
7478 let (elem_dtype, rows) = match arg_values.as_slice() {
7482 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7483 let interval_step = match step {
7484 Value::Interval { .. } => step.clone(),
7485 other => {
7486 return Err(EngineError::Unsupported(alloc::format!(
7487 "generate_series(timestamp, timestamp, …): \
7488 step must be INTERVAL, got {:?}",
7489 other.data_type()
7490 )));
7491 }
7492 };
7493 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7494 (DataType::Timestamp, rows)
7495 }
7496 [start, stop, step]
7497 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7498 {
7499 let s = value_to_i64(start);
7500 let e = value_to_i64(stop);
7501 let st = value_to_i64(step);
7502 let rows = generate_series_integers(s, e, st, &cancel)?;
7503 (DataType::BigInt, rows)
7504 }
7505 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7506 let s = value_to_i64(start);
7507 let e = value_to_i64(stop);
7508 let rows = generate_series_integers(s, e, 1, &cancel)?;
7509 (DataType::BigInt, rows)
7510 }
7511 _ => {
7512 return Err(EngineError::Unsupported(alloc::format!(
7513 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7514 argument shapes; got {:?}",
7515 arg_values
7516 .iter()
7517 .map(|v| v.data_type())
7518 .collect::<alloc::vec::Vec<_>>()
7519 )));
7520 }
7521 };
7522 let alias = primary
7523 .alias
7524 .clone()
7525 .unwrap_or_else(|| "generate_series".to_string());
7526 let col_name = alias.clone();
7527 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7528 let schema_cols = alloc::vec![col_schema.clone()];
7529 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7530 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7532 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7533 for row in rows {
7534 cancel.check()?;
7535 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7536 if matches!(v, Value::Bool(true)) {
7537 out.push(row);
7538 }
7539 }
7540 out
7541 } else {
7542 rows
7543 };
7544 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 for row in &filtered {
7567 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7568 for p in &projection {
7569 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7570 }
7571 projected_rows.push(Row::new(vals));
7572 }
7573 let columns: alloc::vec::Vec<ColumnSchema> = projection
7574 .iter()
7575 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7576 .collect();
7577 if !stmt.order_by.is_empty() {
7579 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7580 .iter()
7581 .enumerate()
7582 .map(|(i, r)| -> Result<_, EngineError> {
7583 let keys: Result<Vec<Value>, EngineError> = stmt
7584 .order_by
7585 .iter()
7586 .map(|ob| {
7587 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7588 })
7589 .collect();
7590 Ok((i, keys?))
7591 })
7592 .collect::<Result<_, _>>()?;
7593 indexed.sort_by(|a, b| {
7594 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7595 let mut cmp = value_cmp(ka, kb);
7596 if stmt.order_by[idx].desc {
7597 cmp = cmp.reverse();
7598 }
7599 if cmp != core::cmp::Ordering::Equal {
7600 return cmp;
7601 }
7602 }
7603 core::cmp::Ordering::Equal
7604 });
7605 projected_rows = indexed
7606 .into_iter()
7607 .map(|(i, _)| projected_rows[i].clone())
7608 .collect();
7609 }
7610 if let Some(offset) = stmt.offset_literal() {
7611 let off = (offset as usize).min(projected_rows.len());
7612 projected_rows.drain(..off);
7613 }
7614 if let Some(limit) = stmt.limit_literal() {
7615 projected_rows.truncate(limit as usize);
7616 }
7617 Ok(QueryResult::Rows {
7618 columns,
7619 rows: projected_rows,
7620 })
7621 }
7622
7623 fn exec_bare_select_cancel(
7624 &self,
7625 stmt: &SelectStatement,
7626 cancel: CancelToken<'_>,
7627 ) -> Result<QueryResult, EngineError> {
7628 check_with_ties_requires_order_by(stmt)?;
7633 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7641 return self.exec_select_with_meta_views(stmt, cancel);
7642 }
7643 if select_has_window(stmt) {
7648 return self.exec_select_with_window(stmt, cancel);
7649 }
7650 let Some(from) = &stmt.from else {
7655 let empty_schema: Vec<ColumnSchema> = Vec::new();
7656 let ctx = self.ev_ctx(&empty_schema, None);
7657 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7658 let dummy_row = Row::new(Vec::new());
7659 let mut values = Vec::with_capacity(projection.len());
7660 for p in &projection {
7661 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7662 }
7663 let columns: Vec<ColumnSchema> = projection
7664 .into_iter()
7665 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7666 .collect();
7667 return Ok(QueryResult::Rows {
7668 columns,
7669 rows: alloc::vec![Row::new(values)],
7670 });
7671 };
7672 if !from.joins.is_empty() {
7676 return self.exec_joined_select(stmt, from);
7677 }
7678 if from.primary.unnest_expr.is_some() {
7685 return self.exec_select_unnest(stmt, &from.primary, cancel);
7686 }
7687 if from.primary.generate_series_args.is_some() {
7693 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7694 }
7695 let primary = &from.primary;
7696 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7697 StorageError::TableNotFound {
7698 name: primary.name.clone(),
7699 }
7700 })?;
7701 let schema_cols = &table.schema().columns;
7702 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7705 let ctx = self.ev_ctx(schema_cols, Some(alias));
7706
7707 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7712 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7713 }
7714
7715 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7723 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7726 .or_else(|| {
7727 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7733 })
7734 .or_else(|| {
7735 try_trgm_seek(w, schema_cols, table, alias)
7741 })
7742 });
7743
7744 if aggregate::uses_aggregate(stmt) {
7747 let mut filtered: Vec<&Row> = Vec::new();
7748 let mut memo = memoize::MemoizeCache::new();
7752 if let Some(rows) = &indexed_rows {
7753 for cow in rows {
7754 let row = cow.as_ref();
7755 if let Some(where_expr) = &stmt.where_ {
7756 let cond = self.eval_expr_with_correlated(
7757 where_expr,
7758 row,
7759 &ctx,
7760 cancel,
7761 Some(&mut memo),
7762 )?;
7763 if !matches!(cond, Value::Bool(true)) {
7764 continue;
7765 }
7766 }
7767 filtered.push(row);
7768 }
7769 } else {
7770 for i in 0..table.row_count() {
7771 let row = &table.rows()[i];
7772 if let Some(where_expr) = &stmt.where_ {
7773 let cond = self.eval_expr_with_correlated(
7774 where_expr,
7775 row,
7776 &ctx,
7777 cancel,
7778 Some(&mut memo),
7779 )?;
7780 if !matches!(cond, Value::Bool(true)) {
7781 continue;
7782 }
7783 }
7784 filtered.push(row);
7785 }
7786 }
7787 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
7788 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7789 return Ok(QueryResult::Rows {
7790 columns: agg.columns,
7791 rows: agg.rows,
7792 });
7793 }
7794
7795 let projection = build_projection(&stmt.items, schema_cols, alias)?;
7796
7797 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
7800 let mut memo = memoize::MemoizeCache::new();
7802 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
7805 if loop_idx.is_multiple_of(256) {
7806 cancel.check()?;
7807 }
7808 if let Some(where_expr) = &stmt.where_ {
7809 let cond =
7810 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
7811 if !matches!(cond, Value::Bool(true)) {
7812 return Ok(());
7813 }
7814 }
7815 let mut values = Vec::with_capacity(projection.len());
7816 for p in &projection {
7817 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7818 }
7819 let order_keys = if stmt.order_by.is_empty() {
7820 Vec::new()
7821 } else {
7822 build_order_keys(&stmt.order_by, row, &ctx)?
7823 };
7824 tagged.push((order_keys, Row::new(values)));
7825 Ok(())
7826 };
7827 if let Some(rows) = &indexed_rows {
7828 for (loop_idx, cow) in rows.iter().enumerate() {
7829 process_row(cow.as_ref(), loop_idx)?;
7830 }
7831 } else {
7832 for i in 0..table.row_count() {
7833 process_row(&table.rows()[i], i)?;
7834 }
7835 }
7836
7837 if !stmt.order_by.is_empty() {
7838 let keep = if stmt.distinct || stmt.limit_with_ties {
7846 None
7847 } else {
7848 stmt.limit_literal()
7849 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
7850 };
7851 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7852 partial_sort_tagged(&mut tagged, keep, &descs);
7853 }
7854
7855 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
7865 apply_offset_and_limit_tagged(
7866 &mut tagged,
7867 stmt.offset_literal(),
7868 stmt.limit_literal(),
7869 true,
7870 );
7871 tagged.into_iter().map(|(_, r)| r).collect()
7872 } else {
7873 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
7874 if stmt.distinct {
7875 output_rows = dedup_rows(output_rows);
7876 }
7877 apply_offset_and_limit(
7878 &mut output_rows,
7879 stmt.offset_literal(),
7880 stmt.limit_literal(),
7881 );
7882 output_rows
7883 };
7884
7885 let columns: Vec<ColumnSchema> = projection
7886 .into_iter()
7887 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7888 .collect();
7889
7890 Ok(QueryResult::Rows {
7891 columns,
7892 rows: output_rows,
7893 })
7894 }
7895
7896 #[allow(clippy::too_many_lines)]
7903 fn materialise_table_ref(
7911 &self,
7912 tref: &TableRef,
7913 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
7914 if let Some(expr) = tref.unnest_expr.as_deref() {
7915 let empty_schema: Vec<ColumnSchema> = Vec::new();
7916 let ctx = EvalContext::new(&empty_schema, None);
7917 let dummy_row = Row::new(Vec::new());
7918 let (elem_dtype, rows) =
7919 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7920 Value::Null => (DataType::Text, Vec::new()),
7921 Value::TextArray(items) => (
7922 DataType::Text,
7923 items
7924 .into_iter()
7925 .map(|item| {
7926 Row::new(alloc::vec![match item {
7927 Some(s) => Value::Text(s),
7928 None => Value::Null,
7929 }])
7930 })
7931 .collect(),
7932 ),
7933 Value::IntArray(items) => (
7934 DataType::Int,
7935 items
7936 .into_iter()
7937 .map(|item| {
7938 Row::new(alloc::vec![match item {
7939 Some(n) => Value::Int(n),
7940 None => Value::Null,
7941 }])
7942 })
7943 .collect(),
7944 ),
7945 Value::BigIntArray(items) => (
7946 DataType::BigInt,
7947 items
7948 .into_iter()
7949 .map(|item| {
7950 Row::new(alloc::vec![match item {
7951 Some(n) => Value::BigInt(n),
7952 None => Value::Null,
7953 }])
7954 })
7955 .collect(),
7956 ),
7957 other => {
7958 return Err(EngineError::Unsupported(alloc::format!(
7959 "unnest() expects an array argument, got {:?}",
7960 other.data_type()
7961 )));
7962 }
7963 };
7964 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
7965 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
7966 return Ok((
7967 rows,
7968 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
7969 ));
7970 }
7971 let table =
7972 self.active_catalog()
7973 .get(&tref.name)
7974 .ok_or_else(|| StorageError::TableNotFound {
7975 name: tref.name.clone(),
7976 })?;
7977 let rows: Vec<Row> = table.rows().iter().cloned().collect();
7978 let cols = table.schema().columns.clone();
7979 Ok((rows, cols))
7980 }
7981
7982 fn build_joined_filtered_rows(
7994 &self,
7995 from: &FromClause,
7996 where_: Option<&Expr>,
7997 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
7998 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
7999 let primary_alias = from
8000 .primary
8001 .alias
8002 .as_deref()
8003 .unwrap_or(from.primary.name.as_str())
8004 .to_string();
8005 #[allow(clippy::type_complexity)]
8012 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8013 for j in &from.joins {
8014 let a = j
8015 .table
8016 .alias
8017 .as_deref()
8018 .unwrap_or(j.table.name.as_str())
8019 .to_string();
8020 if let Some(inner_box) = &j.table.lateral_subquery {
8021 let schema = self.lateral_probe_schema(inner_box)?;
8026 joined.push(JoinedPeer {
8027 eager_rows: None,
8028 cols: schema,
8029 alias: a,
8030 kind: j.kind,
8031 on: j.on.as_ref(),
8032 lateral: Some(inner_box.as_ref()),
8033 });
8034 } else {
8035 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8036 joined.push(JoinedPeer {
8037 eager_rows: Some(rows),
8038 cols,
8039 alias: a,
8040 kind: j.kind,
8041 on: j.on.as_ref(),
8042 lateral: None,
8043 });
8044 }
8045 }
8046 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8047 for col in &primary_cols {
8048 combined_schema.push(ColumnSchema::new(
8049 alloc::format!("{primary_alias}.{}", col.name),
8050 col.ty,
8051 col.nullable,
8052 ));
8053 }
8054 for peer in &joined {
8055 for col in &peer.cols {
8056 combined_schema.push(ColumnSchema::new(
8057 alloc::format!("{}.{}", peer.alias, col.name),
8058 col.ty,
8059 col.nullable,
8060 ));
8061 }
8062 }
8063 let ctx = EvalContext::new(&combined_schema, None);
8064 let mut working: Vec<Row> = primary_rows;
8065 let mut consumed_cols = primary_cols.len();
8068 for peer in &joined {
8069 let right_arity = peer.cols.len();
8070 let mut next: Vec<Row> = Vec::new();
8071 for left in &working {
8072 let mut left_matched = false;
8073 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8074 Some(inner) => {
8075 let outer_schema = &combined_schema[..consumed_cols];
8079 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8080 alloc::borrow::Cow::Owned(rows)
8081 }
8082 None => {
8083 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8084 alloc::borrow::Cow::Borrowed(r.as_slice())
8085 }
8086 };
8087 for right in per_left_rrows.as_ref() {
8088 let mut combined_vals = left.values.clone();
8089 combined_vals.extend(right.values.iter().cloned());
8090 let combined = Row::new(combined_vals);
8091 let keep = if let Some(on_expr) = peer.on {
8092 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
8093 matches!(cond, Value::Bool(true))
8094 } else {
8095 true
8096 };
8097 if keep {
8098 next.push(combined);
8099 left_matched = true;
8100 }
8101 }
8102 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8103 let mut combined_vals = left.values.clone();
8104 for _ in 0..right_arity {
8105 combined_vals.push(Value::Null);
8106 }
8107 next.push(Row::new(combined_vals));
8108 }
8109 }
8110 working = next;
8111 consumed_cols += right_arity;
8112 debug_assert!(consumed_cols <= combined_schema.len());
8113 }
8114 let mut filtered: Vec<Row> = Vec::new();
8115 for row in working {
8116 if let Some(where_expr) = where_ {
8117 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
8118 if !matches!(cond, Value::Bool(true)) {
8119 continue;
8120 }
8121 }
8122 filtered.push(row);
8123 }
8124 Ok((combined_schema, filtered))
8125 }
8126
8127 fn lateral_probe_schema(
8133 &self,
8134 inner: &SelectStatement,
8135 ) -> Result<Vec<ColumnSchema>, EngineError> {
8136 match self.execute_readonly_select_for_lateral_probe(inner) {
8146 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8147 _ => {
8153 let mut out: Vec<ColumnSchema> = Vec::new();
8154 for (i, item) in inner.items.iter().enumerate() {
8155 let name = match item {
8156 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8157 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8158 SelectItem::Wildcard => alloc::format!("col{i}"),
8159 };
8160 out.push(ColumnSchema::new(name, DataType::Text, true));
8161 }
8162 Ok(out)
8163 }
8164 }
8165 }
8166
8167 fn execute_readonly_select_for_lateral_probe(
8173 &self,
8174 inner: &SelectStatement,
8175 ) -> Result<QueryResult, EngineError> {
8176 self.exec_bare_select_cancel(inner, CancelToken::none())
8177 }
8178
8179 fn materialise_lateral_for_outer(
8185 &self,
8186 inner: &SelectStatement,
8187 outer_schema: &[ColumnSchema],
8188 outer_row: &Row,
8189 ) -> Result<Vec<Row>, EngineError> {
8190 let mut substituted = inner.clone();
8191 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8192 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8193 match result {
8194 QueryResult::Rows { rows, .. } => Ok(rows),
8195 _ => Err(EngineError::Unsupported(
8196 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8197 )),
8198 }
8199 }
8200
8201 fn exec_joined_select(
8202 &self,
8203 stmt: &SelectStatement,
8204 from: &FromClause,
8205 ) -> Result<QueryResult, EngineError> {
8206 let (combined_schema, filtered) =
8214 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8215 let ctx = EvalContext::new(&combined_schema, None);
8216 if aggregate::uses_aggregate(stmt) {
8219 let refs: Vec<&Row> = filtered.iter().collect();
8220 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8221 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8222 return Ok(QueryResult::Rows {
8223 columns: agg.columns,
8224 rows: agg.rows,
8225 });
8226 }
8227
8228 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8229 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8230 for row in &filtered {
8231 let mut values = Vec::with_capacity(projection.len());
8232 for p in &projection {
8233 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8234 }
8235 let order_keys = if stmt.order_by.is_empty() {
8236 Vec::new()
8237 } else {
8238 build_order_keys(&stmt.order_by, row, &ctx)?
8239 };
8240 tagged.push((order_keys, Row::new(values)));
8241 }
8242 if !stmt.order_by.is_empty() {
8243 let keep = if stmt.distinct {
8244 None
8245 } else {
8246 stmt.limit_literal()
8247 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8248 };
8249 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8250 partial_sort_tagged(&mut tagged, keep, &descs);
8251 }
8252 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8253 if stmt.distinct {
8254 output_rows = dedup_rows(output_rows);
8255 }
8256 apply_offset_and_limit(
8257 &mut output_rows,
8258 stmt.offset_literal(),
8259 stmt.limit_literal(),
8260 );
8261 let columns: Vec<ColumnSchema> = projection
8262 .into_iter()
8263 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8264 .collect();
8265 Ok(QueryResult::Rows {
8266 columns,
8267 rows: output_rows,
8268 })
8269 }
8270}
8271
8272#[derive(Debug, Clone)]
8275struct ProjectedItem {
8276 expr: Expr,
8277 output_name: String,
8278 ty: DataType,
8279 nullable: bool,
8280}
8281
8282fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8288 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8289 for r in rows {
8290 if !out.iter().any(|seen| seen == &r) {
8291 out.push(r);
8292 }
8293 }
8294 out
8295}
8296
8297fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8301 match v {
8302 Value::Null => Ok(f64::INFINITY),
8303 Value::SmallInt(n) => Ok(f64::from(*n)),
8304 Value::Int(n) => Ok(f64::from(*n)),
8305 Value::Date(d) => Ok(f64::from(*d)),
8306 #[allow(clippy::cast_precision_loss)]
8307 Value::Timestamp(t) => Ok(*t as f64),
8308 #[allow(clippy::cast_precision_loss)]
8311 Value::Time(us) => Ok(*us as f64),
8312 Value::Year(y) => Ok(f64::from(*y)),
8316 #[allow(clippy::cast_precision_loss)]
8321 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8322 #[allow(clippy::cast_precision_loss)]
8324 Value::Money(c) => Ok(*c as f64),
8325 Value::Range { .. } => Err(EngineError::Unsupported(
8328 "ORDER BY of a range value is not supported in v7.17.0".into(),
8329 )),
8330 Value::Hstore(_) => Err(EngineError::Unsupported(
8332 "ORDER BY of a hstore value is not supported".into(),
8333 )),
8334 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8336 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8337 ),
8338 #[allow(clippy::cast_precision_loss)]
8339 Value::Numeric { scaled, scale } => {
8340 let mut divisor = 1.0_f64;
8346 for _ in 0..*scale {
8347 divisor *= 10.0;
8348 }
8349 Ok((*scaled as f64) / divisor)
8350 }
8351 #[allow(clippy::cast_precision_loss)]
8352 Value::BigInt(n) => Ok(*n as f64),
8353 Value::Float(x) => Ok(*x),
8354 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8355 Value::Text(s) => {
8356 let mut key: u64 = 0;
8360 for &b in s.as_bytes().iter().take(8) {
8361 key = (key << 8) | u64::from(b);
8362 }
8363 #[allow(clippy::cast_precision_loss)]
8364 Ok(key as f64)
8365 }
8366 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8367 Err(EngineError::Unsupported(
8368 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8369 ))
8370 }
8371 Value::Interval { .. } => Err(EngineError::Unsupported(
8372 "ORDER BY of an INTERVAL is not supported in v2.11 \
8373 (months vs micros has no single canonical ordering)"
8374 .into(),
8375 )),
8376 Value::Json(_) => Err(EngineError::Unsupported(
8377 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8378 )),
8379 _ => Err(EngineError::Unsupported(
8383 "ORDER BY of this value type is not supported".into(),
8384 )),
8385 }
8386}
8387
8388fn try_nsw_knn(
8402 stmt: &SelectStatement,
8403 table: &Table,
8404 schema_cols: &[ColumnSchema],
8405 table_alias: &str,
8406) -> Option<Vec<usize>> {
8407 if stmt.distinct {
8408 return None;
8409 }
8410 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8411 if limit == 0 {
8412 return None;
8413 }
8414 if stmt.order_by.len() != 1 {
8418 return None;
8419 }
8420 let order = &stmt.order_by[0];
8421 if order.desc {
8425 return None;
8426 }
8427 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8428 return None;
8429 };
8430 let metric = match op {
8431 BinOp::L2Distance => spg_storage::NswMetric::L2,
8432 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8433 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8434 _ => return None,
8435 };
8436 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8438 (lhs.as_ref(), rhs.as_ref())
8439 else {
8440 return None;
8441 };
8442 if let Some(q) = &col.qualifier
8443 && q != table_alias
8444 {
8445 return None;
8446 }
8447 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8448 let query = literal_to_vector(literal)?;
8449 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8450 if let Some(where_expr) = &stmt.where_ {
8451 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8455 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8456 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8457 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8458 for i in candidates {
8459 let row = &table.rows()[i];
8460 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8461 if matches!(cond, Value::Bool(true)) {
8462 kept.push(i);
8463 if kept.len() >= limit {
8464 break;
8465 }
8466 }
8467 }
8468 Some(kept)
8469 } else {
8470 Some(spg_storage::nsw_query(
8471 table, &idx.name, &query, limit, metric,
8472 ))
8473 }
8474}
8475
8476const NSW_OVER_FETCH_FLOOR: usize = 32;
8480
8481fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8484 match e {
8485 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8486 Expr::Cast { expr, .. } => literal_to_vector(expr),
8487 _ => None,
8488 }
8489}
8490
8491fn materialise_in_order(
8495 stmt: &SelectStatement,
8496 table: &Table,
8497 schema_cols: &[ColumnSchema],
8498 table_alias: &str,
8499 ordered_rows: &[usize],
8500) -> Result<QueryResult, EngineError> {
8501 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8502 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8503 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8504 for &i in ordered_rows {
8505 let row = &table.rows()[i];
8506 let mut values = Vec::with_capacity(projection.len());
8507 for p in &projection {
8508 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8509 }
8510 output_rows.push(Row::new(values));
8511 }
8512 apply_offset_and_limit(
8513 &mut output_rows,
8514 stmt.offset_literal(),
8515 stmt.limit_literal(),
8516 );
8517 let columns: Vec<ColumnSchema> = projection
8518 .into_iter()
8519 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8520 .collect();
8521 Ok(QueryResult::Rows {
8522 columns,
8523 rows: output_rows,
8524 })
8525}
8526
8527fn try_index_seek<'a>(
8528 where_expr: &Expr,
8529 schema_cols: &[ColumnSchema],
8530 catalog: &'a Catalog,
8531 table: &'a Table,
8532 table_alias: &str,
8533) -> Option<Vec<Cow<'a, Row>>> {
8534 if let Expr::Binary {
8541 lhs,
8542 op: BinOp::And,
8543 rhs,
8544 } = where_expr
8545 {
8546 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8549 return Some(rows);
8550 }
8551 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8552 }
8553 let Expr::Binary {
8554 lhs,
8555 op: BinOp::Eq,
8556 rhs,
8557 } = where_expr
8558 else {
8559 return None;
8560 };
8561 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8562 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8563 let idx = table.index_on(col_pos)?;
8564 let key = IndexKey::from_value(&value)?;
8565 let locators = idx.lookup_eq(&key);
8566 let table_name = table.schema().name.as_str();
8567 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8575 for loc in locators {
8576 match *loc {
8577 spg_storage::RowLocator::Hot(i) => {
8578 if let Some(row) = table.rows().get(i) {
8579 out.push(Cow::Borrowed(row));
8580 }
8581 }
8582 spg_storage::RowLocator::Cold { segment_id, .. } => {
8583 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8584 out.push(Cow::Owned(row));
8585 }
8586 }
8587 }
8588 }
8589 Some(out)
8590}
8591
8592fn try_gin_seek<'a>(
8611 where_expr: &Expr,
8612 schema_cols: &[ColumnSchema],
8613 catalog: &'a Catalog,
8614 table: &'a Table,
8615 table_alias: &str,
8616 ctx: &eval::EvalContext<'_>,
8617) -> Option<Vec<Cow<'a, Row>>> {
8618 if let Expr::Binary {
8619 lhs,
8620 op: BinOp::And,
8621 rhs,
8622 } = where_expr
8623 {
8624 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8625 return Some(rows);
8626 }
8627 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8628 }
8629 if let Expr::Binary {
8638 lhs,
8639 op: BinOp::Or,
8640 rhs,
8641 } = where_expr
8642 {
8643 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8644 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8645 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8646 out.extend(left);
8647 out.extend(right);
8648 return Some(out);
8649 }
8650 let Expr::Binary {
8651 lhs,
8652 op: BinOp::TsMatch,
8653 rhs,
8654 } = where_expr
8655 else {
8656 return None;
8657 };
8658 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8663 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8664 let idx = table
8671 .indices()
8672 .iter()
8673 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8674 let candidates = gin_query_candidates(idx, &query)?;
8675 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8677 for loc in candidates {
8678 match loc {
8679 spg_storage::RowLocator::Hot(i) => {
8680 if let Some(row) = table.rows().get(i) {
8681 out.push(Cow::Borrowed(row));
8682 }
8683 }
8684 spg_storage::RowLocator::Cold { .. } => {}
8691 }
8692 }
8693 Some(out)
8694}
8695
8696fn try_trgm_seek<'a>(
8712 where_expr: &Expr,
8713 schema_cols: &[ColumnSchema],
8714 table: &'a Table,
8715 table_alias: &str,
8716) -> Option<Vec<Cow<'a, Row>>> {
8717 if let Expr::Binary {
8718 lhs,
8719 op: BinOp::And,
8720 rhs,
8721 } = where_expr
8722 {
8723 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
8724 return Some(rows);
8725 }
8726 return try_trgm_seek(rhs, schema_cols, table, table_alias);
8727 }
8728 let Expr::Like { expr, pattern, .. } = where_expr else {
8734 return None;
8735 };
8736 let Expr::Column(c) = expr.as_ref() else {
8738 return None;
8739 };
8740 if let Some(q) = &c.qualifier
8741 && q != table_alias
8742 {
8743 return None;
8744 }
8745 let col_pos = schema_cols
8746 .iter()
8747 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
8748 let idx = table
8750 .indices()
8751 .iter()
8752 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
8753 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
8757 return None;
8758 };
8759 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
8760 let mut iter = trigrams.iter();
8763 let first = iter.next()?;
8764 let mut acc: Vec<spg_storage::RowLocator> = {
8765 let mut v = idx.gin_trgm_lookup(first).to_vec();
8766 v.sort_by_key(locator_sort_key);
8767 v.dedup_by_key(|l| locator_sort_key(l));
8768 v
8769 };
8770 for tri in iter {
8771 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
8772 next.sort_by_key(locator_sort_key);
8773 next.dedup_by_key(|l| locator_sort_key(l));
8774 let mut merged: Vec<spg_storage::RowLocator> =
8776 Vec::with_capacity(acc.len().min(next.len()));
8777 let (mut i, mut j) = (0usize, 0usize);
8778 while i < acc.len() && j < next.len() {
8779 let lk = locator_sort_key(&acc[i]);
8780 let rk = locator_sort_key(&next[j]);
8781 match lk.cmp(&rk) {
8782 core::cmp::Ordering::Less => i += 1,
8783 core::cmp::Ordering::Greater => j += 1,
8784 core::cmp::Ordering::Equal => {
8785 merged.push(acc[i]);
8786 i += 1;
8787 j += 1;
8788 }
8789 }
8790 }
8791 acc = merged;
8792 if acc.is_empty() {
8793 break;
8794 }
8795 }
8796 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
8797 for loc in acc {
8798 if let spg_storage::RowLocator::Hot(i) = loc
8799 && let Some(row) = table.rows().get(i)
8800 {
8801 out.push(Cow::Borrowed(row));
8802 }
8803 }
8805 Some(out)
8806}
8807
8808fn resolve_gin_col_query(
8814 col_side: &Expr,
8815 query_side: &Expr,
8816 schema_cols: &[ColumnSchema],
8817 table_alias: &str,
8818 ctx: &eval::EvalContext<'_>,
8819) -> Option<(usize, spg_storage::TsQueryAst)> {
8820 let column = match col_side {
8825 Expr::Column(c) => c,
8826 Expr::FunctionCall { name, args }
8827 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
8828 {
8829 if let Expr::Column(c) = args.last().unwrap() {
8833 c
8834 } else {
8835 return None;
8836 }
8837 }
8838 _ => return None,
8839 };
8840 let c = column;
8841 if let Some(q) = &c.qualifier
8842 && q != table_alias
8843 {
8844 return None;
8845 }
8846 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
8847 let empty_row = Row::new(Vec::new());
8851 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
8852 let Value::TsQuery(q) = v else { return None };
8853 Some((pos, q))
8854}
8855
8856fn gin_query_candidates(
8867 idx: &spg_storage::Index,
8868 query: &spg_storage::TsQueryAst,
8869) -> Option<Vec<spg_storage::RowLocator>> {
8870 use spg_storage::TsQueryAst;
8871 match query {
8872 TsQueryAst::Term { word, .. } => {
8873 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
8874 v.sort_by_key(locator_sort_key);
8875 v.dedup_by_key(|l| locator_sort_key(l));
8876 Some(v)
8877 }
8878 TsQueryAst::And(l, r) => {
8879 let mut left = gin_query_candidates(idx, l)?;
8880 let mut right = gin_query_candidates(idx, r)?;
8881 left.sort_by_key(locator_sort_key);
8882 right.sort_by_key(locator_sort_key);
8883 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
8885 let (mut i, mut j) = (0usize, 0usize);
8886 while i < left.len() && j < right.len() {
8887 let lk = locator_sort_key(&left[i]);
8888 let rk = locator_sort_key(&right[j]);
8889 match lk.cmp(&rk) {
8890 core::cmp::Ordering::Less => i += 1,
8891 core::cmp::Ordering::Greater => j += 1,
8892 core::cmp::Ordering::Equal => {
8893 out.push(left[i]);
8894 i += 1;
8895 j += 1;
8896 }
8897 }
8898 }
8899 Some(out)
8900 }
8901 TsQueryAst::Or(l, r) => {
8902 let mut out = gin_query_candidates(idx, l)?;
8903 out.extend(gin_query_candidates(idx, r)?);
8904 out.sort_by_key(locator_sort_key);
8905 out.dedup_by_key(|l| locator_sort_key(l));
8906 Some(out)
8907 }
8908 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
8913 }
8914}
8915
8916fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
8921 match *l {
8922 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
8923 spg_storage::RowLocator::Cold {
8924 segment_id,
8925 page_offset,
8926 } => (1, u64::from(segment_id), u64::from(page_offset)),
8927 }
8928}
8929
8930fn try_pk_predicate(
8942 where_expr: &Expr,
8943 schema_cols: &[ColumnSchema],
8944 table_alias: &str,
8945) -> Option<(usize, IndexKey)> {
8946 let Expr::Binary {
8947 lhs,
8948 op: BinOp::Eq,
8949 rhs,
8950 } = where_expr
8951 else {
8952 return None;
8953 };
8954 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8955 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8956 let key = IndexKey::from_value(&value)?;
8957 Some((col_pos, key))
8958}
8959
8960fn resolve_col_literal_pair(
8961 col_side: &Expr,
8962 lit_side: &Expr,
8963 schema_cols: &[ColumnSchema],
8964 table_alias: &str,
8965) -> Option<(usize, Value)> {
8966 let Expr::Column(c) = col_side else {
8967 return None;
8968 };
8969 if let Some(q) = &c.qualifier
8970 && q != table_alias
8971 {
8972 return None;
8973 }
8974 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
8975 let Expr::Literal(l) = lit_side else {
8976 return None;
8977 };
8978 let v = match l {
8979 Literal::Integer(n) => {
8980 if let Ok(small) = i32::try_from(*n) {
8981 Value::Int(small)
8982 } else {
8983 Value::BigInt(*n)
8984 }
8985 }
8986 Literal::Float(x) => Value::Float(*x),
8987 Literal::String(s) => Value::Text(s.clone()),
8988 Literal::Bool(b) => Value::Bool(*b),
8989 Literal::Null => Value::Null,
8990 Literal::Vector(_) | Literal::Interval { .. } => return None,
8993 };
8994 Some((pos, v))
8995}
8996
8997fn resolve_projection_column<'a>(
9002 c: &ColumnName,
9003 schema_cols: &'a [ColumnSchema],
9004 table_alias: &str,
9005) -> Result<&'a ColumnSchema, EngineError> {
9006 if let Some(q) = &c.qualifier {
9007 let composite = alloc::format!("{q}.{name}", name = c.name);
9008 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9009 return Ok(s);
9010 }
9011 if q == table_alias
9014 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9015 {
9016 return Ok(s);
9017 }
9018 let prefix = alloc::format!("{q}.");
9022 let qualifier_known =
9023 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9024 if !qualifier_known {
9025 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9026 qualifier: q.clone(),
9027 }));
9028 }
9029 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9030 name: c.name.clone(),
9031 }));
9032 }
9033 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9034 return Ok(s);
9035 }
9036 let suffix = alloc::format!(".{name}", name = c.name);
9037 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9038 let first = matches.next();
9039 let extra = matches.next();
9040 match (first, extra) {
9041 (Some(s), None) => Ok(s),
9042 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9043 detail: alloc::format!("ambiguous column reference: {}", c.name),
9044 })),
9045 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9046 name: c.name.clone(),
9047 })),
9048 }
9049}
9050
9051fn build_projection(
9052 items: &[SelectItem],
9053 schema_cols: &[ColumnSchema],
9054 table_alias: &str,
9055) -> Result<Vec<ProjectedItem>, EngineError> {
9056 let mut out = Vec::new();
9057 for item in items {
9058 match item {
9059 SelectItem::Wildcard => {
9060 for col in schema_cols {
9061 out.push(ProjectedItem {
9062 expr: Expr::Column(ColumnName {
9063 qualifier: None,
9064 name: col.name.clone(),
9065 }),
9066 output_name: col.name.clone(),
9067 ty: col.ty,
9068 nullable: col.nullable,
9069 });
9070 }
9071 }
9072 SelectItem::Expr { expr, alias } => {
9073 if let Expr::Column(c) = expr {
9080 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9081 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9082 out.push(ProjectedItem {
9083 expr: expr.clone(),
9084 output_name,
9085 ty: sch.ty,
9086 nullable: sch.nullable,
9087 });
9088 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9089 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9090 out.push(ProjectedItem {
9091 expr: expr.clone(),
9092 output_name,
9093 ty: shape.ty,
9094 nullable: shape.nullable,
9095 });
9096 } else {
9097 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9098 out.push(ProjectedItem {
9099 expr: expr.clone(),
9100 output_name,
9101 ty: DataType::Text,
9102 nullable: true,
9103 });
9104 }
9105 }
9106 }
9107 }
9108 Ok(out)
9109}
9110
9111fn numeric_from_integer(
9115 n: i128,
9116 precision: u8,
9117 scale: u8,
9118 col_name: &str,
9119) -> Result<Value, EngineError> {
9120 let factor = pow10_i128(scale);
9121 let scaled = n.checked_mul(factor).ok_or_else(|| {
9122 EngineError::Unsupported(alloc::format!(
9123 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9124 ))
9125 })?;
9126 check_precision(scaled, precision, col_name)?;
9127 Ok(Value::Numeric { scaled, scale })
9128}
9129
9130#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9133fn numeric_from_float(
9134 x: f64,
9135 precision: u8,
9136 scale: u8,
9137 col_name: &str,
9138) -> Result<Value, EngineError> {
9139 if !x.is_finite() {
9140 return Err(EngineError::Unsupported(alloc::format!(
9141 "cannot store non-finite float in NUMERIC column `{col_name}`"
9142 )));
9143 }
9144 let mut factor = 1.0_f64;
9145 for _ in 0..scale {
9146 factor *= 10.0;
9147 }
9148 let shifted = x * factor;
9153 let biased = if shifted >= 0.0 {
9154 shifted + 0.5
9155 } else {
9156 shifted - 0.5
9157 };
9158 if !(-1e38..=1e38).contains(&biased) {
9161 return Err(EngineError::Unsupported(alloc::format!(
9162 "value {x} overflows NUMERIC range for column `{col_name}`"
9163 )));
9164 }
9165 let scaled = biased as i128;
9166 check_precision(scaled, precision, col_name)?;
9167 Ok(Value::Numeric { scaled, scale })
9168}
9169
9170fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9177 let s = s.trim();
9178 if s.is_empty() {
9179 return None;
9180 }
9181 let (negative, rest) = match s.as_bytes()[0] {
9182 b'-' => (true, &s[1..]),
9183 b'+' => (false, &s[1..]),
9184 _ => (false, s),
9185 };
9186 if rest.is_empty() {
9187 return None;
9188 }
9189 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9193 return None;
9194 }
9195 let (int_part, frac_part) = match rest.find('.') {
9196 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9197 None => (rest, ""),
9198 };
9199 if int_part.is_empty() && frac_part.is_empty() {
9200 return None;
9201 }
9202 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9203 return None;
9204 }
9205 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9206 return None;
9207 }
9208 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9209 if scale_u32 > u32::from(u8::MAX) {
9210 return None;
9211 }
9212 let scale = scale_u32 as u8;
9213 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9214 if negative {
9215 digits.push('-');
9216 }
9217 digits.push_str(int_part);
9218 digits.push_str(frac_part);
9219 let digits = if digits == "-" {
9221 return None;
9222 } else if digits.is_empty() {
9223 "0"
9224 } else {
9225 digits.as_str()
9226 };
9227 let mantissa: i128 = digits.parse().ok()?;
9228 Some((mantissa, scale))
9229}
9230
9231fn numeric_rescale(
9234 scaled: i128,
9235 src_scale: u8,
9236 precision: u8,
9237 dst_scale: u8,
9238 col_name: &str,
9239) -> Result<Value, EngineError> {
9240 let new_scaled = if dst_scale >= src_scale {
9241 let bump = pow10_i128(dst_scale - src_scale);
9242 scaled.checked_mul(bump).ok_or_else(|| {
9243 EngineError::Unsupported(alloc::format!(
9244 "overflow rescaling NUMERIC for column `{col_name}`"
9245 ))
9246 })?
9247 } else {
9248 let drop = pow10_i128(src_scale - dst_scale);
9249 let half = drop / 2;
9250 if scaled >= 0 {
9251 (scaled + half) / drop
9252 } else {
9253 (scaled - half) / drop
9254 }
9255 };
9256 check_precision(new_scaled, precision, col_name)?;
9257 Ok(Value::Numeric {
9258 scaled: new_scaled,
9259 scale: dst_scale,
9260 })
9261}
9262
9263const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9266 if scale == 0 {
9267 return scaled;
9268 }
9269 let factor = pow10_i128_const(scale);
9270 scaled / factor
9271}
9272
9273fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9277 if precision == 0 {
9278 return Ok(());
9279 }
9280 let limit = pow10_i128(precision);
9281 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9282 return Err(EngineError::Unsupported(alloc::format!(
9283 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9284 )));
9285 }
9286 Ok(())
9287}
9288
9289const fn pow10_i128_const(p: u8) -> i128 {
9290 let mut acc: i128 = 1;
9291 let mut i = 0;
9292 while i < p {
9293 acc *= 10;
9294 i += 1;
9295 }
9296 acc
9297}
9298
9299fn pow10_i128(p: u8) -> i128 {
9300 pow10_i128_const(p)
9301}
9302
9303impl Engine {
9318 #[allow(
9329 clippy::too_many_lines,
9330 clippy::type_complexity,
9331 clippy::needless_range_loop
9332 )] fn exec_select_with_window(
9334 &self,
9335 stmt: &SelectStatement,
9336 cancel: CancelToken<'_>,
9337 ) -> Result<QueryResult, EngineError> {
9338 let from = stmt.from.as_ref().ok_or_else(|| {
9339 EngineError::Unsupported("window functions require a FROM clause".into())
9340 })?;
9341 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9350 let filtered: Vec<Row>;
9351 if from.joins.is_empty() {
9352 let primary = &from.primary;
9353 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9354 StorageError::TableNotFound {
9355 name: primary.name.clone(),
9356 }
9357 })?;
9358 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9359 schema_cols_owned = table.schema().columns.clone();
9360 alias_opt = Some(alias);
9361 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9366 let mut owned: Vec<Row> = Vec::new();
9367 for (i, row) in table.rows().iter().enumerate() {
9368 if i.is_multiple_of(256) {
9369 cancel.check()?;
9370 }
9371 if let Some(w) = &stmt.where_ {
9372 let cond = eval::eval_expr(w, row, &ctx)?;
9373 if !matches!(cond, Value::Bool(true)) {
9374 continue;
9375 }
9376 }
9377 owned.push(row.clone());
9378 }
9379 filtered = owned;
9380 } else {
9381 let (combined_schema, rows) =
9382 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9383 schema_cols_owned = combined_schema;
9384 alias_opt = None;
9385 filtered = rows;
9386 }
9387 let schema_cols = &schema_cols_owned;
9388 let ctx = self.ev_ctx(schema_cols, alias_opt);
9389 let alias = alias_opt.unwrap_or("");
9390 let n_rows = filtered.len();
9391 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9395
9396 let mut window_nodes: Vec<Expr> = Vec::new();
9398 for item in &stmt.items {
9399 if let SelectItem::Expr { expr, .. } = item {
9400 collect_window_nodes(expr, &mut window_nodes);
9401 }
9402 }
9403
9404 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9407 for wnode in &window_nodes {
9408 let Expr::WindowFunction {
9409 name,
9410 args,
9411 partition_by,
9412 order_by,
9413 frame,
9414 null_treatment,
9415 } = wnode
9416 else {
9417 unreachable!("collect_window_nodes pushes only WindowFunction");
9418 };
9419 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9421 Vec::with_capacity(n_rows);
9422 for (i, row) in filtered.iter().enumerate() {
9423 let pkey: Vec<Value> = partition_by
9424 .iter()
9425 .map(|p| eval::eval_expr(p, row, &ctx))
9426 .collect::<Result<_, _>>()?;
9427 let okey: Vec<(Value, bool)> = order_by
9428 .iter()
9429 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9430 .collect::<Result<_, _>>()?;
9431 indexed.push((pkey, okey, i));
9432 }
9433 indexed.sort_by(|a, b| {
9436 let p_cmp = partition_key_cmp(&a.0, &b.0);
9437 if p_cmp != core::cmp::Ordering::Equal {
9438 return p_cmp;
9439 }
9440 order_key_cmp(&a.1, &b.1)
9441 });
9442 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9444 let mut p_start = 0;
9445 while p_start < indexed.len() {
9446 let mut p_end = p_start + 1;
9447 while p_end < indexed.len()
9448 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9449 == core::cmp::Ordering::Equal
9450 {
9451 p_end += 1;
9452 }
9453 compute_window_partition(
9455 name,
9456 args,
9457 !order_by.is_empty(),
9458 frame.as_ref(),
9459 *null_treatment,
9460 &indexed[p_start..p_end],
9461 &filtered_refs,
9462 &ctx,
9463 &mut out_vals,
9464 )?;
9465 p_start = p_end;
9466 }
9467 win_vals.push(out_vals);
9468 }
9469
9470 let mut ext_cols = schema_cols.clone();
9472 for i in 0..window_nodes.len() {
9473 ext_cols.push(ColumnSchema::new(
9474 alloc::format!("__win_{i}"),
9475 DataType::Text, true,
9477 ));
9478 }
9479 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9481 for i in 0..n_rows {
9482 let mut values = filtered[i].values.clone();
9483 for w in 0..window_nodes.len() {
9484 values.push(win_vals[w][i].clone());
9485 }
9486 ext_rows.push(Row::new(values));
9487 }
9488 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9490 for item in &stmt.items {
9491 let new_item = match item {
9492 SelectItem::Wildcard => SelectItem::Wildcard,
9493 SelectItem::Expr { expr, alias } => {
9494 let mut e = expr.clone();
9495 rewrite_window_to_columns(&mut e, &window_nodes);
9496 SelectItem::Expr {
9497 expr: e,
9498 alias: alias.clone(),
9499 }
9500 }
9501 };
9502 rewritten_items.push(new_item);
9503 }
9504
9505 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9511 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9512 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9513 for (i, row) in ext_rows.iter().enumerate() {
9514 if i.is_multiple_of(256) {
9515 cancel.check()?;
9516 }
9517 let mut values = Vec::with_capacity(projection.len());
9518 for p in &projection {
9519 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9520 }
9521 let order_keys = if stmt.order_by.is_empty() {
9522 Vec::new()
9523 } else {
9524 let mut keys = Vec::with_capacity(stmt.order_by.len());
9525 for o in &stmt.order_by {
9526 let mut e = o.expr.clone();
9527 rewrite_window_to_columns(&mut e, &window_nodes);
9528 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9529 keys.push(value_to_order_key(&key)?);
9530 }
9531 keys
9532 };
9533 tagged.push((order_keys, Row::new(values)));
9534 }
9535 if !stmt.order_by.is_empty() {
9537 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9538 sort_by_keys(&mut tagged, &descs);
9539 }
9540 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9541 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9542 let final_cols: Vec<ColumnSchema> = projection
9543 .into_iter()
9544 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9545 .collect();
9546 Ok(QueryResult::Rows {
9547 columns: final_cols,
9548 rows: out_rows,
9549 })
9550 }
9551
9552 fn exec_select_with_meta_views(
9569 &self,
9570 stmt: &SelectStatement,
9571 cancel: CancelToken<'_>,
9572 ) -> Result<QueryResult, EngineError> {
9573 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9574 collect_meta_view_names(stmt, &mut needed);
9575 let mut catalog = self.active_catalog().clone();
9576 for view in &needed {
9577 if catalog.get(view).is_some() {
9578 continue;
9579 }
9580 match view.as_str() {
9581 "__spg_info_columns" => {
9582 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9583 materialise_meta_view(&mut catalog, view, schema, rows)?;
9584 }
9585 "__spg_info_tables" => {
9586 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9587 materialise_meta_view(&mut catalog, view, schema, rows)?;
9588 }
9589 "__spg_pg_class" => {
9590 let (schema, rows) = synth_pg_class(self.active_catalog());
9591 materialise_meta_view(&mut catalog, view, schema, rows)?;
9592 }
9593 "__spg_pg_attribute" => {
9594 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9595 materialise_meta_view(&mut catalog, view, schema, rows)?;
9596 }
9597 "__spg_pg_type" => {
9600 let (schema, rows) = synth_pg_type(self.active_catalog());
9601 materialise_meta_view(&mut catalog, view, schema, rows)?;
9602 }
9603 "__spg_pg_proc" => {
9606 let (schema, rows) = synth_pg_proc(self.active_catalog());
9607 materialise_meta_view(&mut catalog, view, schema, rows)?;
9608 }
9609 "__spg_pg_namespace" => {
9612 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9613 materialise_meta_view(&mut catalog, view, schema, rows)?;
9614 }
9615 "__spg_pg_indexes" => {
9618 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9619 materialise_meta_view(&mut catalog, view, schema, rows)?;
9620 }
9621 "__spg_pg_index" => {
9624 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9625 materialise_meta_view(&mut catalog, view, schema, rows)?;
9626 }
9627 "__spg_pg_constraint" => {
9630 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9631 materialise_meta_view(&mut catalog, view, schema, rows)?;
9632 }
9633 "__spg_pg_database" => {
9638 let (schema, rows) = synth_pg_database(self.active_catalog());
9639 materialise_meta_view(&mut catalog, view, schema, rows)?;
9640 }
9641 "__spg_pg_roles" | "__spg_pg_user" => {
9642 let (schema, rows) = synth_pg_roles(self);
9643 materialise_meta_view(&mut catalog, view, schema, rows)?;
9644 }
9645 "__spg_pg_views" => {
9649 let (schema, rows) = synth_pg_views(self.active_catalog());
9650 materialise_meta_view(&mut catalog, view, schema, rows)?;
9651 }
9652 "__spg_pg_matviews" => {
9656 let (schema, _) = synth_pg_views(self.active_catalog());
9657 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9658 }
9659 "__spg_pg_settings" => {
9661 let (schema, rows) = synth_pg_settings(self);
9662 materialise_meta_view(&mut catalog, view, schema, rows)?;
9663 }
9664 "__spg_info_key_column_usage" => {
9666 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9667 materialise_meta_view(&mut catalog, view, schema, rows)?;
9668 }
9669 "__spg_info_referential_constraints" => {
9671 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9672 materialise_meta_view(&mut catalog, view, schema, rows)?;
9673 }
9674 "__spg_info_statistics" => {
9676 let (schema, rows) = synth_info_statistics(self.active_catalog());
9677 materialise_meta_view(&mut catalog, view, schema, rows)?;
9678 }
9679 "__spg_info_routines" => {
9681 let (schema, rows) = synth_info_routines();
9682 materialise_meta_view(&mut catalog, view, schema, rows)?;
9683 }
9684 "__spg_mysql_user" => {
9686 let (schema, rows) = synth_mysql_user(self);
9687 materialise_meta_view(&mut catalog, view, schema, rows)?;
9688 }
9689 "__spg_mysql_db" => {
9690 let (schema, rows) = synth_mysql_db();
9691 materialise_meta_view(&mut catalog, view, schema, rows)?;
9692 }
9693 _ => {
9694 return Err(EngineError::Unsupported(alloc::format!(
9695 "meta view {view:?} is not yet materialisable; \
9696 v7.16.2 covers information_schema.columns / .tables \
9697 and pg_catalog.pg_class / pg_attribute; \
9698 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9699 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
9700 pg_user / pg_views / pg_matviews / pg_settings"
9701 )));
9702 }
9703 }
9704 }
9705 let mut temp = Engine::restore(catalog);
9706 if let Some(c) = self.clock {
9707 temp = temp.with_clock(c);
9708 }
9709 if let Some(f) = self.salt_fn {
9710 temp = temp.with_salt_fn(f);
9711 }
9712 temp.meta_views_materialised = true;
9713 temp.exec_select_cancel(stmt, cancel)
9714 }
9715
9716 fn exec_with_ctes(
9717 &self,
9718 stmt: &SelectStatement,
9719 cancel: CancelToken<'_>,
9720 ) -> Result<QueryResult, EngineError> {
9721 cancel.check()?;
9722 let mut catalog = self.active_catalog().clone();
9723 for cte in &stmt.ctes {
9724 if catalog.get(&cte.name).is_some() {
9725 return Err(EngineError::Unsupported(alloc::format!(
9726 "CTE name {:?} shadows an existing table; rename the CTE",
9727 cte.name
9728 )));
9729 }
9730 let (columns, rows) = if cte.recursive {
9731 self.materialise_recursive_cte(cte, &catalog, cancel)?
9732 } else {
9733 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
9734 let QueryResult::Rows { columns, rows } = body_result else {
9735 return Err(EngineError::Unsupported(alloc::format!(
9736 "CTE {:?} body did not return rows",
9737 cte.name
9738 )));
9739 };
9740 (columns, rows)
9741 };
9742 let inferred = infer_column_types(&columns, &rows);
9747 let mut columns = inferred;
9748 if !cte.column_overrides.is_empty() {
9750 if cte.column_overrides.len() != columns.len() {
9751 return Err(EngineError::Unsupported(alloc::format!(
9752 "CTE {:?} column list has {} names but body returns {} columns",
9753 cte.name,
9754 cte.column_overrides.len(),
9755 columns.len()
9756 )));
9757 }
9758 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9759 col.name.clone_from(name);
9760 }
9761 }
9762 let schema = TableSchema::new(cte.name.clone(), columns);
9763 catalog.create_table(schema).map_err(EngineError::Storage)?;
9764 let table = catalog
9765 .get_mut(&cte.name)
9766 .expect("just-created CTE table must exist");
9767 for row in rows {
9768 table.insert(row).map_err(EngineError::Storage)?;
9769 }
9770 }
9771 let mut body = stmt.clone();
9774 body.ctes = Vec::new();
9775 let mut temp = Engine::restore(catalog);
9776 if let Some(c) = self.clock {
9777 temp = temp.with_clock(c);
9778 }
9779 if let Some(f) = self.salt_fn {
9780 temp = temp.with_salt_fn(f);
9781 }
9782 temp.exec_select_cancel(&body, cancel)
9783 }
9784
9785 #[allow(clippy::too_many_lines)]
9795 fn materialise_recursive_cte(
9796 &self,
9797 cte: &spg_sql::ast::Cte,
9798 base_catalog: &Catalog,
9799 cancel: CancelToken<'_>,
9800 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
9801 const MAX_TOTAL_ROWS: usize = 1_000_000;
9802 const MAX_ITERATIONS: usize = 100_000;
9803 cancel.check()?;
9804 if cte.body.unions.is_empty() {
9805 return Err(EngineError::Unsupported(alloc::format!(
9806 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
9807 cte.name
9808 )));
9809 }
9810 let mut anchor = cte.body.clone();
9812 let union_terms = core::mem::take(&mut anchor.unions);
9813 anchor.ctes = Vec::new();
9814 if select_refers_to(&anchor, &cte.name) {
9816 return Err(EngineError::Unsupported(alloc::format!(
9817 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
9818 cte.name
9819 )));
9820 }
9821 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
9822 let QueryResult::Rows {
9823 columns: anchor_cols,
9824 rows: anchor_rows,
9825 } = anchor_result
9826 else {
9827 return Err(EngineError::Unsupported(alloc::format!(
9828 "WITH RECURSIVE {:?}: anchor did not return rows",
9829 cte.name
9830 )));
9831 };
9832 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
9836 if !cte.column_overrides.is_empty() {
9837 if cte.column_overrides.len() != columns.len() {
9838 return Err(EngineError::Unsupported(alloc::format!(
9839 "CTE {:?} column list has {} names but anchor returns {} columns",
9840 cte.name,
9841 cte.column_overrides.len(),
9842 columns.len()
9843 )));
9844 }
9845 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9846 col.name.clone_from(name);
9847 }
9848 }
9849 let mut all_rows: Vec<Row> = anchor_rows.clone();
9850 let mut working_set: Vec<Row> = anchor_rows;
9851 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
9852 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
9855 if !all_union_all {
9856 for r in &all_rows {
9857 seen.insert(encode_row_key(r));
9858 }
9859 }
9860 for iter in 0..MAX_ITERATIONS {
9861 cancel.check()?;
9862 if working_set.is_empty() {
9863 break;
9864 }
9865 let mut iter_catalog = base_catalog.clone();
9867 let schema = TableSchema::new(cte.name.clone(), columns.clone());
9868 iter_catalog
9869 .create_table(schema)
9870 .map_err(EngineError::Storage)?;
9871 {
9872 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
9873 for row in &working_set {
9874 table.insert(row.clone()).map_err(EngineError::Storage)?;
9875 }
9876 }
9877 let mut iter_engine = Engine::restore(iter_catalog);
9878 if let Some(c) = self.clock {
9879 iter_engine = iter_engine.with_clock(c);
9880 }
9881 if let Some(f) = self.salt_fn {
9882 iter_engine = iter_engine.with_salt_fn(f);
9883 }
9884 let mut next_set: Vec<Row> = Vec::new();
9886 for (_, term) in &union_terms {
9887 let mut term = term.clone();
9888 term.ctes = Vec::new();
9889 let r = iter_engine.exec_select_cancel(&term, cancel)?;
9890 let QueryResult::Rows {
9891 columns: rc,
9892 rows: rs,
9893 } = r
9894 else {
9895 return Err(EngineError::Unsupported(alloc::format!(
9896 "WITH RECURSIVE {:?}: recursive term did not return rows",
9897 cte.name
9898 )));
9899 };
9900 if rc.len() != columns.len() {
9901 return Err(EngineError::Unsupported(alloc::format!(
9902 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
9903 cte.name,
9904 rc.len(),
9905 columns.len()
9906 )));
9907 }
9908 for row in rs {
9909 if !all_union_all {
9910 let key = encode_row_key(&row);
9911 if !seen.insert(key) {
9912 continue;
9913 }
9914 }
9915 next_set.push(row);
9916 }
9917 }
9918 if next_set.is_empty() {
9919 break;
9920 }
9921 all_rows.extend(next_set.iter().cloned());
9922 working_set = next_set;
9923 if all_rows.len() > MAX_TOTAL_ROWS {
9924 return Err(EngineError::Unsupported(alloc::format!(
9925 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
9926 cte.name
9927 )));
9928 }
9929 if iter + 1 == MAX_ITERATIONS {
9930 return Err(EngineError::Unsupported(alloc::format!(
9931 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
9932 cte.name
9933 )));
9934 }
9935 }
9936 Ok((columns, all_rows))
9937 }
9938
9939 fn resolve_select_subqueries(
9940 &self,
9941 stmt: &mut SelectStatement,
9942 cancel: CancelToken<'_>,
9943 ) -> Result<(), EngineError> {
9944 for item in &mut stmt.items {
9945 if let SelectItem::Expr { expr, .. } = item {
9946 self.resolve_expr_subqueries(expr, cancel)?;
9947 }
9948 }
9949 if let Some(w) = &mut stmt.where_ {
9950 self.resolve_expr_subqueries(w, cancel)?;
9951 }
9952 if let Some(gs) = &mut stmt.group_by {
9953 for g in gs {
9954 self.resolve_expr_subqueries(g, cancel)?;
9955 }
9956 }
9957 if let Some(h) = &mut stmt.having {
9958 self.resolve_expr_subqueries(h, cancel)?;
9959 }
9960 for o in &mut stmt.order_by {
9961 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
9962 }
9963 for (_, peer) in &mut stmt.unions {
9964 self.resolve_select_subqueries(peer, cancel)?;
9965 }
9966 Ok(())
9967 }
9968
9969 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
9971 &self,
9972 e: &mut Expr,
9973 cancel: CancelToken<'_>,
9974 ) -> Result<(), EngineError> {
9975 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
9977 *e = replacement;
9978 return Ok(());
9979 }
9980 match e {
9981 Expr::Binary { lhs, rhs, .. } => {
9982 self.resolve_expr_subqueries(lhs, cancel)?;
9983 self.resolve_expr_subqueries(rhs, cancel)?;
9984 }
9985 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
9986 self.resolve_expr_subqueries(expr, cancel)?;
9987 }
9988 Expr::FunctionCall { args, .. } => {
9989 for a in args {
9990 self.resolve_expr_subqueries(a, cancel)?;
9991 }
9992 }
9993 Expr::Like { expr, pattern, .. } => {
9994 self.resolve_expr_subqueries(expr, cancel)?;
9995 self.resolve_expr_subqueries(pattern, cancel)?;
9996 }
9997 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
9998 Expr::WindowFunction {
10001 args,
10002 partition_by,
10003 order_by,
10004 ..
10005 } => {
10006 for a in args {
10007 self.resolve_expr_subqueries(a, cancel)?;
10008 }
10009 for p in partition_by {
10010 self.resolve_expr_subqueries(p, cancel)?;
10011 }
10012 for (e, _) in order_by {
10013 self.resolve_expr_subqueries(e, cancel)?;
10014 }
10015 }
10016 Expr::ScalarSubquery(_)
10020 | Expr::Exists { .. }
10021 | Expr::InSubquery { .. }
10022 | Expr::Literal(_)
10023 | Expr::Placeholder(_)
10024 | Expr::Column(_) => {}
10025 Expr::Array(items) => {
10027 for elem in items {
10028 self.resolve_expr_subqueries(elem, cancel)?;
10029 }
10030 }
10031 Expr::ArraySubscript { target, index } => {
10032 self.resolve_expr_subqueries(target, cancel)?;
10033 self.resolve_expr_subqueries(index, cancel)?;
10034 }
10035 Expr::AnyAll { expr, array, .. } => {
10036 self.resolve_expr_subqueries(expr, cancel)?;
10037 self.resolve_expr_subqueries(array, cancel)?;
10038 }
10039 Expr::Case {
10040 operand,
10041 branches,
10042 else_branch,
10043 } => {
10044 if let Some(o) = operand {
10045 self.resolve_expr_subqueries(o, cancel)?;
10046 }
10047 for (w, t) in branches {
10048 self.resolve_expr_subqueries(w, cancel)?;
10049 self.resolve_expr_subqueries(t, cancel)?;
10050 }
10051 if let Some(e) = else_branch {
10052 self.resolve_expr_subqueries(e, cancel)?;
10053 }
10054 }
10055 }
10056 Ok(())
10057 }
10058
10059 fn eval_expr_with_correlated(
10067 &self,
10068 expr: &Expr,
10069 row: &Row,
10070 ctx: &EvalContext<'_>,
10071 cancel: CancelToken<'_>,
10072 memo: Option<&mut memoize::MemoizeCache>,
10073 ) -> Result<Value, EngineError> {
10074 if !expr_has_subquery(expr) {
10075 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10076 }
10077 let mut e = expr.clone();
10078 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10079 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10080 }
10081
10082 fn resolve_correlated_in_expr(
10083 &self,
10084 e: &mut Expr,
10085 row: &Row,
10086 ctx: &EvalContext<'_>,
10087 cancel: CancelToken<'_>,
10088 mut memo: Option<&mut memoize::MemoizeCache>,
10089 ) -> Result<(), EngineError> {
10090 match e {
10091 Expr::ScalarSubquery(inner) => {
10092 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10097 subquery_repr: alloc::format!("{}", **inner),
10098 outer_values: row.values.clone(),
10099 });
10100 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10101 && let Some(cached) = cache.get(k)
10102 {
10103 *e = value_to_literal_expr(cached)?;
10104 return Ok(());
10105 }
10106 let mut s = (**inner).clone();
10107 substitute_outer_columns(&mut s, row, ctx);
10108 let r = self.exec_select_cancel(&s, cancel)?;
10109 let QueryResult::Rows { rows, .. } = r else {
10110 return Err(EngineError::Unsupported(
10111 "scalar subquery: inner did not return rows".into(),
10112 ));
10113 };
10114 let value = match rows.as_slice() {
10115 [] => Value::Null,
10116 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10117 _ => {
10118 return Err(EngineError::Unsupported(alloc::format!(
10119 "scalar subquery returned {} rows; expected 0 or 1",
10120 rows.len()
10121 )));
10122 }
10123 };
10124 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10125 cache.insert(k, value.clone());
10126 }
10127 *e = value_to_literal_expr(value)?;
10128 }
10129 Expr::Exists { subquery, negated } => {
10130 let mut s = (**subquery).clone();
10131 substitute_outer_columns(&mut s, row, ctx);
10132 let r = self.exec_select_cancel(&s, cancel)?;
10133 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10134 let bit = if *negated { !exists } else { exists };
10135 *e = Expr::Literal(Literal::Bool(bit));
10136 }
10137 Expr::InSubquery {
10138 expr: lhs,
10139 subquery,
10140 negated,
10141 } => {
10142 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10143 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10144 let mut s = (**subquery).clone();
10145 substitute_outer_columns(&mut s, row, ctx);
10146 let r = self.exec_select_cancel(&s, cancel)?;
10147 let QueryResult::Rows { columns, rows, .. } = r else {
10148 return Err(EngineError::Unsupported(
10149 "IN-subquery: inner did not return rows".into(),
10150 ));
10151 };
10152 if columns.len() != 1 {
10153 return Err(EngineError::Unsupported(alloc::format!(
10154 "IN-subquery must project exactly one column; got {}",
10155 columns.len()
10156 )));
10157 }
10158 let mut found = false;
10159 let mut any_null = false;
10160 for r0 in rows {
10161 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10162 if v.is_null() {
10163 any_null = true;
10164 continue;
10165 }
10166 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10167 found = true;
10168 break;
10169 }
10170 }
10171 let bit = if found {
10172 !*negated
10173 } else if any_null {
10174 return Err(EngineError::Unsupported(
10175 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10176 ));
10177 } else {
10178 *negated
10179 };
10180 *e = Expr::Literal(Literal::Bool(bit));
10181 }
10182 Expr::Binary { lhs, rhs, .. } => {
10183 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10184 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10185 }
10186 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10187 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10188 }
10189 Expr::Like { expr, pattern, .. } => {
10190 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10191 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10192 }
10193 Expr::FunctionCall { args, .. } => {
10194 for a in args {
10195 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10196 }
10197 }
10198 Expr::Extract { source, .. } => {
10199 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10200 }
10201 Expr::WindowFunction { .. }
10202 | Expr::Literal(_)
10203 | Expr::Placeholder(_)
10204 | Expr::Column(_) => {}
10205 Expr::Array(items) => {
10207 for elem in items {
10208 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10209 }
10210 }
10211 Expr::ArraySubscript { target, index } => {
10212 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10213 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10214 }
10215 Expr::AnyAll { expr, array, .. } => {
10216 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10217 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10218 }
10219 Expr::Case {
10220 operand,
10221 branches,
10222 else_branch,
10223 } => {
10224 if let Some(o) = operand {
10225 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10226 }
10227 for (w, t) in branches {
10228 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10229 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10230 }
10231 if let Some(e) = else_branch {
10232 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10233 }
10234 }
10235 }
10236 Ok(())
10237 }
10238
10239 fn subquery_replacement(
10240 &self,
10241 e: &Expr,
10242 cancel: CancelToken<'_>,
10243 ) -> Result<Option<Expr>, EngineError> {
10244 match e {
10245 Expr::ScalarSubquery(inner) => {
10246 let mut s = (**inner).clone();
10247 self.resolve_select_subqueries(&mut s, cancel)?;
10250 let r = match self.exec_bare_select_cancel(&s, cancel) {
10251 Ok(r) => r,
10252 Err(e) if is_correlation_error(&e) => return Ok(None),
10253 Err(e) => return Err(e),
10254 };
10255 let QueryResult::Rows { rows, .. } = r else {
10256 return Err(EngineError::Unsupported(
10257 "scalar subquery: inner statement did not return rows".into(),
10258 ));
10259 };
10260 let value = match rows.as_slice() {
10261 [] => Value::Null,
10262 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10263 _ => {
10264 return Err(EngineError::Unsupported(alloc::format!(
10265 "scalar subquery returned {} rows; expected 0 or 1",
10266 rows.len()
10267 )));
10268 }
10269 };
10270 Ok(Some(value_to_literal_expr(value)?))
10271 }
10272 Expr::Exists { subquery, negated } => {
10273 let mut s = (**subquery).clone();
10274 self.resolve_select_subqueries(&mut s, cancel)?;
10275 let r = match self.exec_bare_select_cancel(&s, cancel) {
10276 Ok(r) => r,
10277 Err(e) if is_correlation_error(&e) => return Ok(None),
10278 Err(e) => return Err(e),
10279 };
10280 let exists = match r {
10281 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10282 QueryResult::CommandOk { .. } => false,
10283 };
10284 let bit = if *negated { !exists } else { exists };
10285 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10286 }
10287 Expr::InSubquery {
10288 expr,
10289 subquery,
10290 negated,
10291 } => {
10292 let mut s = (**subquery).clone();
10293 self.resolve_select_subqueries(&mut s, cancel)?;
10294 let r = match self.exec_bare_select_cancel(&s, cancel) {
10295 Ok(r) => r,
10296 Err(e) if is_correlation_error(&e) => return Ok(None),
10297 Err(e) => return Err(e),
10298 };
10299 let QueryResult::Rows { columns, rows, .. } = r else {
10300 return Err(EngineError::Unsupported(
10301 "IN-subquery: inner statement did not return rows".into(),
10302 ));
10303 };
10304 if columns.len() != 1 {
10305 return Err(EngineError::Unsupported(alloc::format!(
10306 "IN-subquery must project exactly one column; got {}",
10307 columns.len()
10308 )));
10309 }
10310 let mut acc: Option<Expr> = None;
10313 for row in rows {
10314 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10315 let lit = value_to_literal_expr(v)?;
10316 let cmp = Expr::Binary {
10317 lhs: expr.clone(),
10318 op: BinOp::Eq,
10319 rhs: Box::new(lit),
10320 };
10321 acc = Some(match acc {
10322 None => cmp,
10323 Some(prev) => Expr::Binary {
10324 lhs: Box::new(prev),
10325 op: BinOp::Or,
10326 rhs: Box::new(cmp),
10327 },
10328 });
10329 }
10330 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10331 let final_expr = if *negated {
10332 Expr::Unary {
10333 op: UnOp::Not,
10334 expr: Box::new(combined),
10335 }
10336 } else {
10337 combined
10338 };
10339 Ok(Some(final_expr))
10340 }
10341 _ => Ok(None),
10342 }
10343 }
10344}
10345
10346fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10358 if let Some(from) = &stmt.from
10359 && from_refers_to(from, target)
10360 {
10361 return true;
10362 }
10363 for (_, peer) in &stmt.unions {
10364 if select_refers_to(peer, target) {
10365 return true;
10366 }
10367 }
10368 for item in &stmt.items {
10369 if let SelectItem::Expr { expr, .. } = item
10370 && expr_refers_to(expr, target)
10371 {
10372 return true;
10373 }
10374 }
10375 if let Some(w) = &stmt.where_
10376 && expr_refers_to(w, target)
10377 {
10378 return true;
10379 }
10380 false
10381}
10382
10383fn from_refers_to(from: &FromClause, target: &str) -> bool {
10384 if from.primary.name.eq_ignore_ascii_case(target) {
10385 return true;
10386 }
10387 from.joins
10388 .iter()
10389 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10390}
10391
10392fn expr_refers_to(e: &Expr, target: &str) -> bool {
10393 match e {
10394 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10395 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10396 select_refers_to(subquery, target)
10397 }
10398 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10399 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10400 expr_refers_to(expr, target)
10401 }
10402 Expr::Like { expr, pattern, .. } => {
10403 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10404 }
10405 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10406 Expr::Extract { source, .. } => expr_refers_to(source, target),
10407 Expr::WindowFunction {
10408 args,
10409 partition_by,
10410 order_by,
10411 ..
10412 } => {
10413 args.iter().any(|a| expr_refers_to(a, target))
10414 || partition_by.iter().any(|p| expr_refers_to(p, target))
10415 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10416 }
10417 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10418 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10419 Expr::ArraySubscript { target: t, index } => {
10420 expr_refers_to(t, target) || expr_refers_to(index, target)
10421 }
10422 Expr::AnyAll { expr, array, .. } => {
10423 expr_refers_to(expr, target) || expr_refers_to(array, target)
10424 }
10425 Expr::Case {
10426 operand,
10427 branches,
10428 else_branch,
10429 } => {
10430 operand
10431 .as_deref()
10432 .is_some_and(|o| expr_refers_to(o, target))
10433 || branches
10434 .iter()
10435 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10436 || else_branch
10437 .as_deref()
10438 .is_some_and(|e| expr_refers_to(e, target))
10439 }
10440 }
10441}
10442
10443fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10454 let s = match ty {
10455 DataType::Int => "integer",
10456 DataType::BigInt => "bigint",
10457 DataType::SmallInt => "smallint",
10458 DataType::Float => "double precision",
10459 DataType::Bool => "boolean",
10460 DataType::Text => "text",
10461 DataType::Varchar(_) => "character varying",
10462 DataType::Date => "date",
10463 DataType::Timestamp => "timestamp without time zone",
10464 DataType::Timestamptz => "timestamp with time zone",
10465 DataType::Json => "jsonb",
10466 DataType::Bytes => "bytea",
10467 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10468 DataType::TsVector => "tsvector",
10469 DataType::TsQuery => "tsquery",
10470 DataType::Vector { .. } => "USER-DEFINED",
10471 _ => "USER-DEFINED",
10474 };
10475 alloc::string::String::from(s)
10476}
10477
10478fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10485 let schema = alloc::vec![
10486 ColumnSchema::new("table_catalog", DataType::Text, false),
10487 ColumnSchema::new("table_schema", DataType::Text, false),
10488 ColumnSchema::new("table_name", DataType::Text, false),
10489 ColumnSchema::new("column_name", DataType::Text, false),
10490 ColumnSchema::new("ordinal_position", DataType::Int, false),
10491 ColumnSchema::new("is_nullable", DataType::Text, false),
10492 ColumnSchema::new("data_type", DataType::Text, false),
10493 ];
10494 let mut rows: Vec<Row> = Vec::new();
10495 for tname in cat.table_names() {
10496 let Some(t) = cat.get(&tname) else { continue };
10497 for (i, col) in t.schema().columns.iter().enumerate() {
10498 #[allow(clippy::cast_possible_wrap)]
10499 let ordinal = (i + 1) as i32;
10500 rows.push(Row::new(alloc::vec![
10501 Value::Text("spg".into()),
10502 Value::Text("public".into()),
10503 Value::Text(tname.clone()),
10504 Value::Text(col.name.clone()),
10505 Value::Int(ordinal),
10506 Value::Text(if col.nullable {
10507 "YES".into()
10508 } else {
10509 "NO".into()
10510 }),
10511 Value::Text(pg_data_type_text(col.ty)),
10512 ]));
10513 }
10514 }
10515 (schema, rows)
10516}
10517
10518fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10520 let schema = alloc::vec![
10521 ColumnSchema::new("table_catalog", DataType::Text, false),
10522 ColumnSchema::new("table_schema", DataType::Text, false),
10523 ColumnSchema::new("table_name", DataType::Text, false),
10524 ColumnSchema::new("table_type", DataType::Text, false),
10525 ];
10526 let mut rows: Vec<Row> = Vec::new();
10527 for tname in cat.table_names() {
10528 rows.push(Row::new(alloc::vec![
10529 Value::Text("spg".into()),
10530 Value::Text("public".into()),
10531 Value::Text(tname.clone()),
10532 Value::Text("BASE TABLE".into()),
10533 ]));
10534 }
10535 (schema, rows)
10536}
10537
10538fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10542 let schema = alloc::vec![
10543 ColumnSchema::new("relname", DataType::Text, false),
10544 ColumnSchema::new("relkind", DataType::Text, false),
10545 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10546 ];
10547 let mut rows: Vec<Row> = Vec::new();
10548 for tname in cat.table_names() {
10549 rows.push(Row::new(alloc::vec![
10550 Value::Text(tname.clone()),
10551 Value::Text("r".into()),
10552 Value::BigInt(2200), ]));
10554 }
10555 (schema, rows)
10556}
10557
10558fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10562 let schema = alloc::vec![
10563 ColumnSchema::new("attrelid", DataType::Text, false),
10564 ColumnSchema::new("attname", DataType::Text, false),
10565 ColumnSchema::new("attnum", DataType::Int, false),
10566 ColumnSchema::new("atttypid", DataType::Text, false),
10567 ColumnSchema::new("attnotnull", DataType::Bool, false),
10568 ];
10569 let mut rows: Vec<Row> = Vec::new();
10570 for tname in cat.table_names() {
10571 let Some(t) = cat.get(&tname) else { continue };
10572 for (i, col) in t.schema().columns.iter().enumerate() {
10573 #[allow(clippy::cast_possible_wrap)]
10574 let ordinal = (i + 1) as i32;
10575 rows.push(Row::new(alloc::vec![
10576 Value::Text(tname.clone()),
10577 Value::Text(col.name.clone()),
10578 Value::Int(ordinal),
10579 Value::Text(pg_data_type_text(col.ty)),
10580 Value::Bool(!col.nullable),
10581 ]));
10582 }
10583 }
10584 (schema, rows)
10585}
10586
10587fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10604 let schema = alloc::vec![
10605 ColumnSchema::new("oid", DataType::BigInt, false),
10606 ColumnSchema::new("typname", DataType::Text, false),
10607 ColumnSchema::new("typlen", DataType::SmallInt, false),
10608 ColumnSchema::new("typtype", DataType::Text, false),
10609 ColumnSchema::new("typcategory", DataType::Text, false),
10610 ColumnSchema::new("typelem", DataType::BigInt, false),
10611 ColumnSchema::new("typarray", DataType::BigInt, false),
10612 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10613 ];
10614 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10617 (16, "bool", 1, "b", "B", 0, 1000),
10619 (17, "bytea", -1, "b", "U", 0, 1001),
10620 (18, "char", 1, "b", "S", 0, 1002),
10621 (19, "name", 64, "b", "S", 0, 1003),
10622 (20, "int8", 8, "b", "N", 0, 1016),
10623 (21, "int2", 2, "b", "N", 0, 1005),
10624 (23, "int4", 4, "b", "N", 0, 1007),
10625 (24, "regproc", 4, "b", "N", 0, 1008),
10626 (25, "text", -1, "b", "S", 0, 1009),
10627 (26, "oid", 4, "b", "N", 0, 1028),
10628 (114, "json", -1, "b", "U", 0, 199),
10629 (142, "xml", -1, "b", "U", 0, 143),
10630 (700, "float4", 4, "b", "N", 0, 1021),
10631 (701, "float8", 8, "b", "N", 0, 1022),
10632 (650, "cidr", -1, "b", "I", 0, 651),
10633 (869, "inet", -1, "b", "I", 0, 1041),
10634 (829, "macaddr", 6, "b", "U", 0, 1040),
10635 (1042, "bpchar", -1, "b", "S", 0, 1014),
10636 (1043, "varchar", -1, "b", "S", 0, 1015),
10637 (1082, "date", 4, "b", "D", 0, 1182),
10638 (1083, "time", 8, "b", "D", 0, 1183),
10639 (1114, "timestamp", 8, "b", "D", 0, 1115),
10640 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10641 (1186, "interval", 16, "b", "T", 0, 1187),
10642 (1266, "timetz", 12, "b", "D", 0, 1270),
10643 (1700, "numeric", -1, "b", "N", 0, 1231),
10644 (790, "money", 8, "b", "N", 0, 791),
10645 (2950, "uuid", 16, "b", "U", 0, 2951),
10646 (3802, "jsonb", -1, "b", "U", 0, 3807),
10647 (3614, "tsvector", -1, "b", "U", 0, 3643),
10648 (3615, "tsquery", -1, "b", "U", 0, 3645),
10649 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10651 (3910, "tsrange", -1, "r", "R", 0, 3911),
10652 (3904, "int4range", -1, "r", "R", 0, 3905),
10653 (3926, "int8range", -1, "r", "R", 0, 3927),
10654 (3906, "numrange", -1, "r", "R", 0, 3907),
10655 (3912, "daterange", -1, "r", "R", 0, 3913),
10656 ];
10657 let arrays: &[(i64, &str, i64)] = &[
10660 (1000, "_bool", 16),
10661 (1001, "_bytea", 17),
10662 (1002, "_char", 18),
10663 (1003, "_name", 19),
10664 (1016, "_int8", 20),
10665 (1005, "_int2", 21),
10666 (1007, "_int4", 23),
10667 (1008, "_regproc", 24),
10668 (1009, "_text", 25),
10669 (1028, "_oid", 26),
10670 (199, "_json", 114),
10671 (143, "_xml", 142),
10672 (1021, "_float4", 700),
10673 (1022, "_float8", 701),
10674 (651, "_cidr", 650),
10675 (1041, "_inet", 869),
10676 (1040, "_macaddr", 829),
10677 (1014, "_bpchar", 1042),
10678 (1015, "_varchar", 1043),
10679 (1182, "_date", 1082),
10680 (1183, "_time", 1083),
10681 (1115, "_timestamp", 1114),
10682 (1185, "_timestamptz", 1184),
10683 (1187, "_interval", 1186),
10684 (1270, "_timetz", 1266),
10685 (1231, "_numeric", 1700),
10686 (791, "_money", 790),
10687 (2951, "_uuid", 2950),
10688 (3807, "_jsonb", 3802),
10689 (3643, "_tsvector", 3614),
10690 (3645, "_tsquery", 3615),
10691 ];
10692 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10693 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10694 rows.push(Row::new(alloc::vec![
10695 Value::BigInt(oid),
10696 Value::Text(name.into()),
10697 Value::SmallInt(len),
10698 Value::Text(ty.into()),
10699 Value::Text(cat.into()),
10700 Value::BigInt(elem),
10701 Value::BigInt(arr),
10702 Value::BigInt(2200),
10703 ]));
10704 }
10705 for &(oid, name, elem) in arrays {
10706 rows.push(Row::new(alloc::vec![
10707 Value::BigInt(oid),
10708 Value::Text(name.into()),
10709 Value::SmallInt(-1),
10710 Value::Text("b".into()),
10711 Value::Text("A".into()),
10712 Value::BigInt(elem),
10713 Value::BigInt(0),
10714 Value::BigInt(2200),
10715 ]));
10716 }
10717 (schema, rows)
10718}
10719
10720fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10734 let schema = alloc::vec![
10735 ColumnSchema::new("oid", DataType::BigInt, false),
10736 ColumnSchema::new("proname", DataType::Text, false),
10737 ColumnSchema::new("pronamespace", DataType::BigInt, false),
10738 ColumnSchema::new("prokind", DataType::Text, false),
10739 ColumnSchema::new("pronargs", DataType::Int, false),
10740 ColumnSchema::new("prorettype", DataType::BigInt, false),
10741 ];
10742 let funcs: &[(i64, &str, &str, i32, i64)] = &[
10745 (1318, "length", "f", 1, 23),
10747 (871, "upper", "f", 1, 25),
10748 (870, "lower", "f", 1, 25),
10749 (936, "substring", "f", 3, 25),
10750 (937, "substring", "f", 2, 25),
10751 (3055, "btrim", "f", 1, 25),
10752 (885, "btrim", "f", 2, 25),
10753 (3056, "ltrim", "f", 1, 25),
10754 (875, "ltrim", "f", 2, 25),
10755 (3057, "rtrim", "f", 1, 25),
10756 (876, "rtrim", "f", 2, 25),
10757 (1397, "abs", "f", 1, 23),
10758 (1396, "abs", "f", 1, 20),
10759 (1606, "round", "f", 1, 1700),
10760 (1707, "round", "f", 2, 1700),
10761 (2308, "ceil", "f", 1, 701),
10762 (2309, "ceiling", "f", 1, 701),
10763 (2310, "floor", "f", 1, 701),
10764 (1376, "sqrt", "f", 1, 701),
10765 (1369, "ln", "f", 1, 701),
10766 (1373, "exp", "f", 1, 701),
10767 (1368, "power", "f", 2, 701),
10768 (2228, "random", "f", 0, 701),
10769 (1299, "now", "f", 0, 1184),
10771 (1274, "current_timestamp", "f", 0, 1184),
10772 (1140, "current_date", "f", 0, 1082),
10773 (2050, "current_time", "f", 0, 1083),
10774 (1158, "date_trunc", "f", 2, 1184),
10775 (1171, "date_part", "f", 2, 701),
10776 (1172, "age", "f", 1, 1186),
10777 (936, "to_char", "f", 2, 25),
10778 (861, "current_database", "f", 0, 19),
10780 (745, "current_user", "f", 0, 19),
10781 (745, "session_user", "f", 0, 19),
10782 (1402, "current_schema", "f", 0, 19),
10783 (3058, "concat", "f", -1, 25),
10785 (3059, "concat_ws", "f", -1, 25),
10786 (3539, "format", "f", -1, 25),
10787 (2877, "pg_typeof", "f", 1, 2206),
10789 (3198, "json_build_object", "f", -1, 114),
10791 (3199, "jsonb_build_object", "f", -1, 3802),
10792 (3271, "json_build_array", "f", -1, 114),
10793 (3272, "jsonb_build_array", "f", -1, 3802),
10794 (3253, "gen_random_uuid", "f", 0, 2950),
10796 (3252, "uuid_generate_v4", "f", 0, 2950),
10797 (2147, "count", "a", 0, 20),
10799 (2803, "count", "a", -1, 20),
10800 (2116, "max", "a", 1, 23),
10801 (2132, "min", "a", 1, 23),
10802 (2108, "sum", "a", 1, 20),
10803 (2100, "avg", "a", 1, 1700),
10804 (2517, "string_agg", "a", 2, 25),
10805 (2747, "array_agg", "a", 1, 1009),
10806 (2517, "bool_and", "a", 1, 16),
10807 (2518, "bool_or", "a", 1, 16),
10808 (2519, "every", "a", 1, 16),
10809 (3100, "row_number", "w", 0, 20),
10811 (3101, "rank", "w", 0, 20),
10812 (3102, "dense_rank", "w", 0, 20),
10813 (3103, "percent_rank", "w", 0, 701),
10814 (3104, "cume_dist", "w", 0, 701),
10815 (3105, "lag", "w", -1, 2283),
10816 (3106, "lead", "w", -1, 2283),
10817 (3107, "first_value", "w", 1, 2283),
10818 (3108, "last_value", "w", 1, 2283),
10819 (3109, "nth_value", "w", 2, 2283),
10820 ];
10821 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
10822 for &(oid, name, kind, nargs, rettype) in funcs {
10823 rows.push(Row::new(alloc::vec![
10824 Value::BigInt(oid),
10825 Value::Text(name.into()),
10826 Value::BigInt(11),
10827 Value::Text(kind.into()),
10828 Value::Int(nargs),
10829 Value::BigInt(rettype),
10830 ]));
10831 }
10832 (schema, rows)
10833}
10834
10835fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
10841 let schema = alloc::vec![
10842 ColumnSchema::new("user", DataType::Text, false),
10843 ColumnSchema::new("host", DataType::Text, false),
10844 ColumnSchema::new("select_priv", DataType::Text, false),
10845 ];
10846 let mut rows: Vec<Row> = Vec::new();
10847 rows.push(Row::new(alloc::vec![
10848 Value::Text("root".into()),
10849 Value::Text("localhost".into()),
10850 Value::Text("Y".into()),
10851 ]));
10852 for (name, _) in engine.users.iter() {
10853 if name != "root" {
10854 rows.push(Row::new(alloc::vec![
10855 Value::Text(name.to_string()),
10856 Value::Text("%".into()),
10857 Value::Text("Y".into()),
10858 ]));
10859 }
10860 }
10861 (schema, rows)
10862}
10863
10864fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
10869 let schema = alloc::vec![
10870 ColumnSchema::new("host", DataType::Text, false),
10871 ColumnSchema::new("db", DataType::Text, false),
10872 ColumnSchema::new("user", DataType::Text, false),
10873 ColumnSchema::new("select_priv", DataType::Text, false),
10874 ];
10875 let rows = alloc::vec![Row::new(alloc::vec![
10876 Value::Text("localhost".into()),
10877 Value::Text("postgres".into()),
10878 Value::Text("root".into()),
10879 Value::Text("Y".into()),
10880 ])];
10881 (schema, rows)
10882}
10883
10884fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10897 let schema = alloc::vec![
10898 ColumnSchema::new("constraint_name", DataType::Text, false),
10899 ColumnSchema::new("table_name", DataType::Text, false),
10900 ColumnSchema::new("column_name", DataType::Text, false),
10901 ColumnSchema::new("ordinal_position", DataType::Int, false),
10902 ColumnSchema::new("referenced_table_name", DataType::Text, false),
10903 ColumnSchema::new("referenced_column_name", DataType::Text, false),
10904 ];
10905 let mut rows: Vec<Row> = Vec::new();
10906 for tname in cat.table_names() {
10907 let Some(t) = cat.get(&tname) else { continue };
10908 let cols = &t.schema().columns;
10909 let col_name_at = |pos: usize| -> String {
10910 cols.get(pos)
10911 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
10912 };
10913 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
10915 let conname = fk
10916 .name
10917 .clone()
10918 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
10919 for (i, (&local, &parent)) in fk
10920 .local_columns
10921 .iter()
10922 .zip(fk.parent_columns.iter())
10923 .enumerate()
10924 {
10925 let parent_name = cat
10926 .get(&fk.parent_table)
10927 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
10928 .unwrap_or_else(|| alloc::format!("col{parent}"));
10929 #[allow(clippy::cast_possible_wrap)]
10930 let ordinal = (i + 1) as i32;
10931 rows.push(Row::new(alloc::vec![
10932 Value::Text(conname.clone()),
10933 Value::Text(tname.clone()),
10934 Value::Text(col_name_at(local)),
10935 Value::Int(ordinal),
10936 Value::Text(fk.parent_table.clone()),
10937 Value::Text(parent_name),
10938 ]));
10939 }
10940 }
10941 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
10943 let conname = if uc.is_primary_key {
10944 alloc::format!("{}_pkey", tname)
10945 } else {
10946 alloc::format!("{}_uniq{ci}", tname)
10947 };
10948 for (i, &local) in uc.columns.iter().enumerate() {
10949 #[allow(clippy::cast_possible_wrap)]
10950 let ordinal = (i + 1) as i32;
10951 rows.push(Row::new(alloc::vec![
10952 Value::Text(conname.clone()),
10953 Value::Text(tname.clone()),
10954 Value::Text(col_name_at(local)),
10955 Value::Int(ordinal),
10956 Value::Text(String::new()),
10957 Value::Text(String::new()),
10958 ]));
10959 }
10960 }
10961 }
10962 (schema, rows)
10963}
10964
10965fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10968 let schema = alloc::vec![
10969 ColumnSchema::new("constraint_name", DataType::Text, false),
10970 ColumnSchema::new("table_name", DataType::Text, false),
10971 ColumnSchema::new("referenced_table_name", DataType::Text, false),
10972 ColumnSchema::new("update_rule", DataType::Text, false),
10973 ColumnSchema::new("delete_rule", DataType::Text, false),
10974 ];
10975 fn rule_name(a: spg_storage::FkAction) -> &'static str {
10976 match a {
10977 spg_storage::FkAction::Cascade => "CASCADE",
10978 spg_storage::FkAction::SetNull => "SET NULL",
10979 spg_storage::FkAction::SetDefault => "SET DEFAULT",
10980 spg_storage::FkAction::Restrict => "RESTRICT",
10981 spg_storage::FkAction::NoAction => "NO ACTION",
10982 }
10983 }
10984 let mut rows: Vec<Row> = Vec::new();
10985 for tname in cat.table_names() {
10986 let Some(t) = cat.get(&tname) else { continue };
10987 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
10988 let conname = fk
10989 .name
10990 .clone()
10991 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
10992 rows.push(Row::new(alloc::vec![
10993 Value::Text(conname),
10994 Value::Text(tname.clone()),
10995 Value::Text(fk.parent_table.clone()),
10996 Value::Text(rule_name(fk.on_update).into()),
10997 Value::Text(rule_name(fk.on_delete).into()),
10998 ]));
10999 }
11000 }
11001 (schema, rows)
11002}
11003
11004fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11008 let schema = alloc::vec![
11009 ColumnSchema::new("table_name", DataType::Text, false),
11010 ColumnSchema::new("index_name", DataType::Text, false),
11011 ColumnSchema::new("column_name", DataType::Text, false),
11012 ColumnSchema::new("seq_in_index", DataType::Int, false),
11013 ColumnSchema::new("non_unique", DataType::Int, false),
11014 ColumnSchema::new("index_type", DataType::Text, false),
11015 ];
11016 let mut rows: Vec<Row> = Vec::new();
11017 for tname in cat.table_names() {
11018 let Some(t) = cat.get(&tname) else { continue };
11019 for idx in t.indices() {
11020 let col = t
11021 .schema()
11022 .columns
11023 .get(idx.column_position)
11024 .map_or("?".into(), |c| c.name.clone());
11025 rows.push(Row::new(alloc::vec![
11026 Value::Text(tname.clone()),
11027 Value::Text(idx.name.clone()),
11028 Value::Text(col),
11029 Value::Int(1),
11030 Value::Int(i32::from(!idx.is_unique)),
11031 Value::Text("BTREE".into()),
11032 ]));
11033 }
11034 }
11035 (schema, rows)
11036}
11037
11038fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11042 let schema = alloc::vec![
11043 ColumnSchema::new("routine_name", DataType::Text, false),
11044 ColumnSchema::new("routine_type", DataType::Text, false),
11045 ColumnSchema::new("data_type", DataType::Text, false),
11046 ];
11047 (schema, Vec::new())
11048}
11049
11050fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11065 let schema = alloc::vec![
11066 ColumnSchema::new("conname", DataType::Text, false),
11067 ColumnSchema::new("contype", DataType::Text, false),
11068 ColumnSchema::new("conrelid", DataType::Text, false),
11069 ColumnSchema::new("confrelid", DataType::Text, false),
11070 ColumnSchema::new("conkey", DataType::Text, false),
11071 ColumnSchema::new("confkey", DataType::Text, false),
11072 ];
11073 let mut rows: Vec<Row> = Vec::new();
11074 for tname in cat.table_names() {
11075 let Some(t) = cat.get(&tname) else { continue };
11076 let cols = &t.schema().columns;
11077 let col_name_at = |pos: usize| -> String {
11078 cols.get(pos)
11079 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11080 };
11081 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11083 let kind = if uc.is_primary_key { "p" } else { "u" };
11084 let conname = if uc.is_primary_key {
11085 alloc::format!("{}_pkey", tname)
11086 } else {
11087 alloc::format!("{}_uniq{ci}", tname)
11088 };
11089 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11090 rows.push(Row::new(alloc::vec![
11091 Value::Text(conname),
11092 Value::Text(kind.into()),
11093 Value::Text(tname.clone()),
11094 Value::Text(String::new()),
11095 Value::Text(conkey.join(",")),
11096 Value::Text(String::new()),
11097 ]));
11098 }
11099 for idx in t.indices() {
11104 if !idx.is_unique {
11105 continue;
11106 }
11107 let is_primary = idx.name.ends_with("_pkey");
11108 let conname = idx.name.clone();
11109 let kind = if is_primary { "p" } else { "u" };
11110 let col_name = col_name_at(idx.column_position);
11111 let already = t
11114 .schema()
11115 .uniqueness_constraints
11116 .iter()
11117 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11118 if already {
11119 continue;
11120 }
11121 rows.push(Row::new(alloc::vec![
11122 Value::Text(conname),
11123 Value::Text(kind.into()),
11124 Value::Text(tname.clone()),
11125 Value::Text(String::new()),
11126 Value::Text(col_name),
11127 Value::Text(String::new()),
11128 ]));
11129 }
11130 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11132 let conname = fk
11133 .name
11134 .clone()
11135 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11136 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11137 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11140 fk.parent_columns
11141 .iter()
11142 .map(|&p| {
11143 parent
11144 .schema()
11145 .columns
11146 .get(p)
11147 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11148 })
11149 .collect()
11150 } else {
11151 fk.parent_columns
11152 .iter()
11153 .map(|p| alloc::format!("col{p}"))
11154 .collect()
11155 };
11156 rows.push(Row::new(alloc::vec![
11157 Value::Text(conname),
11158 Value::Text("f".into()),
11159 Value::Text(tname.clone()),
11160 Value::Text(fk.parent_table.clone()),
11161 Value::Text(conkey.join(",")),
11162 Value::Text(confkey.join(",")),
11163 ]));
11164 }
11165 }
11166 (schema, rows)
11167}
11168
11169fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11174 let schema = alloc::vec![
11175 ColumnSchema::new("oid", DataType::BigInt, false),
11176 ColumnSchema::new("datname", DataType::Text, false),
11177 ColumnSchema::new("datdba", DataType::BigInt, false),
11178 ColumnSchema::new("encoding", DataType::Int, false),
11179 ColumnSchema::new("datcollate", DataType::Text, false),
11180 ];
11181 let rows = alloc::vec![Row::new(alloc::vec![
11182 Value::BigInt(16384),
11183 Value::Text("postgres".into()),
11184 Value::BigInt(10),
11185 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11187 ])];
11188 (schema, rows)
11189}
11190
11191fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11196 let schema = alloc::vec![
11197 ColumnSchema::new("oid", DataType::BigInt, false),
11198 ColumnSchema::new("rolname", DataType::Text, false),
11199 ColumnSchema::new("rolsuper", DataType::Bool, false),
11200 ColumnSchema::new("rolinherit", DataType::Bool, false),
11201 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11202 ];
11203 let mut rows: Vec<Row> = Vec::new();
11204 let oid: i64 = 10;
11205 for (i, (name, _)) in engine.users.iter().enumerate() {
11206 rows.push(Row::new(alloc::vec![
11207 Value::BigInt(oid + (i as i64) + 1),
11208 Value::Text(name.to_string()),
11209 Value::Bool(false),
11210 Value::Bool(true),
11211 Value::Bool(true),
11212 ]));
11213 }
11214 if !rows
11217 .iter()
11218 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11219 {
11220 rows.insert(
11221 0,
11222 Row::new(alloc::vec![
11223 Value::BigInt(10),
11224 Value::Text("postgres".into()),
11225 Value::Bool(true),
11226 Value::Bool(true),
11227 Value::Bool(true),
11228 ]),
11229 );
11230 }
11231 (schema, rows)
11232}
11233
11234fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11238 let schema = alloc::vec![
11239 ColumnSchema::new("schemaname", DataType::Text, false),
11240 ColumnSchema::new("viewname", DataType::Text, false),
11241 ColumnSchema::new("definition", DataType::Text, false),
11242 ];
11243 let mut rows: Vec<Row> = Vec::new();
11244 for (name, def) in cat.views() {
11245 rows.push(Row::new(alloc::vec![
11246 Value::Text("public".into()),
11247 Value::Text(name.clone()),
11248 Value::Text(def.body.clone()),
11249 ]));
11250 }
11251 (schema, rows)
11252}
11253
11254fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11260 let schema = alloc::vec![
11261 ColumnSchema::new("name", DataType::Text, false),
11262 ColumnSchema::new("setting", DataType::Text, false),
11263 ColumnSchema::new("category", DataType::Text, false),
11264 ];
11265 let mut rows: Vec<Row> = Vec::new();
11266 let defaults: &[(&str, &str, &str)] = &[
11268 ("server_version", "16.0 (spg)", "Preset Options"),
11269 ("server_encoding", "UTF8", "Client Connection Defaults"),
11270 ("client_encoding", "UTF8", "Client Connection Defaults"),
11271 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11272 ("TimeZone", "UTC", "Client Connection Defaults"),
11273 ("standard_conforming_strings", "on", "Compatibility"),
11274 ("integer_datetimes", "on", "Compatibility"),
11275 ("max_connections", "100", "Connections and Authentication"),
11276 ];
11277 for &(name, val, cat) in defaults {
11278 rows.push(Row::new(alloc::vec![
11279 Value::Text(name.into()),
11280 Value::Text(val.into()),
11281 Value::Text(cat.into()),
11282 ]));
11283 }
11284 for (k, v) in &engine.session_params {
11286 if !defaults
11287 .iter()
11288 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11289 {
11290 rows.push(Row::new(alloc::vec![
11291 Value::Text(k.clone()),
11292 Value::Text(v.clone()),
11293 Value::Text("Session".into()),
11294 ]));
11295 }
11296 }
11297 (schema, rows)
11298}
11299
11300fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11311 let schema = alloc::vec![
11312 ColumnSchema::new("schemaname", DataType::Text, false),
11313 ColumnSchema::new("tablename", DataType::Text, false),
11314 ColumnSchema::new("indexname", DataType::Text, false),
11315 ColumnSchema::new("indexdef", DataType::Text, false),
11316 ];
11317 let mut rows: Vec<Row> = Vec::new();
11318 for tname in cat.table_names() {
11319 let Some(t) = cat.get(&tname) else { continue };
11320 for idx in t.indices() {
11321 let col_name = t
11322 .schema()
11323 .columns
11324 .get(idx.column_position)
11325 .map_or("?".into(), |c| c.name.clone());
11326 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11327 let indexdef = alloc::format!(
11328 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11329 idx.name,
11330 tname,
11331 col_name
11332 );
11333 rows.push(Row::new(alloc::vec![
11334 Value::Text("public".into()),
11335 Value::Text(tname.clone()),
11336 Value::Text(idx.name.clone()),
11337 Value::Text(indexdef),
11338 ]));
11339 }
11340 }
11341 (schema, rows)
11342}
11343
11344fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11356 let schema = alloc::vec![
11357 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11358 ColumnSchema::new("indrelid", DataType::BigInt, false),
11359 ColumnSchema::new("indnatts", DataType::Int, false),
11360 ColumnSchema::new("indisunique", DataType::Bool, false),
11361 ColumnSchema::new("indisprimary", DataType::Bool, false),
11362 ];
11363 let mut rows: Vec<Row> = Vec::new();
11364 let mut idx_oid: i64 = 100_000;
11365 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11366 let Some(t) = cat.get(tname) else { continue };
11367 for idx in t.indices() {
11368 idx_oid += 1;
11369 #[allow(clippy::cast_possible_wrap)]
11370 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11371 let is_primary = idx.name.ends_with("_pkey");
11374 rows.push(Row::new(alloc::vec![
11375 Value::BigInt(idx_oid),
11376 Value::BigInt((table_idx + 1) as i64),
11377 Value::Int(nattrs),
11378 Value::Bool(idx.is_unique),
11379 Value::Bool(is_primary),
11380 ]));
11381 }
11382 }
11383 (schema, rows)
11384}
11385
11386fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11391 let schema = alloc::vec![
11392 ColumnSchema::new("oid", DataType::BigInt, false),
11393 ColumnSchema::new("nspname", DataType::Text, false),
11394 ColumnSchema::new("nspowner", DataType::BigInt, false),
11395 ];
11396 let rows = alloc::vec![
11397 Row::new(alloc::vec![
11398 Value::BigInt(11),
11399 Value::Text("pg_catalog".into()),
11400 Value::BigInt(10),
11401 ]),
11402 Row::new(alloc::vec![
11403 Value::BigInt(2200),
11404 Value::Text("public".into()),
11405 Value::BigInt(10),
11406 ]),
11407 Row::new(alloc::vec![
11408 Value::BigInt(13000),
11409 Value::Text("information_schema".into()),
11410 Value::BigInt(10),
11411 ]),
11412 ];
11413 (schema, rows)
11414}
11415
11416fn materialise_meta_view(
11419 catalog: &mut Catalog,
11420 name: &str,
11421 columns: Vec<ColumnSchema>,
11422 rows: Vec<Row>,
11423) -> Result<(), EngineError> {
11424 let schema = TableSchema::new(name.to_string(), columns);
11425 catalog.create_table(schema).map_err(EngineError::Storage)?;
11426 let table = catalog
11427 .get_mut(name)
11428 .expect("just-created meta view must exist");
11429 for row in rows {
11430 table.insert(row).map_err(EngineError::Storage)?;
11431 }
11432 Ok(())
11433}
11434
11435fn collect_view_refs(
11448 tref: &spg_sql::ast::TableRef,
11449 cat: &spg_storage::Catalog,
11450 into: &mut Vec<String>,
11451) {
11452 if cat.views().contains_key(&tref.name)
11453 && cat.get(&tref.name).is_none()
11454 && !into.iter().any(|n| n == &tref.name)
11455 {
11456 into.push(tref.name.clone());
11457 }
11458}
11459
11460fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11461 fn is_meta(name: &str) -> bool {
11462 name.starts_with("__spg_info_")
11463 || name.starts_with("__spg_pg_")
11464 || name.starts_with("__spg_mysql_")
11465 }
11466 if let Some(from) = &stmt.from {
11467 if is_meta(&from.primary.name) {
11468 return true;
11469 }
11470 for j in &from.joins {
11471 if is_meta(&j.table.name) {
11472 return true;
11473 }
11474 }
11475 }
11476 for cte in &stmt.ctes {
11477 if select_references_meta_view(&cte.body) {
11478 return true;
11479 }
11480 }
11481 false
11482}
11483
11484fn collect_meta_view_names(
11489 stmt: &SelectStatement,
11490 into: &mut alloc::collections::BTreeSet<String>,
11491) {
11492 fn is_meta(name: &str) -> bool {
11493 name.starts_with("__spg_info_")
11494 || name.starts_with("__spg_pg_")
11495 || name.starts_with("__spg_mysql_")
11496 }
11497 if let Some(from) = &stmt.from {
11498 if is_meta(&from.primary.name) {
11499 into.insert(from.primary.name.clone());
11500 }
11501 for j in &from.joins {
11502 if is_meta(&j.table.name) {
11503 into.insert(j.table.name.clone());
11504 }
11505 }
11506 }
11507 for cte in &stmt.ctes {
11508 collect_meta_view_names(&cte.body, into);
11509 }
11510}
11511
11512fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11513 let mut out = columns.to_vec();
11514 for (col_idx, col) in out.iter_mut().enumerate() {
11515 if col.ty != DataType::Text {
11516 continue;
11517 }
11518 let mut inferred: Option<DataType> = None;
11519 let mut all_null = true;
11520 for row in rows {
11521 let Some(v) = row.values.get(col_idx) else {
11522 continue;
11523 };
11524 let ty = match v {
11525 Value::Null => continue,
11526 Value::SmallInt(_) => DataType::SmallInt,
11527 Value::Int(_) => DataType::Int,
11528 Value::BigInt(_) => DataType::BigInt,
11529 Value::Float(_) => DataType::Float,
11530 Value::Bool(_) => DataType::Bool,
11531 Value::Vector(_) => DataType::Vector {
11532 dim: 0,
11533 encoding: VecEncoding::F32,
11534 },
11535 _ => DataType::Text,
11536 };
11537 all_null = false;
11538 inferred = Some(match inferred {
11539 None => ty,
11540 Some(prev) if prev == ty => prev,
11541 Some(_) => DataType::Text,
11542 });
11543 }
11544 if let Some(t) = inferred {
11545 col.ty = t;
11546 col.nullable = true;
11547 } else if all_null {
11548 col.nullable = true;
11549 }
11550 }
11551 out
11552}
11553
11554#[allow(clippy::too_many_lines, clippy::format_push_string)]
11559fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11576 use alloc::collections::BTreeSet;
11577 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11578 let mut out: Vec<String> = Vec::new();
11579 let cat = engine.active_catalog();
11580 let Some(from) = &stmt.from else {
11584 return out;
11585 };
11586 let mut tables: Vec<String> = Vec::new();
11587 tables.push(from.primary.name.clone());
11588 for j in &from.joins {
11589 tables.push(j.table.name.clone());
11590 }
11591 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11594 if let Some(w) = &stmt.where_ {
11595 collect_column_refs(w, &mut col_refs);
11596 }
11597 for j in &from.joins {
11598 if let Some(on) = &j.on {
11599 collect_column_refs(on, &mut col_refs);
11600 }
11601 }
11602 for cn in &col_refs {
11603 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11606 tables.iter().find(|t| t == &q).cloned()
11607 } else {
11608 tables.iter().find_map(|t| {
11609 cat.get(t).and_then(|tbl| {
11610 if tbl.schema().column_position(&cn.name).is_some() {
11611 Some(t.clone())
11612 } else {
11613 None
11614 }
11615 })
11616 })
11617 };
11618 let Some(owner) = owner else {
11619 continue;
11620 };
11621 let Some(tbl) = cat.get(&owner) else {
11622 continue;
11623 };
11624 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11625 continue;
11626 };
11627 let already_indexed = tbl.indices().iter().any(|i| {
11630 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11631 && i.column_position == col_pos
11632 && i.expression.is_none()
11633 && i.partial_predicate.is_none()
11634 });
11635 if already_indexed {
11636 continue;
11637 }
11638 if seen.insert((owner.clone(), cn.name.clone())) {
11639 out.push(alloc::format!(
11640 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11641 owner,
11642 cn.name,
11643 owner,
11644 cn.name
11645 ));
11646 }
11647 }
11648 out
11649}
11650
11651fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11654 match expr {
11655 Expr::Column(cn) => out.push(cn.clone()),
11656 Expr::FunctionCall { args, .. } => {
11657 for a in args {
11658 collect_column_refs(a, out);
11659 }
11660 }
11661 Expr::Binary { lhs, rhs, .. } => {
11662 collect_column_refs(lhs, out);
11663 collect_column_refs(rhs, out);
11664 }
11665 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11666 _ => {}
11667 }
11668}
11669
11670fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11671 let catalog = engine.active_catalog();
11672 let cold_ids = catalog.cold_segment_ids_global();
11673 let any_cold = !cold_ids.is_empty();
11674 let cold_ids_repr = if any_cold {
11675 let mut s = alloc::string::String::from("[");
11676 for (i, id) in cold_ids.iter().enumerate() {
11677 if i > 0 {
11678 s.push(',');
11679 }
11680 s.push_str(&alloc::format!("{id}"));
11681 }
11682 s.push(']');
11683 s
11684 } else {
11685 alloc::string::String::new()
11686 };
11687 for (idx, line) in lines.iter_mut().enumerate() {
11688 let trimmed = line.trim_start();
11689 let is_top_level = idx == 0;
11690 if is_top_level {
11691 line.push_str(&alloc::format!(" (rows={total_rows})"));
11692 continue;
11693 }
11694 if let Some(rest) = trimmed.strip_prefix("From: ") {
11695 let (name, scan_kind) = match rest.split_once(" [") {
11696 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
11697 None => (rest.trim(), ""),
11698 };
11699 let bare = name.split_whitespace().next().unwrap_or(name);
11700 let hot = catalog.get(bare).map(|t| t.rows().len());
11701 let annot = match (hot, scan_kind) {
11706 (Some(h), "full scan") => {
11707 let mut s = alloc::format!(" (hot_rows={h}");
11708 if any_cold {
11709 s.push_str(&alloc::format!(
11710 ", cold_tier=present, cold_segments={cold_ids_repr}"
11711 ));
11712 }
11713 s.push(')');
11714 s
11715 }
11716 (Some(h), "index seek") => {
11717 let mut s = alloc::format!(" (hot_rows≤{h}");
11718 if any_cold {
11719 s.push_str(&alloc::format!(
11720 ", cold_tier=present, cold_segments={cold_ids_repr}"
11721 ));
11722 }
11723 s.push(')');
11724 s
11725 }
11726 _ => " (rows=—)".to_string(),
11727 };
11728 line.push_str(&annot);
11729 continue;
11730 }
11731 line.push_str(" (rows=—)");
11733 }
11734}
11735
11736fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
11737 let pad = " ".repeat(depth);
11738 let top = if !stmt.ctes.is_empty() {
11740 if stmt.ctes.iter().any(|c| c.recursive) {
11741 "CTEScan (WITH RECURSIVE)"
11742 } else {
11743 "CTEScan (WITH)"
11744 }
11745 } else if !stmt.unions.is_empty() {
11746 "UnionScan"
11747 } else if select_has_window(stmt) {
11748 "WindowAgg"
11749 } else if aggregate::uses_aggregate(stmt) {
11750 "Aggregate"
11751 } else if stmt.distinct {
11752 "Distinct"
11753 } else if stmt.from.is_some() {
11754 "TableScan"
11755 } else {
11756 "Result"
11757 };
11758 out.push(alloc::format!("{pad}{top}"));
11759 let child = " ".repeat(depth + 1);
11760 for cte in &stmt.ctes {
11762 let head = if cte.recursive {
11763 alloc::format!("{child}CTE (recursive): {}", cte.name)
11764 } else {
11765 alloc::format!("{child}CTE: {}", cte.name)
11766 };
11767 out.push(head);
11768 explain_select(&cte.body, engine, depth + 2, out);
11769 }
11770 if let Some(from) = &stmt.from {
11772 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
11773 if let Some(alias) = &from.primary.alias {
11774 tag.push_str(&alloc::format!(" AS {alias}"));
11775 }
11776 if let Some(w) = &stmt.where_
11779 && let Some(table) = engine.active_catalog().get(&from.primary.name)
11780 {
11781 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
11782 let cols = &table.schema().columns;
11783 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
11784 tag.push_str(" [index seek]");
11785 } else {
11786 tag.push_str(" [full scan]");
11787 }
11788 } else {
11789 tag.push_str(" [full scan]");
11790 }
11791 out.push(tag);
11792 for j in &from.joins {
11793 let kind = match j.kind {
11794 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
11795 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
11796 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
11797 };
11798 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
11799 if let Some(alias) = &j.table.alias {
11800 s.push_str(&alloc::format!(" AS {alias}"));
11801 }
11802 if j.on.is_some() {
11803 s.push_str(" (ON …)");
11804 }
11805 out.push(s);
11806 }
11807 }
11808 if let Some(w) = &stmt.where_ {
11810 let mut s = alloc::format!("{child}Filter: {w}");
11811 if expr_has_subquery(w) {
11812 s.push_str(" [subquery]");
11813 }
11814 out.push(s);
11815 }
11816 if let Some(gs) = &stmt.group_by {
11817 let mut parts = Vec::new();
11818 for g in gs {
11819 parts.push(alloc::format!("{g}"));
11820 }
11821 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
11822 }
11823 if let Some(h) = &stmt.having {
11824 out.push(alloc::format!("{child}Having: {h}"));
11825 }
11826 for o in &stmt.order_by {
11827 let dir = if o.desc { "DESC" } else { "ASC" };
11828 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
11829 }
11830 if let Some(lim) = stmt.limit {
11831 out.push(alloc::format!("{child}Limit: {lim}"));
11832 }
11833 if let Some(off) = stmt.offset {
11834 out.push(alloc::format!("{child}Offset: {off}"));
11835 }
11836 if stmt
11838 .items
11839 .iter()
11840 .any(|it| matches!(it, SelectItem::Wildcard))
11841 {
11842 out.push(alloc::format!("{child}Project: *"));
11843 } else {
11844 out.push(alloc::format!(
11845 "{child}Project: {} item(s)",
11846 stmt.items.len()
11847 ));
11848 }
11849 for (kind, peer) in &stmt.unions {
11851 let label = match kind {
11852 UnionKind::All => "UNION ALL",
11853 UnionKind::Distinct => "UNION",
11854 };
11855 out.push(alloc::format!("{child}{label}"));
11856 explain_select(peer, engine, depth + 2, out);
11857 }
11858}
11859
11860fn is_correlation_error(e: &EngineError) -> bool {
11865 matches!(
11866 e,
11867 EngineError::Eval(
11868 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
11869 )
11870 )
11871}
11872
11873struct JoinedPeer<'a> {
11884 eager_rows: Option<Vec<Row>>,
11885 cols: Vec<ColumnSchema>,
11886 alias: String,
11887 kind: JoinKind,
11888 on: Option<&'a Expr>,
11889 lateral: Option<&'a SelectStatement>,
11890}
11891
11892fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
11899 match expr {
11900 Expr::Column(c) => c.name.clone(),
11902 Expr::FunctionCall { name, .. } => name.clone(),
11905 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
11907 _ => alloc::format!("column{}", idx + 1),
11909 }
11910}
11911
11912fn substitute_outer_columns_multi(
11919 stmt: &mut SelectStatement,
11920 outer_row: &Row,
11921 outer_schema: &[ColumnSchema],
11922) {
11923 substitute_outer_in_select(stmt, outer_row, outer_schema);
11924}
11925
11926fn substitute_outer_in_select(
11927 stmt: &mut SelectStatement,
11928 outer_row: &Row,
11929 outer_schema: &[ColumnSchema],
11930) {
11931 for item in &mut stmt.items {
11932 if let SelectItem::Expr { expr, .. } = item {
11933 substitute_outer_in_expr(expr, outer_row, outer_schema);
11934 }
11935 }
11936 if let Some(w) = &mut stmt.where_ {
11937 substitute_outer_in_expr(w, outer_row, outer_schema);
11938 }
11939 if let Some(gs) = &mut stmt.group_by {
11940 for g in gs {
11941 substitute_outer_in_expr(g, outer_row, outer_schema);
11942 }
11943 }
11944 if let Some(h) = &mut stmt.having {
11945 substitute_outer_in_expr(h, outer_row, outer_schema);
11946 }
11947 for o in &mut stmt.order_by {
11948 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
11949 }
11950 for (_, peer) in &mut stmt.unions {
11951 substitute_outer_in_select(peer, outer_row, outer_schema);
11952 }
11953}
11954
11955fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
11956 if let Expr::Column(c) = e
11957 && let Some(qual) = &c.qualifier
11958 {
11959 let composite = alloc::format!("{qual}.{}", c.name);
11960 if let Some(idx) = outer_schema
11961 .iter()
11962 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
11963 {
11964 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
11965 if let Ok(lit) = value_to_literal_expr(v) {
11966 *e = lit;
11967 return;
11968 }
11969 }
11970 }
11971 match e {
11972 Expr::Binary { lhs, rhs, .. } => {
11973 substitute_outer_in_expr(lhs, outer_row, outer_schema);
11974 substitute_outer_in_expr(rhs, outer_row, outer_schema);
11975 }
11976 Expr::Unary { expr: inner, .. } => {
11977 substitute_outer_in_expr(inner, outer_row, outer_schema);
11978 }
11979 Expr::FunctionCall { args, .. } => {
11980 for a in args {
11981 substitute_outer_in_expr(a, outer_row, outer_schema);
11982 }
11983 }
11984 Expr::Cast { expr: inner, .. } => {
11985 substitute_outer_in_expr(inner, outer_row, outer_schema);
11986 }
11987 Expr::Case {
11988 operand,
11989 branches,
11990 else_branch,
11991 } => {
11992 if let Some(op) = operand {
11993 substitute_outer_in_expr(op, outer_row, outer_schema);
11994 }
11995 for (cond, val) in branches {
11996 substitute_outer_in_expr(cond, outer_row, outer_schema);
11997 substitute_outer_in_expr(val, outer_row, outer_schema);
11998 }
11999 if let Some(e) = else_branch {
12000 substitute_outer_in_expr(e, outer_row, outer_schema);
12001 }
12002 }
12003 _ => {}
12004 }
12005}
12006
12007fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12008 let Some(outer_alias) = ctx.table_alias else {
12009 return;
12010 };
12011 substitute_in_select(stmt, row, ctx, outer_alias);
12012}
12013
12014fn substitute_in_select(
12015 stmt: &mut SelectStatement,
12016 row: &Row,
12017 ctx: &EvalContext<'_>,
12018 outer_alias: &str,
12019) {
12020 for item in &mut stmt.items {
12021 if let SelectItem::Expr { expr, .. } = item {
12022 substitute_in_expr(expr, row, ctx, outer_alias);
12023 }
12024 }
12025 if let Some(w) = &mut stmt.where_ {
12026 substitute_in_expr(w, row, ctx, outer_alias);
12027 }
12028 if let Some(gs) = &mut stmt.group_by {
12029 for g in gs {
12030 substitute_in_expr(g, row, ctx, outer_alias);
12031 }
12032 }
12033 if let Some(h) = &mut stmt.having {
12034 substitute_in_expr(h, row, ctx, outer_alias);
12035 }
12036 for o in &mut stmt.order_by {
12037 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12038 }
12039 for (_, peer) in &mut stmt.unions {
12040 substitute_in_select(peer, row, ctx, outer_alias);
12041 }
12042}
12043
12044fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12045 if let Expr::Column(c) = e
12046 && let Some(qual) = &c.qualifier
12047 && qual.eq_ignore_ascii_case(outer_alias)
12048 {
12049 if let Some(idx) = ctx
12051 .columns
12052 .iter()
12053 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12054 {
12055 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12056 if let Ok(lit) = value_to_literal_expr(v) {
12057 *e = lit;
12058 return;
12059 }
12060 }
12061 }
12062 match e {
12063 Expr::Binary { lhs, rhs, .. } => {
12064 substitute_in_expr(lhs, row, ctx, outer_alias);
12065 substitute_in_expr(rhs, row, ctx, outer_alias);
12066 }
12067 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12068 substitute_in_expr(expr, row, ctx, outer_alias);
12069 }
12070 Expr::Like { expr, pattern, .. } => {
12071 substitute_in_expr(expr, row, ctx, outer_alias);
12072 substitute_in_expr(pattern, row, ctx, outer_alias);
12073 }
12074 Expr::FunctionCall { args, .. } => {
12075 for a in args {
12076 substitute_in_expr(a, row, ctx, outer_alias);
12077 }
12078 }
12079 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12080 Expr::WindowFunction {
12081 args,
12082 partition_by,
12083 order_by,
12084 ..
12085 } => {
12086 for a in args {
12087 substitute_in_expr(a, row, ctx, outer_alias);
12088 }
12089 for p in partition_by {
12090 substitute_in_expr(p, row, ctx, outer_alias);
12091 }
12092 for (o, _) in order_by {
12093 substitute_in_expr(o, row, ctx, outer_alias);
12094 }
12095 }
12096 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12097 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12098 substitute_in_select(subquery, row, ctx, outer_alias);
12099 }
12100 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12101 Expr::Array(items) => {
12102 for elem in items {
12103 substitute_in_expr(elem, row, ctx, outer_alias);
12104 }
12105 }
12106 Expr::ArraySubscript { target, index } => {
12107 substitute_in_expr(target, row, ctx, outer_alias);
12108 substitute_in_expr(index, row, ctx, outer_alias);
12109 }
12110 Expr::AnyAll { expr, array, .. } => {
12111 substitute_in_expr(expr, row, ctx, outer_alias);
12112 substitute_in_expr(array, row, ctx, outer_alias);
12113 }
12114 Expr::Case {
12115 operand,
12116 branches,
12117 else_branch,
12118 } => {
12119 if let Some(o) = operand {
12120 substitute_in_expr(o, row, ctx, outer_alias);
12121 }
12122 for (w, t) in branches {
12123 substitute_in_expr(w, row, ctx, outer_alias);
12124 substitute_in_expr(t, row, ctx, outer_alias);
12125 }
12126 if let Some(e) = else_branch {
12127 substitute_in_expr(e, row, ctx, outer_alias);
12128 }
12129 }
12130 }
12131}
12132
12133fn encode_row_key(row: &Row) -> Vec<u8> {
12137 let mut out = Vec::new();
12138 for v in &row.values {
12139 let s = alloc::format!("{v:?}|");
12140 out.extend_from_slice(s.as_bytes());
12141 }
12142 out
12143}
12144
12145fn select_has_window(stmt: &SelectStatement) -> bool {
12146 for item in &stmt.items {
12147 if let SelectItem::Expr { expr, .. } = item
12148 && expr_has_window(expr)
12149 {
12150 return true;
12151 }
12152 }
12153 false
12154}
12155
12156fn expr_has_window(e: &Expr) -> bool {
12157 match e {
12158 Expr::WindowFunction { .. } => true,
12159 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12160 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12161 expr_has_window(expr)
12162 }
12163 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12164 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12165 Expr::Extract { source, .. } => expr_has_window(source),
12166 Expr::ScalarSubquery(_)
12167 | Expr::Exists { .. }
12168 | Expr::InSubquery { .. }
12169 | Expr::Literal(_)
12170 | Expr::Placeholder(_)
12171 | Expr::Column(_) => false,
12172 Expr::Array(items) => items.iter().any(expr_has_window),
12173 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12174 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12175 Expr::Case {
12176 operand,
12177 branches,
12178 else_branch,
12179 } => {
12180 operand.as_deref().is_some_and(expr_has_window)
12181 || branches
12182 .iter()
12183 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12184 || else_branch.as_deref().is_some_and(expr_has_window)
12185 }
12186 }
12187}
12188
12189fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12190 if let Expr::WindowFunction { .. } = e {
12191 if !out.iter().any(|x| x == e) {
12196 out.push(e.clone());
12197 }
12198 return;
12199 }
12200 match e {
12201 Expr::WindowFunction { .. } => unreachable!(),
12203 Expr::Binary { lhs, rhs, .. } => {
12204 collect_window_nodes(lhs, out);
12205 collect_window_nodes(rhs, out);
12206 }
12207 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12208 collect_window_nodes(expr, out);
12209 }
12210 Expr::FunctionCall { args, .. } => {
12211 for a in args {
12212 collect_window_nodes(a, out);
12213 }
12214 }
12215 Expr::Like { expr, pattern, .. } => {
12216 collect_window_nodes(expr, out);
12217 collect_window_nodes(pattern, out);
12218 }
12219 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12220 _ => {}
12221 }
12222}
12223
12224fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12225 if let Expr::WindowFunction { .. } = e
12226 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12227 {
12228 *e = Expr::Column(spg_sql::ast::ColumnName {
12229 qualifier: None,
12230 name: alloc::format!("__win_{idx}"),
12231 });
12232 return;
12233 }
12234 match e {
12235 Expr::Binary { lhs, rhs, .. } => {
12236 rewrite_window_to_columns(lhs, window_nodes);
12237 rewrite_window_to_columns(rhs, window_nodes);
12238 }
12239 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12240 rewrite_window_to_columns(expr, window_nodes);
12241 }
12242 Expr::FunctionCall { args, .. } => {
12243 for a in args {
12244 rewrite_window_to_columns(a, window_nodes);
12245 }
12246 }
12247 Expr::Like { expr, pattern, .. } => {
12248 rewrite_window_to_columns(expr, window_nodes);
12249 rewrite_window_to_columns(pattern, window_nodes);
12250 }
12251 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12252 _ => {}
12253 }
12254}
12255
12256fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12260 for (x, y) in a.iter().zip(b.iter()) {
12261 let c = value_cmp(x, y);
12262 if c != core::cmp::Ordering::Equal {
12263 return c;
12264 }
12265 }
12266 a.len().cmp(&b.len())
12267}
12268
12269fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12270 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12271 let c = value_cmp(va, vb);
12272 let c = if *desc { c.reverse() } else { c };
12273 if c != core::cmp::Ordering::Equal {
12274 return c;
12275 }
12276 }
12277 a.len().cmp(&b.len())
12278}
12279
12280const fn value_is_integer(v: &Value) -> bool {
12286 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12287}
12288
12289const fn value_to_i64(v: &Value) -> i64 {
12293 match v {
12294 Value::SmallInt(n) => *n as i64,
12295 Value::Int(n) => *n as i64,
12296 Value::BigInt(n) => *n,
12297 _ => panic!("value_to_i64 called on non-integer Value"),
12298 }
12299}
12300
12301fn generate_series_integers(
12307 start: i64,
12308 stop: i64,
12309 step: i64,
12310 cancel: &CancelToken<'_>,
12311) -> Result<alloc::vec::Vec<Row>, EngineError> {
12312 if step == 0 {
12313 return Err(EngineError::Unsupported(
12314 "generate_series(): step argument cannot be zero".into(),
12315 ));
12316 }
12317 let mut out = alloc::vec::Vec::new();
12318 let mut cur = start;
12319 const MAX_ROWS: usize = 10_000_000;
12323 loop {
12324 cancel.check()?;
12325 if step > 0 && cur > stop {
12326 break;
12327 }
12328 if step < 0 && cur < stop {
12329 break;
12330 }
12331 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12332 if out.len() > MAX_ROWS {
12333 return Err(EngineError::Unsupported(alloc::format!(
12334 "generate_series(): exceeded {MAX_ROWS} rows; \
12335 narrow start/stop or use a larger step"
12336 )));
12337 }
12338 cur = match cur.checked_add(step) {
12339 Some(n) => n,
12340 None => break,
12341 };
12342 }
12343 Ok(out)
12344}
12345
12346fn generate_series_timestamps(
12351 start: i64,
12352 stop: i64,
12353 step: Value,
12354 cancel: &CancelToken<'_>,
12355) -> Result<alloc::vec::Vec<Row>, EngineError> {
12356 let (months, micros) = match &step {
12357 Value::Interval { months, micros } => (*months, *micros),
12358 _ => unreachable!("caller guards step.is_interval"),
12359 };
12360 if months == 0 && micros == 0 {
12361 return Err(EngineError::Unsupported(
12362 "generate_series(): INTERVAL step cannot be zero".into(),
12363 ));
12364 }
12365 let ascending = months > 0 || micros > 0;
12366 let mut out = alloc::vec::Vec::new();
12367 let mut cur = Value::Timestamp(start);
12368 const MAX_ROWS: usize = 10_000_000;
12369 loop {
12370 cancel.check()?;
12371 let cur_t = match cur {
12372 Value::Timestamp(t) => t,
12373 _ => unreachable!("loop invariant: cur is Timestamp"),
12374 };
12375 if ascending && cur_t > stop {
12376 break;
12377 }
12378 if !ascending && cur_t < stop {
12379 break;
12380 }
12381 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12382 if out.len() > MAX_ROWS {
12383 return Err(EngineError::Unsupported(alloc::format!(
12384 "generate_series(): exceeded {MAX_ROWS} rows; \
12385 narrow start/stop or use a larger step"
12386 )));
12387 }
12388 let next = eval::apply_binary_interval(
12389 spg_sql::ast::BinOp::Add,
12390 &cur,
12391 &Value::Interval { months, micros },
12392 )
12393 .map_err(EngineError::Eval)?;
12394 cur = match next {
12395 Some(v) => v,
12396 None => break,
12397 };
12398 }
12399 Ok(out)
12400}
12401
12402#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12404 use core::cmp::Ordering;
12405 match (a, b) {
12406 (Value::Null, Value::Null) => Ordering::Equal,
12407 (Value::Null, _) => Ordering::Less,
12408 (_, Value::Null) => Ordering::Greater,
12409 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12410 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12411 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12412 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12413 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12414 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12415 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12416 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12417 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12420 }
12421}
12422
12423#[allow(
12429 clippy::too_many_arguments,
12430 clippy::cast_possible_truncation,
12431 clippy::cast_possible_wrap,
12432 clippy::cast_precision_loss,
12433 clippy::cast_sign_loss,
12434 clippy::doc_markdown,
12435 clippy::too_many_lines,
12436 clippy::type_complexity,
12437 clippy::match_same_arms
12438)]
12439fn compute_window_partition(
12440 name: &str,
12441 args: &[Expr],
12442 ordered: bool,
12443 frame: Option<&WindowFrame>,
12444 null_treatment: spg_sql::ast::NullTreatment,
12445 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12446 filtered_rows: &[&Row],
12447 ctx: &EvalContext<'_>,
12448 out_vals: &mut [Value],
12449) -> Result<(), EngineError> {
12450 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12451 let lower = name.to_ascii_lowercase();
12452 match lower.as_str() {
12453 "row_number" => {
12454 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12455 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12456 }
12457 Ok(())
12458 }
12459 "rank" => {
12460 let mut prev_key: Option<&[(Value, bool)]> = None;
12461 let mut current_rank: i64 = 1;
12462 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12463 if let Some(p) = prev_key
12464 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12465 {
12466 current_rank = (i + 1) as i64;
12467 }
12468 if prev_key.is_none() {
12469 current_rank = 1;
12470 }
12471 out_vals[*idx] = Value::BigInt(current_rank);
12472 prev_key = Some(okey.as_slice());
12473 }
12474 Ok(())
12475 }
12476 "dense_rank" => {
12477 let mut prev_key: Option<&[(Value, bool)]> = None;
12478 let mut current_rank: i64 = 0;
12479 for (_, okey, idx) in slice {
12480 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12481 current_rank += 1;
12482 }
12483 out_vals[*idx] = Value::BigInt(current_rank);
12484 prev_key = Some(okey.as_slice());
12485 }
12486 Ok(())
12487 }
12488 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12489 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12492 slice.iter().map(|_| Value::Null).collect()
12493 } else {
12494 slice
12495 .iter()
12496 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12497 .collect::<Result<_, _>>()
12498 .map_err(EngineError::Eval)?
12499 };
12500 let eff = effective_frame(frame, ordered)?;
12504 #[allow(clippy::needless_range_loop)]
12505 for i in 0..slice.len() {
12506 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12507 let mut sum: f64 = 0.0;
12508 let mut count: i64 = 0;
12509 let mut min_v: Option<f64> = None;
12510 let mut max_v: Option<f64> = None;
12511 let mut row_count: i64 = 0;
12512 if lo <= hi {
12513 for j in lo..=hi {
12514 let v = &arg_values[j];
12515 match lower.as_str() {
12516 "count_star" => row_count += 1,
12517 "count" => {
12518 if !v.is_null() {
12519 count += 1;
12520 }
12521 }
12522 _ => {
12523 if let Some(x) = value_to_f64(v) {
12524 sum += x;
12525 count += 1;
12526 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12527 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12528 }
12529 }
12530 }
12531 }
12532 }
12533 let value = match lower.as_str() {
12534 "count_star" => Value::BigInt(row_count),
12535 "count" => Value::BigInt(count),
12536 "sum" => Value::Float(sum),
12537 "avg" => {
12538 if count == 0 {
12539 Value::Null
12540 } else {
12541 Value::Float(sum / count as f64)
12542 }
12543 }
12544 "min" => min_v.map_or(Value::Null, Value::Float),
12545 "max" => max_v.map_or(Value::Null, Value::Float),
12546 _ => unreachable!(),
12547 };
12548 let (_, _, idx) = &slice[i];
12549 out_vals[*idx] = value;
12550 }
12551 Ok(())
12552 }
12553 "lag" | "lead" => {
12554 if args.is_empty() {
12557 return Err(EngineError::Unsupported(alloc::format!(
12558 "{lower}() requires at least one argument"
12559 )));
12560 }
12561 let offset: i64 = if args.len() >= 2 {
12562 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12563 .map_err(EngineError::Eval)?;
12564 match v {
12565 Value::SmallInt(n) => i64::from(n),
12566 Value::Int(n) => i64::from(n),
12567 Value::BigInt(n) => n,
12568 _ => {
12569 return Err(EngineError::Unsupported(alloc::format!(
12570 "{lower}() offset must be integer"
12571 )));
12572 }
12573 }
12574 } else {
12575 1
12576 };
12577 let default: Value = if args.len() >= 3 {
12578 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12579 .map_err(EngineError::Eval)?
12580 } else {
12581 Value::Null
12582 };
12583 let values: Vec<Value> = slice
12584 .iter()
12585 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12586 .collect::<Result<_, _>>()
12587 .map_err(EngineError::Eval)?;
12588 let n = slice.len();
12589 for (i, (_, _, idx)) in slice.iter().enumerate() {
12590 let signed_offset = if lower == "lag" { -offset } else { offset };
12591 let v = if ignore_nulls {
12592 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12596 let needed: i64 = signed_offset.abs();
12597 if needed == 0 {
12598 values[i].clone()
12599 } else {
12600 let mut j: i64 = i as i64;
12601 let mut hits: i64 = 0;
12602 let mut found: Option<Value> = None;
12603 loop {
12604 j += step;
12605 if j < 0 || j >= n as i64 {
12606 break;
12607 }
12608 #[allow(clippy::cast_sign_loss)]
12609 let v = &values[j as usize];
12610 if !v.is_null() {
12611 hits += 1;
12612 if hits == needed {
12613 found = Some(v.clone());
12614 break;
12615 }
12616 }
12617 }
12618 found.unwrap_or_else(|| default.clone())
12619 }
12620 } else {
12621 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12622 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12623 default.clone()
12624 } else {
12625 #[allow(clippy::cast_sign_loss)]
12626 {
12627 values[target_signed as usize].clone()
12628 }
12629 }
12630 };
12631 out_vals[*idx] = v;
12632 }
12633 Ok(())
12634 }
12635 "first_value" | "last_value" | "nth_value" => {
12636 if args.is_empty() {
12637 return Err(EngineError::Unsupported(alloc::format!(
12638 "{lower}() requires at least one argument"
12639 )));
12640 }
12641 let values: Vec<Value> = slice
12642 .iter()
12643 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12644 .collect::<Result<_, _>>()
12645 .map_err(EngineError::Eval)?;
12646 let nth: usize = if lower == "nth_value" {
12647 if args.len() < 2 {
12648 return Err(EngineError::Unsupported(
12649 "nth_value() requires (expr, n)".into(),
12650 ));
12651 }
12652 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12653 .map_err(EngineError::Eval)?;
12654 let raw = match v {
12655 Value::SmallInt(n) => i64::from(n),
12656 Value::Int(n) => i64::from(n),
12657 Value::BigInt(n) => n,
12658 _ => {
12659 return Err(EngineError::Unsupported(
12660 "nth_value() n must be integer".into(),
12661 ));
12662 }
12663 };
12664 if raw < 1 {
12665 return Err(EngineError::Unsupported(
12666 "nth_value() n must be >= 1".into(),
12667 ));
12668 }
12669 #[allow(clippy::cast_sign_loss)]
12670 {
12671 raw as usize
12672 }
12673 } else {
12674 0
12675 };
12676 let eff = effective_frame(frame, ordered)?;
12677 for i in 0..slice.len() {
12678 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12679 let (_, _, idx) = &slice[i];
12680 let v = if lo > hi {
12681 Value::Null
12682 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
12683 if lower == "first_value" {
12686 (lo..=hi)
12687 .find_map(|j| {
12688 let v = &values[j];
12689 (!v.is_null()).then(|| v.clone())
12690 })
12691 .unwrap_or(Value::Null)
12692 } else {
12693 (lo..=hi)
12694 .rev()
12695 .find_map(|j| {
12696 let v = &values[j];
12697 (!v.is_null()).then(|| v.clone())
12698 })
12699 .unwrap_or(Value::Null)
12700 }
12701 } else {
12702 match lower.as_str() {
12703 "first_value" => values[lo].clone(),
12704 "last_value" => values[hi].clone(),
12705 "nth_value" => {
12706 let pos = lo + nth - 1;
12707 if pos > hi {
12708 Value::Null
12709 } else {
12710 values[pos].clone()
12711 }
12712 }
12713 _ => unreachable!(),
12714 }
12715 };
12716 out_vals[*idx] = v;
12717 }
12718 Ok(())
12719 }
12720 "ntile" => {
12721 if args.is_empty() {
12722 return Err(EngineError::Unsupported(
12723 "ntile(n) requires an integer argument".into(),
12724 ));
12725 }
12726 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
12727 .map_err(EngineError::Eval)?;
12728 let bucket_count: i64 = match v {
12729 Value::SmallInt(n) => i64::from(n),
12730 Value::Int(n) => i64::from(n),
12731 Value::BigInt(n) => n,
12732 _ => {
12733 return Err(EngineError::Unsupported(
12734 "ntile() argument must be integer".into(),
12735 ));
12736 }
12737 };
12738 if bucket_count < 1 {
12739 return Err(EngineError::Unsupported(
12740 "ntile() argument must be >= 1".into(),
12741 ));
12742 }
12743 #[allow(clippy::cast_sign_loss)]
12744 let buckets = bucket_count as usize;
12745 let n = slice.len();
12746 let base = n / buckets;
12749 let extras = n % buckets;
12750 let mut bucket: usize = 1;
12751 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
12752 let mut buckets_with_extra_remaining = extras;
12753 for (_, _, idx) in slice {
12754 if remaining_in_bucket == 0 {
12755 bucket += 1;
12756 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
12757 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
12758 base + 1
12759 } else {
12760 base
12761 };
12762 if remaining_in_bucket == 0 {
12765 remaining_in_bucket = 1;
12766 }
12767 }
12768 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
12769 remaining_in_bucket -= 1;
12770 }
12771 Ok(())
12772 }
12773 "percent_rank" => {
12774 let n = slice.len();
12777 let mut prev_key: Option<&[(Value, bool)]> = None;
12778 let mut current_rank: i64 = 1;
12779 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12780 if let Some(p) = prev_key
12781 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12782 {
12783 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
12784 }
12785 if prev_key.is_none() {
12786 current_rank = 1;
12787 }
12788 #[allow(clippy::cast_precision_loss)]
12789 let pr = if n <= 1 {
12790 0.0
12791 } else {
12792 (current_rank - 1) as f64 / (n - 1) as f64
12793 };
12794 out_vals[*idx] = Value::Float(pr);
12795 prev_key = Some(okey.as_slice());
12796 }
12797 Ok(())
12798 }
12799 "cume_dist" => {
12800 let n = slice.len();
12802 for i in 0..slice.len() {
12804 let peer_end = peer_group_end(slice, i);
12805 #[allow(clippy::cast_precision_loss)]
12806 let cd = (peer_end + 1) as f64 / n as f64;
12807 let (_, _, idx) = &slice[i];
12808 out_vals[*idx] = Value::Float(cd);
12809 }
12810 Ok(())
12811 }
12812 other => Err(EngineError::Unsupported(alloc::format!(
12813 "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)"
12814 ))),
12815 }
12816}
12817
12818fn effective_frame(
12825 frame: Option<&WindowFrame>,
12826 ordered: bool,
12827) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
12828 match frame {
12829 None => {
12830 if ordered {
12831 Ok((
12832 FrameKind::Range,
12833 FrameBound::UnboundedPreceding,
12834 FrameBound::CurrentRow,
12835 ))
12836 } else {
12837 Ok((
12838 FrameKind::Rows,
12839 FrameBound::UnboundedPreceding,
12840 FrameBound::UnboundedFollowing,
12841 ))
12842 }
12843 }
12844 Some(fr) => {
12845 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
12846 if matches!(fr.start, FrameBound::UnboundedFollowing)
12848 || matches!(end, FrameBound::UnboundedPreceding)
12849 {
12850 return Err(EngineError::Unsupported(alloc::format!(
12851 "invalid frame: start={:?} end={:?}",
12852 fr.start,
12853 end
12854 )));
12855 }
12856 if fr.kind == FrameKind::Range
12861 && (matches!(
12862 fr.start,
12863 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
12864 ) || matches!(
12865 end,
12866 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
12867 ))
12868 {
12869 return Err(EngineError::Unsupported(
12870 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
12871 ));
12872 }
12873 Ok((fr.kind, fr.start.clone(), end))
12874 }
12875 }
12876}
12877
12878#[allow(clippy::type_complexity)]
12882fn frame_bounds_for_row(
12883 eff: &(FrameKind, FrameBound, FrameBound),
12884 i: usize,
12885 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12886) -> (usize, usize) {
12887 let (kind, start, end) = eff;
12888 let n = slice.len();
12889 let last = n.saturating_sub(1);
12890 let (mut lo, mut hi) = match kind {
12891 FrameKind::Rows => {
12892 let lo = match start {
12893 FrameBound::UnboundedPreceding => 0,
12894 FrameBound::OffsetPreceding(k) => {
12895 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12896 i.saturating_sub(k)
12897 }
12898 FrameBound::CurrentRow => i,
12899 FrameBound::OffsetFollowing(k) => {
12900 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12901 i.saturating_add(k).min(last)
12902 }
12903 FrameBound::UnboundedFollowing => last,
12904 };
12905 let hi = match end {
12906 FrameBound::UnboundedPreceding => 0,
12907 FrameBound::OffsetPreceding(k) => {
12908 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12909 i.saturating_sub(k)
12910 }
12911 FrameBound::CurrentRow => i,
12912 FrameBound::OffsetFollowing(k) => {
12913 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12914 i.saturating_add(k).min(last)
12915 }
12916 FrameBound::UnboundedFollowing => last,
12917 };
12918 (lo, hi)
12919 }
12920 FrameKind::Range => {
12921 let lo = match start {
12927 FrameBound::UnboundedPreceding => 0,
12928 FrameBound::CurrentRow => peer_group_start(slice, i),
12929 FrameBound::UnboundedFollowing => last,
12930 _ => unreachable!("offset bounds rejected for RANGE"),
12931 };
12932 let hi = match end {
12933 FrameBound::UnboundedPreceding => 0,
12934 FrameBound::CurrentRow => peer_group_end(slice, i),
12935 FrameBound::UnboundedFollowing => last,
12936 _ => unreachable!("offset bounds rejected for RANGE"),
12937 };
12938 (lo, hi)
12939 }
12940 };
12941 if hi >= n {
12942 hi = last;
12943 }
12944 if lo >= n {
12945 lo = last;
12946 }
12947 (lo, hi)
12948}
12949
12950#[allow(clippy::type_complexity)]
12954fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
12955 let key = &slice[i].1;
12956 let mut j = i;
12957 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
12958 j -= 1;
12959 }
12960 j
12961}
12962
12963#[allow(clippy::type_complexity)]
12966fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
12967 let key = &slice[i].1;
12968 let mut j = i;
12969 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
12970 j += 1;
12971 }
12972 j
12973}
12974
12975fn value_to_f64(v: &Value) -> Option<f64> {
12976 match v {
12977 Value::SmallInt(n) => Some(f64::from(*n)),
12978 Value::Int(n) => Some(f64::from(*n)),
12979 #[allow(clippy::cast_precision_loss)]
12980 Value::BigInt(n) => Some(*n as f64),
12981 Value::Float(x) => Some(*x),
12982 _ => None,
12983 }
12984}
12985
12986fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
12990 let mut any = false;
12991 for item in &stmt.items {
12992 if let SelectItem::Expr { expr, .. } = item {
12993 any = any || expr_has_subquery(expr);
12994 }
12995 }
12996 if let Some(w) = &stmt.where_ {
12997 any = any || expr_has_subquery(w);
12998 }
12999 if let Some(h) = &stmt.having {
13000 any = any || expr_has_subquery(h);
13001 }
13002 for o in &stmt.order_by {
13003 any = any || expr_has_subquery(&o.expr);
13004 }
13005 for (_, peer) in &stmt.unions {
13006 any = any || expr_tree_has_subquery(peer);
13007 }
13008 any
13009}
13010
13011fn expr_has_subquery(e: &Expr) -> bool {
13012 match e {
13013 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13014 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13015 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13016 expr_has_subquery(expr)
13017 }
13018 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13019 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13020 Expr::Extract { source, .. } => expr_has_subquery(source),
13021 Expr::WindowFunction {
13022 args,
13023 partition_by,
13024 order_by,
13025 ..
13026 } => {
13027 args.iter().any(expr_has_subquery)
13028 || partition_by.iter().any(expr_has_subquery)
13029 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13030 }
13031 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13032 Expr::Array(items) => items.iter().any(expr_has_subquery),
13033 Expr::ArraySubscript { target, index } => {
13034 expr_has_subquery(target) || expr_has_subquery(index)
13035 }
13036 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13037 Expr::Case {
13038 operand,
13039 branches,
13040 else_branch,
13041 } => {
13042 operand.as_deref().is_some_and(expr_has_subquery)
13043 || branches
13044 .iter()
13045 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13046 || else_branch.as_deref().is_some_and(expr_has_subquery)
13047 }
13048 }
13049}
13050
13051fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13058 let lit = match v {
13059 Value::Null => Literal::Null,
13060 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13061 Value::Int(n) => Literal::Integer(i64::from(n)),
13062 Value::BigInt(n) => Literal::Integer(n),
13063 Value::Float(x) => Literal::Float(x),
13064 Value::Text(s) | Value::Json(s) => Literal::String(s),
13065 Value::Bool(b) => Literal::Bool(b),
13066 other => {
13067 return Err(EngineError::Unsupported(alloc::format!(
13068 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13069 other.data_type()
13070 )));
13071 }
13072 };
13073 Ok(Expr::Literal(lit))
13074}
13075
13076fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13082 let lit = match v {
13083 Value::Null => Literal::Null,
13084 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13085 Value::Int(n) => Literal::Integer(i64::from(n)),
13086 Value::BigInt(n) => Literal::Integer(n),
13087 Value::Float(x) => Literal::Float(x),
13088 Value::Text(s) | Value::Json(s) => Literal::String(s),
13089 Value::Bool(b) => Literal::Bool(b),
13090 Value::Vector(xs) => Literal::Vector(xs),
13091 Value::Date(days) => {
13095 let micros = (i64::from(days)) * 86_400_000_000;
13096 Literal::String(format_timestamp_micros_as_date(micros))
13097 }
13098 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13099 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13100 other => {
13101 return Err(EngineError::Unsupported(alloc::format!(
13102 "INSERT … SELECT cannot materialise value of type {:?}; \
13103 add an explicit CAST in the inner SELECT",
13104 other.data_type()
13105 )));
13106 }
13107 };
13108 Ok(Expr::Literal(lit))
13109}
13110
13111fn format_timestamp_micros(us: i64) -> String {
13112 let days = us.div_euclid(86_400_000_000);
13114 let intra_day = us.rem_euclid(86_400_000_000);
13115 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13116 let secs = intra_day / 1_000_000;
13117 let us_rem = intra_day % 1_000_000;
13118 let h = (secs / 3600) % 24;
13119 let m = (secs / 60) % 60;
13120 let s = secs % 60;
13121 if us_rem == 0 {
13122 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13123 } else {
13124 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13125 }
13126}
13127
13128fn format_timestamp_micros_as_date(us: i64) -> String {
13129 let days = us.div_euclid(86_400_000_000);
13132 let jdn = days + 2_440_588;
13134 let (y, mo, d) = jdn_to_ymd(jdn);
13135 alloc::format!("{y:04}-{mo:02}-{d:02}")
13136}
13137
13138fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13139 let l = jdn + 68569;
13141 let n = (4 * l) / 146_097;
13142 let l = l - (146_097 * n + 3) / 4;
13143 let i = (4000 * (l + 1)) / 1_461_001;
13144 let l = l - (1461 * i) / 4 + 31;
13145 let j = (80 * l) / 2447;
13146 let day = (l - (2447 * j) / 80) as u32;
13147 let l = j / 11;
13148 let month = (j + 2 - 12 * l) as u32;
13149 let year = 100 * (n - 49) + i + l;
13150 (year, month, day)
13151}
13152
13153fn format_numeric(scaled: i128, scale: u8) -> String {
13154 if scale == 0 {
13155 return alloc::format!("{scaled}");
13156 }
13157 let abs = scaled.unsigned_abs();
13158 let divisor = 10u128.pow(u32::from(scale));
13159 let whole = abs / divisor;
13160 let frac = abs % divisor;
13161 let sign = if scaled < 0 { "-" } else { "" };
13162 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13163}
13164
13165fn rewrite_column_in_source(
13189 src: &str,
13190 old: &str,
13191 new: &str,
13192) -> Result<alloc::string::String, EngineError> {
13193 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13194 EngineError::Unsupported(alloc::format!(
13195 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13196 failed to parse for rewrite ({e})"
13197 ))
13198 })?;
13199 rewrite_column_in_expr(&mut expr, old, new);
13200 Ok(alloc::format!("{expr}"))
13201}
13202
13203fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13211 match e {
13212 Expr::Column(c) => {
13213 if c.name.eq_ignore_ascii_case(old) {
13214 c.name = new.to_string();
13215 }
13216 }
13217 Expr::Binary { lhs, rhs, .. } => {
13218 rewrite_column_in_expr(lhs, old, new);
13219 rewrite_column_in_expr(rhs, old, new);
13220 }
13221 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13222 rewrite_column_in_expr(expr, old, new);
13223 }
13224 Expr::FunctionCall { args, .. } => {
13225 for a in args {
13226 rewrite_column_in_expr(a, old, new);
13227 }
13228 }
13229 Expr::Like { expr, pattern, .. } => {
13230 rewrite_column_in_expr(expr, old, new);
13231 rewrite_column_in_expr(pattern, old, new);
13232 }
13233 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13234 Expr::WindowFunction {
13235 args,
13236 partition_by,
13237 order_by,
13238 ..
13239 } => {
13240 for a in args {
13241 rewrite_column_in_expr(a, old, new);
13242 }
13243 for p in partition_by {
13244 rewrite_column_in_expr(p, old, new);
13245 }
13246 for (o, _) in order_by {
13247 rewrite_column_in_expr(o, old, new);
13248 }
13249 }
13250 Expr::Array(items) => {
13251 for elem in items {
13252 rewrite_column_in_expr(elem, old, new);
13253 }
13254 }
13255 Expr::ArraySubscript { target, index } => {
13256 rewrite_column_in_expr(target, old, new);
13257 rewrite_column_in_expr(index, old, new);
13258 }
13259 Expr::AnyAll { expr, array, .. } => {
13260 rewrite_column_in_expr(expr, old, new);
13261 rewrite_column_in_expr(array, old, new);
13262 }
13263 Expr::Case {
13264 operand,
13265 branches,
13266 else_branch,
13267 } => {
13268 if let Some(o) = operand {
13269 rewrite_column_in_expr(o, old, new);
13270 }
13271 for (w, t) in branches {
13272 rewrite_column_in_expr(w, old, new);
13273 rewrite_column_in_expr(t, old, new);
13274 }
13275 if let Some(e) = else_branch {
13276 rewrite_column_in_expr(e, old, new);
13277 }
13278 }
13279 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13283 Expr::Literal(_) | Expr::Placeholder(_) => {}
13284 }
13285}
13286
13287pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13295 match stmt {
13296 Statement::Select(s) => substitute_select(s, params)?,
13297 Statement::Insert(ins) => {
13298 for row in &mut ins.rows {
13299 for e in row {
13300 substitute_expr(e, params)?;
13301 }
13302 }
13303 }
13304 Statement::Update(u) => {
13305 for (_, e) in &mut u.assignments {
13306 substitute_expr(e, params)?;
13307 }
13308 if let Some(w) = &mut u.where_ {
13309 substitute_expr(w, params)?;
13310 }
13311 }
13312 Statement::Delete(d) => {
13313 if let Some(w) = &mut d.where_ {
13314 substitute_expr(w, params)?;
13315 }
13316 }
13317 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13318 _ => {}
13321 }
13322 Ok(())
13323}
13324
13325fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13326 for item in &mut s.items {
13327 if let SelectItem::Expr { expr, .. } = item {
13328 substitute_expr(expr, params)?;
13329 }
13330 }
13331 if let Some(w) = &mut s.where_ {
13332 substitute_expr(w, params)?;
13333 }
13334 if let Some(gs) = &mut s.group_by {
13335 for g in gs {
13336 substitute_expr(g, params)?;
13337 }
13338 }
13339 if let Some(h) = &mut s.having {
13340 substitute_expr(h, params)?;
13341 }
13342 for o in &mut s.order_by {
13343 substitute_expr(&mut o.expr, params)?;
13344 }
13345 for (_, peer) in &mut s.unions {
13346 substitute_select(peer, params)?;
13347 }
13348 if let Some(le) = s.limit {
13353 s.limit = Some(resolve_limit_placeholder(le, params)?);
13354 }
13355 if let Some(le) = s.offset {
13356 s.offset = Some(resolve_limit_placeholder(le, params)?);
13357 }
13358 Ok(())
13359}
13360
13361fn resolve_limit_placeholder(
13362 le: spg_sql::ast::LimitExpr,
13363 params: &[Value],
13364) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13365 use spg_sql::ast::LimitExpr;
13366 match le {
13367 LimitExpr::Literal(_) => Ok(le),
13368 LimitExpr::Placeholder(n) => {
13369 let idx = usize::from(n).saturating_sub(1);
13370 let v = params.get(idx).ok_or_else(|| {
13371 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13372 n,
13373 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13374 })
13375 })?;
13376 let int = match v {
13377 Value::SmallInt(x) => Some(i64::from(*x)),
13378 Value::Int(x) => Some(i64::from(*x)),
13379 Value::BigInt(x) => Some(*x),
13380 _ => None,
13381 }
13382 .ok_or_else(|| {
13383 EngineError::Unsupported(alloc::format!(
13384 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13385 ))
13386 })?;
13387 if int < 0 {
13388 return Err(EngineError::Unsupported(alloc::format!(
13389 "LIMIT/OFFSET ${n} bound to negative value {int}"
13390 )));
13391 }
13392 let bounded = u32::try_from(int).map_err(|_| {
13393 EngineError::Unsupported(alloc::format!(
13394 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13395 ))
13396 })?;
13397 Ok(LimitExpr::Literal(bounded))
13398 }
13399 }
13400}
13401
13402fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13403 if let Expr::Placeholder(n) = e {
13404 let idx = usize::from(*n).saturating_sub(1);
13405 let v = params.get(idx).ok_or_else(|| {
13406 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13407 n: *n,
13408 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13409 })
13410 })?;
13411 *e = Expr::Literal(value_to_literal(v.clone()));
13412 return Ok(());
13413 }
13414 match e {
13415 Expr::Binary { lhs, rhs, .. } => {
13416 substitute_expr(lhs, params)?;
13417 substitute_expr(rhs, params)?;
13418 }
13419 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13420 substitute_expr(expr, params)?;
13421 }
13422 Expr::FunctionCall { args, .. } => {
13423 for a in args {
13424 substitute_expr(a, params)?;
13425 }
13426 }
13427 Expr::Like { expr, pattern, .. } => {
13428 substitute_expr(expr, params)?;
13429 substitute_expr(pattern, params)?;
13430 }
13431 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13432 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13433 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13434 Expr::InSubquery { expr, subquery, .. } => {
13435 substitute_expr(expr, params)?;
13436 substitute_select(subquery, params)?;
13437 }
13438 Expr::WindowFunction {
13439 args,
13440 partition_by,
13441 order_by,
13442 ..
13443 } => {
13444 for a in args {
13445 substitute_expr(a, params)?;
13446 }
13447 for p in partition_by {
13448 substitute_expr(p, params)?;
13449 }
13450 for (e, _) in order_by {
13451 substitute_expr(e, params)?;
13452 }
13453 }
13454 Expr::Literal(_) | Expr::Column(_) => {}
13455 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13457 Expr::Array(items) => {
13458 for elem in items {
13459 substitute_expr(elem, params)?;
13460 }
13461 }
13462 Expr::ArraySubscript { target, index } => {
13463 substitute_expr(target, params)?;
13464 substitute_expr(index, params)?;
13465 }
13466 Expr::AnyAll { expr, array, .. } => {
13467 substitute_expr(expr, params)?;
13468 substitute_expr(array, params)?;
13469 }
13470 Expr::Case {
13471 operand,
13472 branches,
13473 else_branch,
13474 } => {
13475 if let Some(o) = operand {
13476 substitute_expr(o, params)?;
13477 }
13478 for (w, t) in branches {
13479 substitute_expr(w, params)?;
13480 substitute_expr(t, params)?;
13481 }
13482 if let Some(e) = else_branch {
13483 substitute_expr(e, params)?;
13484 }
13485 }
13486 }
13487 Ok(())
13488}
13489
13490fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13508 use core::cmp::Ordering;
13509 match (a, b) {
13510 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13511 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13512 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13513 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13514 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13515 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13516 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13517 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13518 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13519 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13520 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13521 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13522 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13523 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13524 (Value::SmallInt(n), Value::Float(x)) => {
13526 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13527 }
13528 (Value::Float(x), Value::SmallInt(n)) => {
13529 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13530 }
13531 (Value::Int(n), Value::Float(x)) => {
13532 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13533 }
13534 (Value::Float(x), Value::Int(n)) => {
13535 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13536 }
13537 (Value::BigInt(n), Value::Float(x)) => {
13538 #[allow(clippy::cast_precision_loss)]
13539 let nf = *n as f64;
13540 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13541 }
13542 (Value::Float(x), Value::BigInt(n)) => {
13543 #[allow(clippy::cast_precision_loss)]
13544 let nf = *n as f64;
13545 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13546 }
13547 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13550 }
13551}
13552
13553fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13560 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13561 out.push('[');
13562 for (i, b) in bounds.iter().enumerate() {
13563 if i > 0 {
13564 out.push_str(", ");
13565 }
13566 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13567 if needs_quote {
13568 out.push('"');
13569 for ch in b.chars() {
13570 if ch == '"' || ch == '\\' {
13571 out.push('\\');
13572 }
13573 out.push(ch);
13574 }
13575 out.push('"');
13576 } else {
13577 out.push_str(b);
13578 }
13579 }
13580 out.push(']');
13581 out
13582}
13583
13584pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13594 match v {
13595 Value::Null => "NULL".to_string(),
13596 Value::SmallInt(n) => alloc::format!("{n}"),
13597 Value::Int(n) => alloc::format!("{n}"),
13598 Value::BigInt(n) => alloc::format!("{n}"),
13599 Value::Float(x) => alloc::format!("{x:?}"),
13600 Value::Text(s) | Value::Json(s) => s.clone(),
13601 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13602 Value::Date(d) => eval::format_date(*d),
13603 Value::Timestamp(t) => eval::format_timestamp(*t),
13604 Value::Time(us) => eval::format_time(*us),
13606 Value::Year(y) => alloc::format!("{y:04}"),
13608 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13610 Value::Money(c) => eval::format_money(*c),
13612 v @ Value::Range { .. } => format_range_str(v),
13614 Value::Hstore(pairs) => format_hstore_str(pairs),
13616 Value::IntArray2D(rows) => format_int_2d_text(rows),
13618 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13619 Value::TextArray2D(rows) => format_text_2d_text(rows),
13620 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13621 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13622 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13623 alloc::format!("{v:?}")
13627 }
13628 _ => alloc::format!("{v:?}"),
13632 }
13633}
13634
13635const fn is_internal_table_name(_name: &str) -> bool {
13642 false
13643}
13644
13645fn value_to_literal(v: Value) -> Literal {
13646 match v {
13647 Value::Null => Literal::Null,
13648 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13649 Value::Int(n) => Literal::Integer(i64::from(n)),
13650 Value::BigInt(n) => Literal::Integer(n),
13651 Value::Float(x) => Literal::Float(x),
13652 Value::Text(s) | Value::Json(s) => Literal::String(s),
13653 Value::Bool(b) => Literal::Bool(b),
13654 Value::Vector(v) => Literal::Vector(v),
13655 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
13656 Value::Date(d) => Literal::String(eval::format_date(d)),
13657 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
13658 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
13664 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
13669 Value::TextArray(items) => Literal::String(eval::format_text_array(&items)),
13674 Value::IntArray(items) => Literal::String(eval::format_int_array(&items)),
13675 Value::BigIntArray(items) => Literal::String(eval::format_bigint_array(&items)),
13676 Value::Interval { months, micros } => Literal::Interval {
13677 months,
13678 micros,
13679 text: eval::format_interval(months, micros),
13680 },
13681 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
13684 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
13685 v => Literal::String(alloc::format!("{v:?}")),
13689 }
13690}
13691
13692fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
13693 let Some(now) = now_micros else {
13694 return;
13695 };
13696 match stmt {
13697 Statement::Select(s) => rewrite_select_clock(s, now),
13698 Statement::Insert(ins) => {
13699 for row in &mut ins.rows {
13700 for e in row {
13701 rewrite_expr_clock(e, now);
13702 }
13703 }
13704 }
13705 _ => {}
13706 }
13707}
13708
13709fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
13710 for item in &mut s.items {
13711 if let SelectItem::Expr { expr, .. } = item {
13712 rewrite_expr_clock(expr, now);
13713 }
13714 }
13715 if let Some(w) = &mut s.where_ {
13716 rewrite_expr_clock(w, now);
13717 }
13718 if let Some(gs) = &mut s.group_by {
13719 for g in gs {
13720 rewrite_expr_clock(g, now);
13721 }
13722 }
13723 if let Some(h) = &mut s.having {
13724 rewrite_expr_clock(h, now);
13725 }
13726 for o in &mut s.order_by {
13727 rewrite_expr_clock(&mut o.expr, now);
13728 }
13729 for (_, peer) in &mut s.unions {
13730 rewrite_select_clock(peer, now);
13731 }
13732}
13733
13734fn rewrite_expr_clock(e: &mut Expr, now: i64) {
13742 if let Some(replacement) = clock_replacement_for(e, now) {
13746 *e = replacement;
13747 return;
13748 }
13749 match e {
13750 Expr::Binary { lhs, rhs, .. } => {
13751 rewrite_expr_clock(lhs, now);
13752 rewrite_expr_clock(rhs, now);
13753 }
13754 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13755 rewrite_expr_clock(expr, now);
13756 }
13757 Expr::FunctionCall { args, .. } => {
13758 for a in args {
13759 rewrite_expr_clock(a, now);
13760 }
13761 }
13762 Expr::Like { expr, pattern, .. } => {
13763 rewrite_expr_clock(expr, now);
13764 rewrite_expr_clock(pattern, now);
13765 }
13766 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
13767 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
13771 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
13772 Expr::InSubquery { expr, subquery, .. } => {
13773 rewrite_expr_clock(expr, now);
13774 rewrite_select_clock(subquery, now);
13775 }
13776 Expr::WindowFunction {
13779 args,
13780 partition_by,
13781 order_by,
13782 ..
13783 } => {
13784 for a in args {
13785 rewrite_expr_clock(a, now);
13786 }
13787 for p in partition_by {
13788 rewrite_expr_clock(p, now);
13789 }
13790 for (e, _) in order_by {
13791 rewrite_expr_clock(e, now);
13792 }
13793 }
13794 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
13795 Expr::Array(items) => {
13796 for elem in items {
13797 rewrite_expr_clock(elem, now);
13798 }
13799 }
13800 Expr::ArraySubscript { target, index } => {
13801 rewrite_expr_clock(target, now);
13802 rewrite_expr_clock(index, now);
13803 }
13804 Expr::AnyAll { expr, array, .. } => {
13805 rewrite_expr_clock(expr, now);
13806 rewrite_expr_clock(array, now);
13807 }
13808 Expr::Case {
13809 operand,
13810 branches,
13811 else_branch,
13812 } => {
13813 if let Some(o) = operand {
13814 rewrite_expr_clock(o, now);
13815 }
13816 for (w, t) in branches {
13817 rewrite_expr_clock(w, now);
13818 rewrite_expr_clock(t, now);
13819 }
13820 if let Some(e) = else_branch {
13821 rewrite_expr_clock(e, now);
13822 }
13823 }
13824 }
13825}
13826
13827fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
13834 let (kind, name) = match e {
13835 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
13836 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
13837 _ => return None,
13838 };
13839 enum ClockShape {
13847 Timestamp,
13848 Date,
13849 UnixSeconds,
13850 }
13851 let shape = match name.len() {
13852 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
13853 Some(ClockShape::Timestamp)
13854 }
13855 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
13856 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
13857 Some(ClockShape::UnixSeconds)
13858 }
13859 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
13860 _ => None,
13861 };
13862 let shape = shape?;
13863 let payload = match shape {
13864 ClockShape::Timestamp => now,
13865 ClockShape::Date => now.div_euclid(86_400_000_000),
13866 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
13867 };
13868 let target = match shape {
13869 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
13870 ClockShape::Date => spg_sql::ast::CastTarget::Date,
13871 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
13872 };
13873 Some(Expr::Cast {
13874 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
13875 target,
13876 })
13877}
13878
13879#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13880enum ClockSite {
13881 Fn,
13882 BareIdent,
13883}
13884
13885fn expand_group_by_all(s: &mut SelectStatement) {
13896 if !s.group_by_all {
13897 for (_, peer) in &mut s.unions {
13898 expand_group_by_all(peer);
13899 }
13900 return;
13901 }
13902 let mut groups: Vec<Expr> = Vec::new();
13903 for item in &s.items {
13904 if let SelectItem::Expr { expr, .. } = item
13905 && !aggregate::contains_aggregate(expr)
13906 {
13907 groups.push(expr.clone());
13908 }
13909 }
13910 s.group_by = Some(groups);
13911 s.group_by_all = false;
13912 for (_, peer) in &mut s.unions {
13913 expand_group_by_all(peer);
13914 }
13915}
13916
13917fn resolve_order_by_position(s: &mut SelectStatement) {
13918 for order in &mut s.order_by {
13923 match &order.expr {
13924 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
13925 if let Ok(idx_one_based) = usize::try_from(*n) {
13926 let idx = idx_one_based - 1;
13927 if idx < s.items.len()
13928 && let SelectItem::Expr { expr, .. } = &s.items[idx]
13929 {
13930 order.expr = expr.clone();
13931 }
13932 }
13933 }
13934 Expr::Column(c) if c.qualifier.is_none() => {
13935 for item in &s.items {
13937 if let SelectItem::Expr {
13938 expr,
13939 alias: Some(a),
13940 } = item
13941 && a == &c.name
13942 {
13943 order.expr = expr.clone();
13944 break;
13945 }
13946 }
13947 }
13948 _ => {}
13949 }
13950 }
13951 for (_, peer) in &mut s.unions {
13952 resolve_order_by_position(peer);
13953 }
13954}
13955
13956fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
13969 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
13970 match keep {
13971 Some(k) if k < tagged.len() && k > 0 => {
13972 let pivot = k - 1;
13973 tagged.select_nth_unstable_by(pivot, cmp);
13974 tagged[..k].sort_by(cmp);
13975 tagged.truncate(k);
13976 }
13977 _ => {
13978 tagged.sort_by(cmp);
13979 }
13980 }
13981}
13982
13983fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
13984 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
13985}
13986
13987fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
13991 use core::cmp::Ordering;
13992 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
13993 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
13994 let ord = if descs.get(i).copied().unwrap_or(false) {
13995 ord.reverse()
13996 } else {
13997 ord
13998 };
13999 if ord != Ordering::Equal {
14000 return ord;
14001 }
14002 }
14003 Ordering::Equal
14004}
14005
14006fn build_order_keys(
14009 order_by: &[OrderBy],
14010 row: &Row,
14011 ctx: &EvalContext,
14012) -> Result<Vec<f64>, EngineError> {
14013 let mut keys = Vec::with_capacity(order_by.len());
14014 for o in order_by {
14015 let v = eval::eval_expr(&o.expr, row, ctx)?;
14016 keys.push(value_to_order_key(&v)?);
14017 }
14018 Ok(keys)
14019}
14020
14021fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14025 if let Some(off) = offset {
14026 let off = off as usize;
14027 if off >= rows.len() {
14028 rows.clear();
14029 } else {
14030 rows.drain(..off);
14031 }
14032 }
14033 if let Some(n) = limit {
14034 rows.truncate(n as usize);
14035 }
14036}
14037
14038fn apply_offset_and_limit_tagged(
14049 tagged: &mut Vec<(Vec<f64>, Row)>,
14050 offset: Option<u32>,
14051 limit: Option<u32>,
14052 with_ties: bool,
14053) {
14054 if let Some(off) = offset {
14055 let off = off as usize;
14056 if off >= tagged.len() {
14057 tagged.clear();
14058 } else {
14059 tagged.drain(..off);
14060 }
14061 }
14062 if let Some(n) = limit {
14063 let n = n as usize;
14064 if with_ties && n > 0 && n < tagged.len() {
14065 let cutoff_key = tagged[n - 1].0.clone();
14066 let mut end = n;
14067 while end < tagged.len() && tagged[end].0 == cutoff_key {
14068 end += 1;
14069 }
14070 tagged.truncate(end);
14071 } else {
14072 tagged.truncate(n);
14073 }
14074 }
14075}
14076
14077fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14083 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14084 return Err(EngineError::Unsupported(alloc::string::String::from(
14085 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14086 )));
14087 }
14088 Ok(())
14089}
14090
14091fn resolve_foreign_key(
14105 local_table_name: &str,
14106 local_cols: &[ColumnSchema],
14107 fk: spg_sql::ast::ForeignKeyConstraint,
14108 catalog: &Catalog,
14109) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14110 let mut local_columns = Vec::with_capacity(fk.columns.len());
14112 for name in &fk.columns {
14113 let pos = local_cols
14114 .iter()
14115 .position(|c| c.name == *name)
14116 .ok_or_else(|| {
14117 EngineError::Unsupported(alloc::format!(
14118 "FOREIGN KEY references unknown local column {name:?}"
14119 ))
14120 })?;
14121 local_columns.push(pos);
14122 }
14123 let is_self_ref = fk.parent_table == local_table_name;
14127 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14128 (local_cols, local_table_name)
14129 } else {
14130 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14131 EngineError::Storage(StorageError::TableNotFound {
14132 name: fk.parent_table.clone(),
14133 })
14134 })?;
14135 (
14136 parent_table.schema().columns.as_slice(),
14137 fk.parent_table.as_str(),
14138 )
14139 };
14140 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14145 if fk.columns.len() != 1 {
14146 return Err(EngineError::Unsupported(
14147 "composite FOREIGN KEY without explicit parent column list is not supported \
14148 — list the parent columns explicitly"
14149 .into(),
14150 ));
14151 }
14152 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14154 .ok_or_else(|| {
14155 EngineError::Unsupported(alloc::format!(
14156 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14157 to default the FOREIGN KEY against"
14158 ))
14159 })?;
14160 alloc::vec![pos]
14161 } else {
14162 let mut out = Vec::with_capacity(fk.parent_columns.len());
14163 for name in &fk.parent_columns {
14164 let pos = parent_cols_for_lookup
14165 .iter()
14166 .position(|c| c.name == *name)
14167 .ok_or_else(|| {
14168 EngineError::Unsupported(alloc::format!(
14169 "FOREIGN KEY references unknown parent column \
14170 {name:?} on table {parent_table_str:?}"
14171 ))
14172 })?;
14173 out.push(pos);
14174 }
14175 out
14176 };
14177 if parent_columns.len() != local_columns.len() {
14178 return Err(EngineError::Unsupported(alloc::format!(
14179 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14180 local_columns.len(),
14181 parent_columns.len()
14182 )));
14183 }
14184 if !is_self_ref {
14194 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14195 let primary_parent_col = parent_columns[0];
14196 let has_btree = parent_table
14197 .schema()
14198 .columns
14199 .get(primary_parent_col)
14200 .is_some()
14201 && parent_table.indices().iter().any(|idx| {
14202 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14203 && idx.column_position == primary_parent_col
14204 && idx.partial_predicate.is_none()
14205 });
14206 if !has_btree {
14207 return Err(EngineError::Unsupported(alloc::format!(
14208 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14209 index — create one with `CREATE INDEX ... ON {} ({})` first",
14210 parent_table_str,
14211 parent_table_str,
14212 parent_table.schema().columns[primary_parent_col].name,
14213 )));
14214 }
14215 }
14216 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14217 let on_update = fk_action_sql_to_storage(fk.on_update);
14218 Ok(spg_storage::ForeignKeyConstraint {
14219 name: fk.name,
14220 local_columns,
14221 parent_table: fk.parent_table,
14222 parent_columns,
14223 on_delete,
14224 on_update,
14225 })
14226}
14227
14228fn pick_pk_index_column(
14234 catalog: &Catalog,
14235 parent_name: &str,
14236 is_self_ref: bool,
14237 local_cols: &[ColumnSchema],
14238) -> Option<usize> {
14239 if is_self_ref {
14240 let _ = local_cols;
14244 return Some(0);
14245 }
14246 let parent = catalog.get(parent_name)?;
14247 parent.indices().iter().find_map(|idx| {
14248 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14249 && idx.partial_predicate.is_none()
14250 && idx.included_columns.is_empty()
14251 && idx.expression.is_none()
14252 {
14253 Some(idx.column_position)
14254 } else {
14255 None
14256 }
14257 })
14258}
14259
14260fn resolve_on_conflict_columns(
14267 catalog: &Catalog,
14268 table_name: &str,
14269 target: &[String],
14270) -> Result<Vec<usize>, EngineError> {
14271 let table = catalog.get(table_name).ok_or_else(|| {
14272 EngineError::Storage(StorageError::TableNotFound {
14273 name: table_name.into(),
14274 })
14275 })?;
14276 if target.is_empty() {
14277 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14287 return Ok(uc.columns.clone());
14288 }
14289 let pos = table
14290 .indices()
14291 .iter()
14292 .find_map(|idx| {
14293 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14294 && idx.partial_predicate.is_none()
14295 && idx.included_columns.is_empty()
14296 && idx.expression.is_none()
14297 {
14298 Some(idx.column_position)
14299 } else {
14300 None
14301 }
14302 })
14303 .ok_or_else(|| {
14304 EngineError::Unsupported(alloc::format!(
14305 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14306 ))
14307 })?;
14308 return Ok(alloc::vec![pos]);
14309 }
14310 let mut out = Vec::with_capacity(target.len());
14311 for name in target {
14312 let pos = table
14313 .schema()
14314 .columns
14315 .iter()
14316 .position(|c| c.name == *name)
14317 .ok_or_else(|| {
14318 EngineError::Unsupported(alloc::format!(
14319 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14320 ))
14321 })?;
14322 out.push(pos);
14323 }
14324 Ok(out)
14325}
14326
14327fn on_conflict_key_exists(
14330 catalog: &Catalog,
14331 table_name: &str,
14332 column_pos: usize,
14333 key: &Value,
14334) -> bool {
14335 let Some(table) = catalog.get(table_name) else {
14336 return false;
14337 };
14338 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14339 return false;
14340 };
14341 table.indices().iter().any(|idx| {
14342 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14343 && idx.column_position == column_pos
14344 && idx.partial_predicate.is_none()
14345 && !idx.lookup_eq(&idx_key).is_empty()
14346 })
14347}
14348
14349fn lookup_row_position_by_keys(
14355 catalog: &Catalog,
14356 table_name: &str,
14357 column_positions: &[usize],
14358 key: &[&Value],
14359) -> Option<usize> {
14360 let table = catalog.get(table_name)?;
14361 table.rows().iter().position(|r| {
14362 column_positions
14363 .iter()
14364 .enumerate()
14365 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14366 })
14367}
14368
14369fn on_conflict_keys_exist(
14374 catalog: &Catalog,
14375 table_name: &str,
14376 column_positions: &[usize],
14377 key: &[&Value],
14378) -> bool {
14379 if column_positions.len() == 1 {
14380 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14381 }
14382 let Some(table) = catalog.get(table_name) else {
14383 return false;
14384 };
14385 table.rows().iter().any(|r| {
14386 column_positions
14387 .iter()
14388 .enumerate()
14389 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14390 })
14391}
14392
14393fn apply_on_conflict_assignments(
14406 catalog: &Catalog,
14407 table_name: &str,
14408 target_pos: usize,
14409 incoming: &[Value],
14410 assignments: &[(String, Expr)],
14411 where_: Option<&Expr>,
14412) -> Result<Option<Vec<Value>>, EngineError> {
14413 let table = catalog.get(table_name).ok_or_else(|| {
14414 EngineError::Storage(StorageError::TableNotFound {
14415 name: table_name.into(),
14416 })
14417 })?;
14418 let schema_cols = table.schema().columns.clone();
14419 let existing = table
14420 .rows()
14421 .get(target_pos)
14422 .ok_or_else(|| {
14423 EngineError::Unsupported(alloc::format!(
14424 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14425 ))
14426 })?
14427 .clone();
14428 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14429 if let Some(w) = where_ {
14431 let pred = w.clone();
14432 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14433 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14434 if !matches!(v, Value::Bool(true)) {
14435 return Ok(None);
14436 }
14437 }
14438 let mut new_values = existing.values.clone();
14439 for (col_name, expr) in assignments {
14440 let target_idx = schema_cols
14441 .iter()
14442 .position(|c| c.name == *col_name)
14443 .ok_or_else(|| {
14444 EngineError::Eval(EvalError::ColumnNotFound {
14445 name: col_name.clone(),
14446 })
14447 })?;
14448 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14449 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14450 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14451 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14452 new_values[target_idx] = coerced;
14453 }
14454 Ok(Some(new_values))
14455}
14456
14457fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14462 use spg_sql::ast::ColumnName;
14463 match expr {
14464 Expr::Column(ColumnName { qualifier, name })
14465 if qualifier
14466 .as_deref()
14467 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14468 {
14469 let pos = schema_cols.iter().position(|c| c.name == name);
14470 match pos {
14471 Some(p) => {
14472 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14473 value_to_literal_expr(v)
14474 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14475 }
14476 None => Expr::Column(ColumnName { qualifier, name }),
14477 }
14478 }
14479 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14480 op,
14481 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14482 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14483 },
14484 Expr::Unary { op, expr } => Expr::Unary {
14485 op,
14486 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14487 },
14488 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14489 name,
14490 args: args
14491 .into_iter()
14492 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14493 .collect(),
14494 },
14495 other => other,
14496 }
14497}
14498
14499fn enforce_uniqueness_inserts(
14522 catalog: &Catalog,
14523 child_table: &str,
14524 constraints: &[spg_storage::UniquenessConstraint],
14525 rows: &[Vec<Value>],
14526) -> Result<(), EngineError> {
14527 if constraints.is_empty() {
14528 return Ok(());
14529 }
14530 let table = catalog.get(child_table).ok_or_else(|| {
14531 EngineError::Storage(StorageError::TableNotFound {
14532 name: child_table.into(),
14533 })
14534 })?;
14535 let schema = table.schema();
14536 for uc in constraints {
14537 for (batch_idx, row_values) in rows.iter().enumerate() {
14538 let key: Vec<Value> = uc
14547 .columns
14548 .iter()
14549 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14550 .collect();
14551 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14552 if has_null && !uc.nulls_not_distinct {
14557 continue;
14558 }
14559 let collides_in_table = table.rows().iter().any(|prow| {
14561 uc.columns.iter().enumerate().all(|(i, &p)| {
14562 prow.values
14563 .get(p)
14564 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14565 })
14566 });
14567 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14569 uc.columns.iter().enumerate().all(|(i, &p)| {
14570 earlier
14571 .get(p)
14572 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14573 })
14574 });
14575 if collides_in_table || collides_in_batch {
14576 let kind = if uc.is_primary_key {
14577 "PRIMARY KEY"
14578 } else {
14579 "UNIQUE"
14580 };
14581 let col_names: Vec<String> = uc
14582 .columns
14583 .iter()
14584 .map(|&i| table.schema().columns[i].name.clone())
14585 .collect();
14586 return Err(EngineError::Unsupported(alloc::format!(
14587 "{kind} violation on {child_table:?} columns {col_names:?}: \
14588 row #{batch_idx} duplicates an existing key"
14589 )));
14590 }
14591 }
14592 }
14593 Ok(())
14594}
14595
14596fn collated_key_cell(
14603 v: &spg_storage::Value,
14604 column_position: usize,
14605 schema: &spg_storage::TableSchema,
14606) -> spg_storage::Value {
14607 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14608 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14609 spg_storage::Value::Text(s.to_ascii_lowercase())
14610 }
14611 _ => v.clone(),
14612 }
14613}
14614
14615fn predicate_truthy(v: &spg_storage::Value) -> bool {
14623 use spg_storage::Value as V;
14624 match v {
14625 V::Bool(b) => *b,
14626 V::Int(n) => *n != 0,
14627 V::BigInt(n) => *n != 0,
14628 V::SmallInt(n) => *n != 0,
14629 _ => false,
14630 }
14631}
14632
14633fn check_existing_unique_violation(
14638 idx: &spg_storage::Index,
14639 schema: &spg_storage::TableSchema,
14640 rows: &[spg_storage::Row],
14641) -> Result<(), EngineError> {
14642 let predicate_expr = match idx.partial_predicate.as_deref() {
14643 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14644 EngineError::Unsupported(alloc::format!(
14645 "stored partial predicate {s:?} failed to re-parse: {e:?}"
14646 ))
14647 })?),
14648 None => None,
14649 };
14650 let ctx = eval::EvalContext::new(&schema.columns, None);
14651 let key_positions = unique_key_positions(idx);
14652 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
14653 for row in rows {
14654 if let Some(expr) = &predicate_expr {
14655 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
14656 EngineError::Unsupported(alloc::format!(
14657 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
14658 ))
14659 })?;
14660 if !predicate_truthy(&v) {
14661 continue;
14662 }
14663 }
14664 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
14665 .iter()
14666 .map(|&p| {
14667 let v = row
14668 .values
14669 .get(p)
14670 .cloned()
14671 .unwrap_or(spg_storage::Value::Null);
14672 collated_key_cell(&v, p, schema)
14673 })
14674 .collect();
14675 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14676 continue;
14677 }
14678 if seen.iter().any(|other| *other == key) {
14679 return Err(EngineError::Unsupported(alloc::format!(
14680 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
14681 idx.name
14682 )));
14683 }
14684 seen.push(key);
14685 }
14686 Ok(())
14687}
14688
14689fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
14693 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
14694 out.push(idx.column_position);
14695 out.extend_from_slice(&idx.extra_column_positions);
14696 out
14697}
14698
14699fn enforce_unique_index_inserts(
14707 catalog: &Catalog,
14708 table_name: &str,
14709 rows: &[alloc::vec::Vec<spg_storage::Value>],
14710) -> Result<(), EngineError> {
14711 let table = catalog.get(table_name).ok_or_else(|| {
14712 EngineError::Storage(StorageError::TableNotFound {
14713 name: table_name.into(),
14714 })
14715 })?;
14716 let schema = table.schema();
14717 let ctx = eval::EvalContext::new(&schema.columns, None);
14718 for idx in table.indices() {
14719 if !idx.is_unique {
14720 continue;
14721 }
14722 let predicate_expr = match idx.partial_predicate.as_deref() {
14724 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14725 EngineError::Unsupported(alloc::format!(
14726 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
14727 idx.name
14728 ))
14729 })?),
14730 None => None,
14731 };
14732 let key_positions = unique_key_positions(idx);
14733 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
14734 key_positions
14738 .iter()
14739 .map(|&p| {
14740 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
14741 collated_key_cell(&v, p, schema)
14742 })
14743 .collect()
14744 };
14745 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
14749 let Some(expr) = &predicate_expr else {
14750 return Ok(true);
14751 };
14752 let tmp_row = spg_storage::Row {
14753 values: values.to_vec(),
14754 };
14755 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14756 EngineError::Unsupported(alloc::format!(
14757 "UNIQUE INDEX {:?} predicate eval: {e:?}",
14758 idx.name
14759 ))
14760 })?;
14761 Ok(predicate_truthy(&v))
14762 };
14763 for (batch_idx, row_values) in rows.iter().enumerate() {
14764 if !participates(row_values)? {
14765 continue;
14766 }
14767 let key = key_of(row_values);
14768 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14769 continue;
14770 }
14771 for prow in table.rows() {
14773 if !participates(&prow.values)? {
14774 continue;
14775 }
14776 if key_of(&prow.values) == key {
14777 return Err(EngineError::Unsupported(alloc::format!(
14778 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14779 row #{batch_idx} duplicates an existing key",
14780 idx.name
14781 )));
14782 }
14783 }
14784 for earlier in &rows[..batch_idx] {
14786 if !participates(earlier)? {
14787 continue;
14788 }
14789 if key_of(earlier) == key {
14790 return Err(EngineError::Unsupported(alloc::format!(
14791 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14792 row #{batch_idx} duplicates an earlier row in the same batch",
14793 idx.name
14794 )));
14795 }
14796 }
14797 }
14798 }
14799 Ok(())
14800}
14801
14802fn any_column_changed(
14810 filter_cols: &[String],
14811 schema_cols: &[ColumnSchema],
14812 old_row: &Row,
14813 new_row: &Row,
14814) -> bool {
14815 for col_name in filter_cols {
14816 let Some(pos) = schema_cols
14817 .iter()
14818 .position(|c| c.name.eq_ignore_ascii_case(col_name))
14819 else {
14820 continue;
14821 };
14822 let old_v = old_row.values.get(pos);
14823 let new_v = new_row.values.get(pos);
14824 if old_v != new_v {
14825 return true;
14826 }
14827 }
14828 false
14829}
14830
14831fn enforce_check_constraints(
14836 catalog: &Catalog,
14837 table_name: &str,
14838 rows: &[alloc::vec::Vec<spg_storage::Value>],
14839) -> Result<(), EngineError> {
14840 let table = catalog.get(table_name).ok_or_else(|| {
14841 EngineError::Storage(StorageError::TableNotFound {
14842 name: table_name.into(),
14843 })
14844 })?;
14845 let schema = table.schema();
14846 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
14850 alloc::vec::Vec::new();
14851 for (idx, col) in schema.columns.iter().enumerate() {
14852 let Some(dname) = &col.user_domain_type else {
14853 continue;
14854 };
14855 let Some(dom) = catalog.domain_types().get(dname) else {
14856 continue;
14857 };
14858 let mut parsed_for_col: alloc::vec::Vec<Expr> =
14859 alloc::vec::Vec::with_capacity(dom.checks.len());
14860 for src in &dom.checks {
14861 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14862 EngineError::Unsupported(alloc::format!(
14863 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
14864 col.name
14865 ))
14866 })?;
14867 parsed_for_col.push(expr);
14868 }
14869 if !parsed_for_col.is_empty() {
14870 domain_checks_per_col.push((idx, parsed_for_col));
14871 }
14872 }
14873 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
14874 return Ok(());
14875 }
14876 let ctx = eval::EvalContext::new(&schema.columns, None);
14877 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
14878 for (i, src) in schema.checks.iter().enumerate() {
14879 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14880 EngineError::Unsupported(alloc::format!(
14881 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
14882 ))
14883 })?;
14884 parsed.push((i, expr));
14885 }
14886 for (batch_idx, row_values) in rows.iter().enumerate() {
14887 let tmp_row = spg_storage::Row {
14888 values: row_values.clone(),
14889 };
14890 for (i, expr) in &parsed {
14891 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14892 EngineError::Unsupported(alloc::format!(
14893 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
14894 ))
14895 })?;
14896 if matches!(v, spg_storage::Value::Bool(false)) {
14898 return Err(EngineError::Unsupported(alloc::format!(
14899 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
14900 schema.checks[*i]
14901 )));
14902 }
14903 }
14904 for (col_idx, checks) in &domain_checks_per_col {
14910 let cell = row_values
14911 .get(*col_idx)
14912 .cloned()
14913 .unwrap_or(spg_storage::Value::Null);
14914 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
14915 "value",
14916 schema.columns[*col_idx].ty,
14917 schema.columns[*col_idx].nullable,
14918 )];
14919 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
14920 let synth_row = spg_storage::Row {
14921 values: alloc::vec![cell],
14922 };
14923 for (ci, expr) in checks.iter().enumerate() {
14924 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
14925 EngineError::Unsupported(alloc::format!(
14926 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
14927 schema.columns[*col_idx].name
14928 ))
14929 })?;
14930 if matches!(v, spg_storage::Value::Bool(false)) {
14931 return Err(EngineError::Unsupported(alloc::format!(
14932 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
14933 schema.columns[*col_idx].name
14934 )));
14935 }
14936 }
14937 }
14938 }
14939 Ok(())
14940}
14941
14942fn enforce_fk_inserts(
14943 catalog: &Catalog,
14944 child_table: &str,
14945 fks: &[spg_storage::ForeignKeyConstraint],
14946 rows: &[Vec<Value>],
14947) -> Result<(), EngineError> {
14948 for fk in fks {
14949 let parent_is_self = fk.parent_table == child_table;
14950 let parent = if parent_is_self {
14951 catalog.get(child_table).ok_or_else(|| {
14954 EngineError::Storage(StorageError::TableNotFound {
14955 name: child_table.into(),
14956 })
14957 })?
14958 } else {
14959 catalog.get(&fk.parent_table).ok_or_else(|| {
14960 EngineError::Storage(StorageError::TableNotFound {
14961 name: fk.parent_table.clone(),
14962 })
14963 })?
14964 };
14965 for (batch_idx, row_values) in rows.iter().enumerate() {
14966 if fk.local_columns.len() == 1 {
14970 let v = &row_values[fk.local_columns[0]];
14971 if matches!(v, Value::Null) {
14972 continue;
14973 }
14974 let parent_col = fk.parent_columns[0];
14975 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
14976 EngineError::Unsupported(alloc::format!(
14977 "FOREIGN KEY column value of type {:?} is not index-eligible",
14978 v.data_type()
14979 ))
14980 })?;
14981 let present_committed = parent.indices().iter().any(|idx| {
14982 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14983 && idx.column_position == parent_col
14984 && idx.partial_predicate.is_none()
14985 && !idx.lookup_eq(&key).is_empty()
14986 });
14987 let present_in_batch = parent_is_self
14991 && rows[..batch_idx]
14992 .iter()
14993 .any(|earlier| earlier.get(parent_col) == Some(v));
14994 if !(present_committed || present_in_batch) {
14995 return Err(EngineError::Unsupported(alloc::format!(
14996 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
14997 fk.parent_table,
14998 parent
14999 .schema()
15000 .columns
15001 .get(parent_col)
15002 .map_or("?", |c| c.name.as_str()),
15003 v,
15004 )));
15005 }
15006 } else {
15007 if fk
15011 .local_columns
15012 .iter()
15013 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15014 {
15015 continue;
15016 }
15017 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15018 let parent_match_committed = parent.rows().iter().any(|prow| {
15019 fk.parent_columns
15020 .iter()
15021 .enumerate()
15022 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15023 });
15024 let parent_match_in_batch = parent_is_self
15025 && rows[..batch_idx].iter().any(|earlier| {
15026 fk.parent_columns
15027 .iter()
15028 .enumerate()
15029 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15030 });
15031 if !(parent_match_committed || parent_match_in_batch) {
15032 return Err(EngineError::Unsupported(alloc::format!(
15033 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15034 fk.parent_table,
15035 )));
15036 }
15037 }
15038 }
15039 }
15040 Ok(())
15041}
15042
15043#[derive(Debug, Clone)]
15047struct FkChildStep {
15048 child_table: String,
15049 action: FkChildAction,
15050}
15051
15052#[derive(Debug, Clone)]
15053enum FkChildAction {
15054 Delete { positions: Vec<usize> },
15056 SetNull {
15060 positions: Vec<usize>,
15061 columns: Vec<usize>,
15062 },
15063 SetDefault {
15067 positions: Vec<usize>,
15068 columns: Vec<usize>,
15069 defaults: Vec<Value>,
15070 },
15071}
15072
15073fn plan_fk_parent_deletions(
15089 catalog: &Catalog,
15090 parent_table_name: &str,
15091 to_delete_positions: &[usize],
15092 to_delete_rows: &[Vec<Value>],
15093) -> Result<Vec<FkChildStep>, EngineError> {
15094 use alloc::collections::{BTreeMap, BTreeSet};
15095 if to_delete_rows.is_empty() {
15096 return Ok(Vec::new());
15097 }
15098 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15099 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15101 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15102 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15103 for &p in to_delete_positions {
15104 visited.insert((parent_table_name.to_string(), p));
15105 }
15106 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15107 .iter()
15108 .map(|r| (parent_table_name.to_string(), r.clone()))
15109 .collect();
15110 while let Some((cur_parent, parent_row)) = work.pop() {
15111 for child_name in catalog.table_names() {
15112 let child = catalog
15113 .get(&child_name)
15114 .expect("table_names → catalog.get round-trip is total");
15115 for fk in &child.schema().foreign_keys {
15116 if fk.parent_table != cur_parent {
15117 continue;
15118 }
15119 let parent_key: Vec<&Value> = fk
15120 .parent_columns
15121 .iter()
15122 .map(|&pi| &parent_row[pi])
15123 .collect();
15124 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15125 continue;
15126 }
15127 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15128 if child_name == cur_parent
15129 && visited.contains(&(child_name.clone(), child_row_idx))
15130 {
15131 continue;
15132 }
15133 let matches_key = fk
15134 .local_columns
15135 .iter()
15136 .enumerate()
15137 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15138 if !matches_key {
15139 continue;
15140 }
15141 match fk.on_delete {
15142 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15143 return Err(EngineError::Unsupported(alloc::format!(
15144 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15145 restricted by FK from {child_name:?}.{:?}",
15146 fk.local_columns,
15147 )));
15148 }
15149 spg_storage::FkAction::Cascade => {
15150 if visited.insert((child_name.clone(), child_row_idx)) {
15151 delete_plan
15152 .entry(child_name.clone())
15153 .or_default()
15154 .insert(child_row_idx);
15155 work.push((child_name.clone(), child_row.values.clone()));
15156 }
15157 }
15158 spg_storage::FkAction::SetNull => {
15159 for &li in &fk.local_columns {
15161 let col = child.schema().columns.get(li).ok_or_else(|| {
15162 EngineError::Unsupported(alloc::format!(
15163 "FK local column {li} missing in {child_name:?}"
15164 ))
15165 })?;
15166 if !col.nullable {
15167 return Err(EngineError::Unsupported(alloc::format!(
15168 "FOREIGN KEY ON DELETE SET NULL: column \
15169 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15170 col.name,
15171 )));
15172 }
15173 }
15174 let entry = setnull_plan.entry(child_name.clone()).or_default();
15175 for &li in &fk.local_columns {
15176 entry.insert((child_row_idx, li));
15177 }
15178 }
15179 spg_storage::FkAction::SetDefault => {
15180 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15182 for &li in &fk.local_columns {
15183 let col = child.schema().columns.get(li).ok_or_else(|| {
15184 EngineError::Unsupported(alloc::format!(
15185 "FK local column {li} missing in {child_name:?}"
15186 ))
15187 })?;
15188 let default = col.default.clone().ok_or_else(|| {
15189 EngineError::Unsupported(alloc::format!(
15190 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15191 {child_name:?}.{:?} has no DEFAULT declared",
15192 col.name,
15193 ))
15194 })?;
15195 entry.insert((child_row_idx, li), default);
15196 }
15197 }
15198 }
15199 }
15200 }
15201 }
15202 }
15203 let mut steps: Vec<FkChildStep> = Vec::new();
15211 for (child_table, entries) in setnull_plan {
15212 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15213 steps.push(FkChildStep {
15214 child_table,
15215 action: FkChildAction::SetNull { positions, columns },
15216 });
15217 }
15218 for (child_table, entries) in setdefault_plan {
15219 let mut positions = Vec::with_capacity(entries.len());
15220 let mut columns = Vec::with_capacity(entries.len());
15221 let mut defaults = Vec::with_capacity(entries.len());
15222 for ((p, c), v) in entries {
15223 positions.push(p);
15224 columns.push(c);
15225 defaults.push(v);
15226 }
15227 steps.push(FkChildStep {
15228 child_table,
15229 action: FkChildAction::SetDefault {
15230 positions,
15231 columns,
15232 defaults,
15233 },
15234 });
15235 }
15236 for (child_table, positions) in delete_plan {
15237 steps.push(FkChildStep {
15238 child_table,
15239 action: FkChildAction::Delete {
15240 positions: positions.into_iter().collect(),
15241 },
15242 });
15243 }
15244 Ok(steps)
15245}
15246
15247fn plan_fk_parent_updates(
15264 catalog: &Catalog,
15265 parent_table_name: &str,
15266 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15267) -> Result<Vec<FkChildStep>, EngineError> {
15268 use alloc::collections::BTreeMap;
15269 if plan_with_old.is_empty() {
15270 return Ok(Vec::new());
15271 }
15272 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15277 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15278 BTreeMap::new();
15279 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15280 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15282
15283 for child_name in catalog.table_names() {
15284 let child = catalog
15285 .get(&child_name)
15286 .expect("table_names → catalog.get total");
15287 for fk in &child.schema().foreign_keys {
15288 if fk.parent_table != parent_table_name {
15289 continue;
15290 }
15291 for (_pos, old_row, new_row) in plan_with_old {
15292 let key_changed = fk
15294 .parent_columns
15295 .iter()
15296 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15297 if !key_changed {
15298 continue;
15299 }
15300 let old_key: Vec<&Value> =
15302 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15303 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15304 continue;
15306 }
15307 let new_key: Vec<&Value> =
15308 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15309 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15310 if child_name == parent_table_name
15313 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15314 {
15315 continue;
15316 }
15317 let matches_key = fk
15318 .local_columns
15319 .iter()
15320 .enumerate()
15321 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15322 if !matches_key {
15323 continue;
15324 }
15325 match fk.on_update {
15326 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15327 return Err(EngineError::Unsupported(alloc::format!(
15328 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15329 restricted by FK from {child_name:?}.{:?}",
15330 fk.local_columns,
15331 )));
15332 }
15333 spg_storage::FkAction::Cascade => {
15334 let entry = cascade_plan.entry(child_name.clone()).or_default();
15336 for (i, &li) in fk.local_columns.iter().enumerate() {
15337 entry.insert((child_row_idx, li), new_key[i].clone());
15338 }
15339 }
15340 spg_storage::FkAction::SetNull => {
15341 for &li in &fk.local_columns {
15342 let col = child.schema().columns.get(li).ok_or_else(|| {
15343 EngineError::Unsupported(alloc::format!(
15344 "FK local column {li} missing in {child_name:?}"
15345 ))
15346 })?;
15347 if !col.nullable {
15348 return Err(EngineError::Unsupported(alloc::format!(
15349 "FOREIGN KEY ON UPDATE SET NULL: column \
15350 {child_name:?}.{:?} is NOT NULL",
15351 col.name,
15352 )));
15353 }
15354 }
15355 let entry = setnull_plan.entry(child_name.clone()).or_default();
15356 for &li in &fk.local_columns {
15357 entry.insert((child_row_idx, li));
15358 }
15359 }
15360 spg_storage::FkAction::SetDefault => {
15361 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15362 for &li in &fk.local_columns {
15363 let col = child.schema().columns.get(li).ok_or_else(|| {
15364 EngineError::Unsupported(alloc::format!(
15365 "FK local column {li} missing in {child_name:?}"
15366 ))
15367 })?;
15368 let default = col.default.clone().ok_or_else(|| {
15369 EngineError::Unsupported(alloc::format!(
15370 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15371 {child_name:?}.{:?} has no DEFAULT",
15372 col.name,
15373 ))
15374 })?;
15375 entry.insert((child_row_idx, li), default);
15376 }
15377 }
15378 }
15379 }
15380 }
15381 }
15382 }
15383 let mut steps: Vec<FkChildStep> = Vec::new();
15386 for (child_table, entries) in cascade_plan {
15387 let mut positions = Vec::with_capacity(entries.len());
15388 let mut columns = Vec::with_capacity(entries.len());
15389 let mut defaults = Vec::with_capacity(entries.len());
15390 for ((p, c), v) in entries {
15391 positions.push(p);
15392 columns.push(c);
15393 defaults.push(v);
15394 }
15395 steps.push(FkChildStep {
15400 child_table,
15401 action: FkChildAction::SetDefault {
15402 positions,
15403 columns,
15404 defaults,
15405 },
15406 });
15407 }
15408 for (child_table, entries) in setnull_plan {
15409 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15410 steps.push(FkChildStep {
15411 child_table,
15412 action: FkChildAction::SetNull { positions, columns },
15413 });
15414 }
15415 for (child_table, entries) in setdefault_plan {
15416 let mut positions = Vec::with_capacity(entries.len());
15417 let mut columns = Vec::with_capacity(entries.len());
15418 let mut defaults = Vec::with_capacity(entries.len());
15419 for ((p, c), v) in entries {
15420 positions.push(p);
15421 columns.push(c);
15422 defaults.push(v);
15423 }
15424 steps.push(FkChildStep {
15425 child_table,
15426 action: FkChildAction::SetDefault {
15427 positions,
15428 columns,
15429 defaults,
15430 },
15431 });
15432 }
15433 let _ = delete_plan; Ok(steps)
15435}
15436
15437fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15441 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15442 EngineError::Storage(StorageError::TableNotFound {
15443 name: step.child_table.clone(),
15444 })
15445 })?;
15446 match &step.action {
15447 FkChildAction::Delete { positions } => {
15448 let _ = child.delete_rows(positions);
15449 }
15450 FkChildAction::SetNull { positions, columns } => {
15451 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15452 }
15453 FkChildAction::SetDefault {
15454 positions,
15455 columns,
15456 defaults,
15457 } => {
15458 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15459 }
15460 }
15461 Ok(())
15462}
15463
15464fn apply_per_cell_writes(
15470 child: &mut spg_storage::Table,
15471 positions: &[usize],
15472 columns: &[usize],
15473 mut value_for: impl FnMut(usize) -> Value,
15474) -> Result<(), EngineError> {
15475 use alloc::collections::BTreeMap;
15476 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15477 for i in 0..positions.len() {
15478 by_row
15479 .entry(positions[i])
15480 .or_default()
15481 .push((columns[i], value_for(i)));
15482 }
15483 for (pos, mutations) in by_row {
15484 let mut new_values = child.rows()[pos].values.clone();
15485 for (col, v) in mutations {
15486 if let Some(slot) = new_values.get_mut(col) {
15487 *slot = v;
15488 }
15489 }
15490 child
15491 .update_row(pos, new_values)
15492 .map_err(EngineError::Storage)?;
15493 }
15494 Ok(())
15495}
15496
15497fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15498 match a {
15499 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15500 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15501 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15502 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15503 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15504 }
15505}
15506
15507fn resolve_column_default_free(
15513 col: &ColumnSchema,
15514 clock_fn: Option<ClockFn>,
15515) -> Result<Value, EngineError> {
15516 if let Some(rt) = &col.runtime_default {
15517 return eval_runtime_default_free(rt, col.ty, clock_fn);
15518 }
15519 Ok(col.default.clone().unwrap_or(Value::Null))
15520}
15521
15522fn eval_runtime_default_free(
15523 rt: &str,
15524 ty: DataType,
15525 clock_fn: Option<ClockFn>,
15526) -> Result<Value, EngineError> {
15527 let s = rt.trim().to_ascii_lowercase();
15528 let with_no_parens = s.trim_end_matches("()");
15534 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15535 if with_no_parens.ends_with(')') {
15536 &with_no_parens[..open_idx]
15537 } else {
15538 with_no_parens
15539 }
15540 } else {
15541 with_no_parens
15542 };
15543 let now_us = match clock_fn {
15544 Some(f) => f(),
15545 None => 0,
15546 };
15547 let v = match canonical {
15548 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15549 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15550 "current_time" | "localtime" => Value::Timestamp(now_us),
15551 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15557 other => {
15558 return Err(EngineError::Unsupported(alloc::format!(
15559 "runtime DEFAULT expression {other:?} not supported \
15560 (v7.17.0 whitelist: now() / current_timestamp / \
15561 current_date / current_time / localtimestamp / \
15562 localtime / gen_random_uuid() / \
15563 uuid_generate_v4())"
15564 )));
15565 }
15566 };
15567 coerce_value(v, ty, "DEFAULT", 0)
15568}
15569
15570fn is_runtime_default_expr(expr: &Expr) -> bool {
15576 match expr {
15577 Expr::FunctionCall { .. } => true,
15578 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15579 _ => false,
15580 }
15581}
15582
15583fn canonicalize_set_value(
15596 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15597 col_idx: usize,
15598 col_name: &str,
15599 value: Value,
15600) -> Result<Value, EngineError> {
15601 let Some(variants) = lookup.get(&col_idx) else {
15602 return Ok(value);
15603 };
15604 match value {
15605 Value::Null => Ok(Value::Null),
15606 Value::Text(s) => {
15607 if s.is_empty() {
15608 return Ok(Value::Text(alloc::string::String::new()));
15609 }
15610 let mut present = alloc::vec![false; variants.len()];
15613 for raw in s.split(',') {
15614 let tok = raw.trim();
15615 if tok.is_empty() {
15616 continue;
15617 }
15618 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15619 EngineError::Unsupported(alloc::format!(
15620 "column {col_name:?}: invalid SET token {tok:?}; \
15621 allowed: {variants:?}"
15622 ))
15623 })?;
15624 present[idx] = true;
15625 }
15626 let mut out = alloc::string::String::new();
15628 let mut first = true;
15629 for (i, keep) in present.iter().enumerate() {
15630 if !keep {
15631 continue;
15632 }
15633 if !first {
15634 out.push(',');
15635 }
15636 first = false;
15637 out.push_str(&variants[i]);
15638 }
15639 Ok(Value::Text(out))
15640 }
15641 other => Err(EngineError::Unsupported(alloc::format!(
15642 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
15643 other.data_type()
15644 ))),
15645 }
15646}
15647
15648fn enforce_enum_label(
15649 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15650 col_idx: usize,
15651 col_name: &str,
15652 value: &Value,
15653) -> Result<(), EngineError> {
15654 if let Some(labels) = lookup.get(&col_idx) {
15655 match value {
15656 Value::Null => Ok(()),
15657 Value::Text(s) => {
15658 if labels.iter().any(|l| l == s) {
15659 Ok(())
15660 } else {
15661 Err(EngineError::Unsupported(alloc::format!(
15662 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
15663 )))
15664 }
15665 }
15666 other => Err(EngineError::Unsupported(alloc::format!(
15667 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
15668 other.data_type()
15669 ))),
15670 }
15671 } else {
15672 Ok(())
15673 }
15674}
15675
15676fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
15677 let ty = column_type_to_data_type(c.ty);
15678 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
15679 if let Some(name) = c.user_type_ref {
15686 schema.user_enum_type = Some(name);
15687 }
15688 if let Some(expr) = c.on_update_runtime {
15691 schema.on_update_runtime = Some(alloc::format!("{expr}"));
15692 }
15693 schema.collation = match c.collation {
15697 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
15698 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
15699 };
15700 schema.is_unsigned = c.is_unsigned;
15703 schema.inline_enum_variants = c.inline_enum_variants;
15707 schema.inline_set_variants = c.inline_set_variants;
15711 if let Some(default_expr) = c.default {
15712 if is_runtime_default_expr(&default_expr) {
15718 let display = alloc::format!("{default_expr}");
15719 schema = schema.with_runtime_default(display);
15720 } else {
15721 let raw = literal_expr_to_value(default_expr)?;
15722 let coerced = coerce_value(raw, ty, &c.name, 0)?;
15723 schema = schema.with_default(coerced);
15724 }
15725 }
15726 if c.auto_increment {
15727 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
15729 return Err(EngineError::Unsupported(alloc::format!(
15730 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
15731 )));
15732 }
15733 schema = schema.with_auto_increment();
15734 }
15735 Ok(schema)
15736}
15737
15738fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
15743 let s = s.trim();
15744 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
15745 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
15747 if cleaned.len() % 2 != 0 {
15748 return Err("odd-length hex literal");
15749 }
15750 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
15751 let cleaned_bytes = cleaned.as_bytes();
15752 for i in (0..cleaned_bytes.len()).step_by(2) {
15753 let hi = hex_nibble(cleaned_bytes[i])?;
15754 let lo = hex_nibble(cleaned_bytes[i + 1])?;
15755 out.push((hi << 4) | lo);
15756 }
15757 return Ok(out);
15758 }
15759 let bytes = s.as_bytes();
15762 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
15763 let mut i = 0;
15764 while i < bytes.len() {
15765 let b = bytes[i];
15766 if b == b'\\' && i + 1 < bytes.len() {
15767 let n = bytes[i + 1];
15768 if n == b'\\' {
15769 out.push(b'\\');
15770 i += 2;
15771 continue;
15772 }
15773 if n.is_ascii_digit()
15774 && i + 3 < bytes.len()
15775 && bytes[i + 2].is_ascii_digit()
15776 && bytes[i + 3].is_ascii_digit()
15777 {
15778 let oct = |x: u8| (x - b'0') as u32;
15779 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
15780 if v <= 0xFF {
15781 out.push(v as u8);
15782 i += 4;
15783 continue;
15784 }
15785 }
15786 }
15787 out.push(b);
15788 i += 1;
15789 }
15790 Ok(out)
15791}
15792
15793fn hex_nibble(b: u8) -> Result<u8, &'static str> {
15794 match b {
15795 b'0'..=b'9' => Ok(b - b'0'),
15796 b'a'..=b'f' => Ok(b - b'a' + 10),
15797 b'A'..=b'F' => Ok(b - b'A' + 10),
15798 _ => Err("invalid hex digit"),
15799 }
15800}
15801
15802fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
15816 let mut has_text = false;
15817 let mut has_bigint = false;
15818 let mut has_int = false;
15819 for v in &items {
15820 match v {
15821 Value::Null => {}
15822 Value::Text(_) | Value::Json(_) => has_text = true,
15823 Value::BigInt(_) => has_bigint = true,
15824 Value::Int(_) | Value::SmallInt(_) => has_int = true,
15825 _ => has_text = true,
15826 }
15827 }
15828 if has_text || (!has_bigint && !has_int) {
15829 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
15830 .into_iter()
15831 .map(|v| match v {
15832 Value::Null => None,
15833 Value::Text(s) | Value::Json(s) => Some(s),
15834 other => Some(alloc::format!("{other:?}")),
15835 })
15836 .collect();
15837 return Value::TextArray(out);
15838 }
15839 if has_bigint {
15840 let out: alloc::vec::Vec<Option<i64>> = items
15841 .into_iter()
15842 .map(|v| match v {
15843 Value::Null => None,
15844 Value::Int(n) => Some(i64::from(n)),
15845 Value::SmallInt(n) => Some(i64::from(n)),
15846 Value::BigInt(n) => Some(n),
15847 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
15848 })
15849 .collect();
15850 return Value::BigIntArray(out);
15851 }
15852 let out: alloc::vec::Vec<Option<i32>> = items
15853 .into_iter()
15854 .map(|v| match v {
15855 Value::Null => None,
15856 Value::Int(n) => Some(n),
15857 Value::SmallInt(n) => Some(i32::from(n)),
15858 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
15859 })
15860 .collect();
15861 Value::IntArray(out)
15862}
15863
15864fn decode_text_array_literal(
15865 s: &str,
15866) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
15867 let trimmed = s.trim();
15868 let inner = trimmed
15869 .strip_prefix('{')
15870 .and_then(|x| x.strip_suffix('}'))
15871 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
15872 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
15873 if inner.trim().is_empty() {
15874 return Ok(out);
15875 }
15876 let bytes = inner.as_bytes();
15877 let mut i = 0;
15878 while i <= bytes.len() {
15879 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
15881 i += 1;
15882 }
15883 if i < bytes.len() && bytes[i] == b'"' {
15885 i += 1; let mut buf = alloc::string::String::new();
15887 while i < bytes.len() && bytes[i] != b'"' {
15888 if bytes[i] == b'\\' && i + 1 < bytes.len() {
15889 buf.push(bytes[i + 1] as char);
15890 i += 2;
15891 } else {
15892 buf.push(bytes[i] as char);
15893 i += 1;
15894 }
15895 }
15896 if i >= bytes.len() {
15897 return Err("unterminated quoted element");
15898 }
15899 i += 1; out.push(Some(buf));
15901 } else {
15902 let start = i;
15904 while i < bytes.len() && bytes[i] != b',' {
15905 i += 1;
15906 }
15907 let raw = inner[start..i].trim();
15908 if raw.eq_ignore_ascii_case("NULL") {
15909 out.push(None);
15910 } else {
15911 out.push(Some(alloc::string::ToString::to_string(raw)));
15912 }
15913 }
15914 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
15916 i += 1;
15917 }
15918 if i >= bytes.len() {
15919 break;
15920 }
15921 if bytes[i] != b',' {
15922 return Err("expected ',' between TEXT[] elements");
15923 }
15924 i += 1;
15925 }
15926 Ok(out)
15927}
15928
15929fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
15934 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
15935 out.push('{');
15936 for (i, item) in items.iter().enumerate() {
15937 if i > 0 {
15938 out.push(',');
15939 }
15940 match item {
15941 None => out.push_str("NULL"),
15942 Some(s) => {
15943 let needs_quote = s.is_empty()
15944 || s.eq_ignore_ascii_case("NULL")
15945 || s.chars()
15946 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
15947 if needs_quote {
15948 out.push('"');
15949 for c in s.chars() {
15950 if c == '"' || c == '\\' {
15951 out.push('\\');
15952 }
15953 out.push(c);
15954 }
15955 out.push('"');
15956 } else {
15957 out.push_str(s);
15958 }
15959 }
15960 }
15961 }
15962 out.push('}');
15963 out
15964}
15965
15966fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
15970 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
15971 out.push_str("\\x");
15972 for byte in b {
15973 let hi = byte >> 4;
15974 let lo = byte & 0x0F;
15975 out.push(hex_digit(hi));
15976 out.push(hex_digit(lo));
15977 }
15978 out
15979}
15980
15981const fn hex_digit(n: u8) -> char {
15982 match n {
15983 0..=9 => (b'0' + n) as char,
15984 10..=15 => (b'a' + n - 10) as char,
15985 _ => '?',
15986 }
15987}
15988
15989fn parse_hstore_str(
16001 s: &str,
16002) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16003 let bytes = s.as_bytes();
16004 let mut i = 0;
16005 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16006 let skip_ws = |bytes: &[u8], i: &mut usize| {
16007 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16008 *i += 1;
16009 }
16010 };
16011 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16012 if *i >= bytes.len() {
16013 return None;
16014 }
16015 if bytes[*i] == b'"' {
16016 *i += 1;
16017 let mut out = alloc::string::String::new();
16018 while *i < bytes.len() {
16019 match bytes[*i] {
16020 b'"' => {
16021 *i += 1;
16022 return Some(out);
16023 }
16024 b'\\' if *i + 1 < bytes.len() => {
16025 out.push(bytes[*i + 1] as char);
16026 *i += 2;
16027 }
16028 c => {
16029 out.push(c as char);
16030 *i += 1;
16031 }
16032 }
16033 }
16034 None
16035 } else {
16036 let start = *i;
16037 while *i < bytes.len()
16038 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16039 {
16040 *i += 1;
16041 }
16042 if *i == start {
16043 return None;
16044 }
16045 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16046 }
16047 };
16048 skip_ws(bytes, &mut i);
16049 while i < bytes.len() {
16050 let key = parse_token(bytes, &mut i)?;
16051 skip_ws(bytes, &mut i);
16052 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16053 return None;
16054 }
16055 i += 2;
16056 skip_ws(bytes, &mut i);
16057 let val_token = if i + 4 <= bytes.len()
16059 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16060 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16061 {
16062 i += 4;
16063 None
16064 } else {
16065 Some(parse_token(bytes, &mut i)?)
16066 };
16067 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16069 out[pos] = (key, val_token);
16070 } else {
16071 out.push((key, val_token));
16072 }
16073 skip_ws(bytes, &mut i);
16074 if i >= bytes.len() {
16075 break;
16076 }
16077 if bytes[i] == b',' {
16078 i += 1;
16079 skip_ws(bytes, &mut i);
16080 continue;
16081 }
16082 return None;
16083 }
16084 Some(out)
16085}
16086
16087fn format_hstore_str(
16091 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16092) -> alloc::string::String {
16093 let mut out = alloc::string::String::new();
16094 for (i, (k, v)) in pairs.iter().enumerate() {
16095 if i > 0 {
16096 out.push_str(", ");
16097 }
16098 out.push('"');
16099 out.push_str(k);
16100 out.push_str("\"=>");
16101 match v {
16102 None => out.push_str("NULL"),
16103 Some(val) => {
16104 out.push('"');
16105 out.push_str(val);
16106 out.push('"');
16107 }
16108 }
16109 }
16110 out
16111}
16112
16113pub fn format_hstore_text(
16116 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16117) -> alloc::string::String {
16118 format_hstore_str(pairs)
16119}
16120
16121fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16126 let s = s.trim();
16127 let outer = s
16128 .strip_prefix('{')
16129 .and_then(|x| x.strip_suffix('}'))
16130 .ok_or("missing outer '{...}' braces")?;
16131 let trimmed = outer.trim();
16132 if trimmed.is_empty() {
16133 return Ok(Vec::new());
16134 }
16135 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16136 let mut i = 0;
16137 let bytes = trimmed.as_bytes();
16138 while i < bytes.len() {
16139 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16140 i += 1;
16141 }
16142 if i >= bytes.len() {
16143 break;
16144 }
16145 if bytes[i] != b'{' {
16146 return Err("expected '{' opening a row");
16147 }
16148 i += 1;
16149 let row_start = i;
16150 let mut depth = 1;
16151 while i < bytes.len() && depth > 0 {
16152 match bytes[i] {
16153 b'{' => depth += 1,
16154 b'}' => depth -= 1,
16155 _ => {}
16156 }
16157 if depth > 0 {
16158 i += 1;
16159 }
16160 }
16161 if depth != 0 {
16162 return Err("unbalanced '{...}' in row");
16163 }
16164 let row_text = &trimmed[row_start..i];
16165 i += 1;
16166 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16167 Vec::new()
16168 } else {
16169 row_text.split(',').map(|t| t.trim().to_string()).collect()
16170 };
16171 rows.push(cells);
16172 }
16173 if let Some(first) = rows.first() {
16174 let cols = first.len();
16175 for r in &rows {
16176 if r.len() != cols {
16177 return Err("ragged 2D array (rows have different column counts)");
16178 }
16179 }
16180 }
16181 Ok(rows)
16182}
16183
16184fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16185 let raw = split_2d_literal(s)?;
16186 raw.into_iter()
16187 .map(|row| {
16188 row.into_iter()
16189 .map(|cell| {
16190 if cell.eq_ignore_ascii_case("NULL") {
16191 Ok(None)
16192 } else {
16193 cell.parse::<i32>()
16194 .map(Some)
16195 .map_err(|_| "invalid int element")
16196 }
16197 })
16198 .collect()
16199 })
16200 .collect()
16201}
16202
16203fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16204 let raw = split_2d_literal(s)?;
16205 raw.into_iter()
16206 .map(|row| {
16207 row.into_iter()
16208 .map(|cell| {
16209 if cell.eq_ignore_ascii_case("NULL") {
16210 Ok(None)
16211 } else {
16212 cell.parse::<i64>()
16213 .map(Some)
16214 .map_err(|_| "invalid bigint element")
16215 }
16216 })
16217 .collect()
16218 })
16219 .collect()
16220}
16221
16222fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16223 let raw = split_2d_literal(s)?;
16224 Ok(raw
16225 .into_iter()
16226 .map(|row| {
16227 row.into_iter()
16228 .map(|cell| {
16229 if cell.eq_ignore_ascii_case("NULL") {
16230 None
16231 } else {
16232 Some(cell.trim_matches('"').to_string())
16233 }
16234 })
16235 .collect()
16236 })
16237 .collect())
16238}
16239
16240fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16241 let mut out = alloc::string::String::from("{");
16242 for (i, row) in rows.iter().enumerate() {
16243 if i > 0 {
16244 out.push(',');
16245 }
16246 out.push('{');
16247 for (j, cell) in row.iter().enumerate() {
16248 if j > 0 {
16249 out.push(',');
16250 }
16251 match cell {
16252 None => out.push_str("NULL"),
16253 Some(n) => out.push_str(&alloc::format!("{n}")),
16254 }
16255 }
16256 out.push('}');
16257 }
16258 out.push('}');
16259 out
16260}
16261
16262fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16263 let mut out = alloc::string::String::from("{");
16264 for (i, row) in rows.iter().enumerate() {
16265 if i > 0 {
16266 out.push(',');
16267 }
16268 out.push('{');
16269 for (j, cell) in row.iter().enumerate() {
16270 if j > 0 {
16271 out.push(',');
16272 }
16273 match cell {
16274 None => out.push_str("NULL"),
16275 Some(n) => out.push_str(&alloc::format!("{n}")),
16276 }
16277 }
16278 out.push('}');
16279 }
16280 out.push('}');
16281 out
16282}
16283
16284fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16285 let mut out = alloc::string::String::from("{");
16286 for (i, row) in rows.iter().enumerate() {
16287 if i > 0 {
16288 out.push(',');
16289 }
16290 out.push('{');
16291 for (j, cell) in row.iter().enumerate() {
16292 if j > 0 {
16293 out.push(',');
16294 }
16295 match cell {
16296 None => out.push_str("NULL"),
16297 Some(s) => out.push_str(s),
16298 }
16299 }
16300 out.push('}');
16301 }
16302 out.push('}');
16303 out
16304}
16305
16306pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16309 format_int_2d_text(rows)
16310}
16311pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16312 format_bigint_2d_text(rows)
16313}
16314pub fn format_text_2d_text_pub(
16315 rows: &[Vec<Option<alloc::string::String>>],
16316) -> alloc::string::String {
16317 format_text_2d_text(rows)
16318}
16319
16320fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16325 let s = s.trim();
16326 if s.eq_ignore_ascii_case("empty") {
16327 return Some(Value::Range {
16328 kind,
16329 lower: None,
16330 upper: None,
16331 lower_inc: false,
16332 upper_inc: false,
16333 empty: true,
16334 });
16335 }
16336 let bytes = s.as_bytes();
16337 if bytes.len() < 3 {
16338 return None;
16339 }
16340 let lower_inc = match bytes[0] {
16341 b'[' => true,
16342 b'(' => false,
16343 _ => return None,
16344 };
16345 let upper_inc = match bytes[bytes.len() - 1] {
16346 b']' => true,
16347 b')' => false,
16348 _ => return None,
16349 };
16350 let inner = &s[1..s.len() - 1];
16351 let (lo_text, up_text) = inner.split_once(',')?;
16352 let lower = if lo_text.is_empty() {
16353 None
16354 } else {
16355 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16356 };
16357 let upper = if up_text.is_empty() {
16358 None
16359 } else {
16360 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16361 };
16362 Some(Value::Range {
16363 kind,
16364 lower,
16365 upper,
16366 lower_inc,
16367 upper_inc,
16368 empty: false,
16369 })
16370}
16371
16372fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16375 let text = text.trim().trim_matches('"');
16376 use spg_storage::RangeKind as K;
16377 match kind {
16378 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16379 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16380 K::Num => {
16381 let dot = text.find('.');
16384 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16385 let digits: alloc::string::String = text
16386 .chars()
16387 .filter(|c| *c == '-' || c.is_ascii_digit())
16388 .collect();
16389 let scaled: i128 = digits.parse().ok()?;
16390 Some(Value::Numeric { scaled, scale })
16391 }
16392 K::Ts | K::TsTz => {
16393 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16398 }
16399 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16400 }
16401}
16402
16403pub fn format_range_text(v: &Value) -> alloc::string::String {
16407 format_range_str(v)
16408}
16409
16410fn format_range_str(v: &Value) -> alloc::string::String {
16411 let Value::Range {
16412 lower,
16413 upper,
16414 lower_inc,
16415 upper_inc,
16416 empty,
16417 ..
16418 } = v
16419 else {
16420 return alloc::string::String::new();
16421 };
16422 if *empty {
16423 return "empty".into();
16424 }
16425 let mut out = alloc::string::String::new();
16426 out.push(if *lower_inc { '[' } else { '(' });
16427 if let Some(l) = lower {
16428 out.push_str(&format_range_element(l));
16429 }
16430 out.push(',');
16431 if let Some(u) = upper {
16432 out.push_str(&format_range_element(u));
16433 }
16434 out.push(if *upper_inc { ']' } else { ')' });
16435 out
16436}
16437
16438fn format_range_element(v: &Value) -> alloc::string::String {
16439 match v {
16440 Value::Int(n) => alloc::format!("{n}"),
16441 Value::BigInt(n) => alloc::format!("{n}"),
16442 Value::Date(d) => crate::eval::format_date(*d),
16443 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16444 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16445 other => alloc::format!("{other:?}"),
16446 }
16447}
16448
16449fn parse_money_str(s: &str) -> Option<i64> {
16460 let s = s.trim();
16461 let (neg, rest) = match s.strip_prefix('-') {
16462 Some(r) => (true, r.trim_start()),
16463 None => (false, s),
16464 };
16465 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16466 let (int_part, frac_part) = match rest.split_once('.') {
16467 Some((i, f)) => (i, Some(f)),
16468 None => (rest, None),
16469 };
16470 if int_part.is_empty() {
16471 return None;
16472 }
16473 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16475 for b in int_part.bytes() {
16476 match b {
16477 b',' => {}
16478 b'0'..=b'9' => int_digits.push(b as char),
16479 _ => return None,
16480 }
16481 }
16482 if int_digits.is_empty() {
16483 return None;
16484 }
16485 let dollars: i64 = int_digits.parse().ok()?;
16486 let cents: i64 = match frac_part {
16487 None => 0,
16488 Some(f) => {
16489 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16490 return None;
16491 }
16492 let padded = if f.len() == 1 {
16493 alloc::format!("{f}0")
16494 } else {
16495 f.to_string()
16496 };
16497 padded.parse().ok()?
16498 }
16499 };
16500 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16501 Some(if neg { -total } else { total })
16502}
16503
16504fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16515 let s = s.trim();
16516 let bytes = s.as_bytes();
16520 let sign_pos = bytes
16521 .iter()
16522 .enumerate()
16523 .rev()
16524 .find(|&(_, &b)| b == b'+' || b == b'-')
16525 .map(|(i, _)| i)?;
16526 if sign_pos == 0 {
16527 return None; }
16529 let time_part = &s[..sign_pos];
16530 let offset_part = &s[sign_pos..];
16531 let us = parse_time_str(time_part)?;
16532 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16533 let offset_body = &offset_part[1..];
16534 let (hh_str, mm_str) = match offset_body.split_once(':') {
16535 Some((h, m)) => (h, m),
16536 None => (offset_body, "0"),
16537 };
16538 let hh: i32 = hh_str.parse().ok()?;
16539 let mm: i32 = mm_str.parse().ok()?;
16540 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16541 return None;
16542 }
16543 let total = sign * (hh * 3600 + mm * 60);
16544 if total.abs() > 50_400 {
16545 return None;
16546 }
16547 Some((us, total))
16548}
16549
16550fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16555 if n == 0 || (1901..=2155).contains(&n) {
16556 return Ok(Value::Year(n as u16));
16559 }
16560 Err(EngineError::Eval(EvalError::TypeMismatch {
16561 detail: alloc::format!(
16562 "year value out of range: {n} (column `{col_name}`; \
16563 MySQL accepts 0 or 1901..=2155)"
16564 ),
16565 }))
16566}
16567
16568fn parse_time_str(s: &str) -> Option<i64> {
16580 let s = s.trim();
16581 let (hms, frac) = match s.split_once('.') {
16582 Some((h, f)) => (h, Some(f)),
16583 None => (s, None),
16584 };
16585 let mut parts = hms.split(':');
16586 let hh: u32 = parts.next()?.parse().ok()?;
16587 let mm: u32 = parts.next()?.parse().ok()?;
16588 let ss: u32 = parts.next()?.parse().ok()?;
16589 if parts.next().is_some() {
16590 return None;
16591 }
16592 if hh > 23 || mm > 59 || ss > 59 {
16593 return None;
16594 }
16595 let frac_us: i64 = match frac {
16596 None => 0,
16597 Some(f) => {
16598 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16599 return None;
16600 }
16601 let mut padded = alloc::string::String::with_capacity(6);
16603 padded.push_str(f);
16604 while padded.len() < 6 {
16605 padded.push('0');
16606 }
16607 padded.parse().ok()?
16608 }
16609 };
16610 Some(
16611 i64::from(hh) * 3_600_000_000
16612 + i64::from(mm) * 60_000_000
16613 + i64::from(ss) * 1_000_000
16614 + frac_us,
16615 )
16616}
16617
16618const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16619 match t {
16620 ColumnTypeName::SmallInt => DataType::SmallInt,
16621 ColumnTypeName::Int => DataType::Int,
16622 ColumnTypeName::BigInt => DataType::BigInt,
16623 ColumnTypeName::Float => DataType::Float,
16624 ColumnTypeName::Text => DataType::Text,
16625 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
16626 ColumnTypeName::Char(n) => DataType::Char(n),
16627 ColumnTypeName::Bool => DataType::Bool,
16628 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
16629 dim,
16630 encoding: match encoding {
16631 SqlVecEncoding::F32 => VecEncoding::F32,
16632 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
16633 SqlVecEncoding::F16 => VecEncoding::F16,
16634 },
16635 },
16636 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
16637 ColumnTypeName::Date => DataType::Date,
16638 ColumnTypeName::Timestamp => DataType::Timestamp,
16639 ColumnTypeName::Timestamptz => DataType::Timestamptz,
16640 ColumnTypeName::Json => DataType::Json,
16641 ColumnTypeName::Jsonb => DataType::Jsonb,
16642 ColumnTypeName::Bytes => DataType::Bytes,
16643 ColumnTypeName::TextArray => DataType::TextArray,
16644 ColumnTypeName::IntArray => DataType::IntArray,
16645 ColumnTypeName::BigIntArray => DataType::BigIntArray,
16646 ColumnTypeName::TsVector => DataType::TsVector,
16647 ColumnTypeName::TsQuery => DataType::TsQuery,
16648 ColumnTypeName::Uuid => DataType::Uuid,
16649 ColumnTypeName::Time => DataType::Time,
16650 ColumnTypeName::Year => DataType::Year,
16651 ColumnTypeName::TimeTz => DataType::TimeTz,
16652 ColumnTypeName::Money => DataType::Money,
16653 ColumnTypeName::Range(k) => DataType::Range(match k {
16654 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
16655 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
16656 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
16657 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
16658 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
16659 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
16660 }),
16661 ColumnTypeName::Hstore => DataType::Hstore,
16662 ColumnTypeName::IntArray2D => DataType::IntArray2D,
16663 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
16664 ColumnTypeName::TextArray2D => DataType::TextArray2D,
16665 }
16666}
16667
16668fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
16672 match expr {
16673 Expr::Literal(l) => Ok(literal_to_value(l)),
16674 Expr::Cast { expr, target } => {
16675 let inner_value = literal_expr_to_value(*expr)?;
16676 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
16677 }
16678 Expr::Unary {
16679 op: UnOp::Neg,
16680 expr,
16681 } => match *expr {
16682 Expr::Literal(Literal::Integer(n)) => {
16683 let neg = n.checked_neg().ok_or_else(|| {
16686 EngineError::Unsupported("integer literal overflow on negation".into())
16687 })?;
16688 Ok(int_value_for(neg))
16689 }
16690 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
16691 other => Err(EngineError::Unsupported(alloc::format!(
16692 "unary minus over non-literal expression: {other:?}"
16693 ))),
16694 },
16695 Expr::Array(items) => {
16703 let mut materialised: alloc::vec::Vec<Value> =
16704 alloc::vec::Vec::with_capacity(items.len());
16705 for elem in items {
16706 materialised.push(literal_expr_to_value(elem)?);
16707 }
16708 Ok(array_literal_widen(materialised))
16709 }
16710 other => {
16723 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
16724 let ctx = EvalContext::new(&empty_schema, None);
16725 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
16726 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
16727 }
16728 }
16729}
16730
16731fn literal_to_value(l: Literal) -> Value {
16732 match l {
16733 Literal::Integer(n) => int_value_for(n),
16734 Literal::Float(x) => Value::Float(x),
16735 Literal::String(s) => Value::Text(s),
16736 Literal::Bool(b) => Value::Bool(b),
16737 Literal::Null => Value::Null,
16738 Literal::Vector(v) => Value::Vector(v),
16739 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
16740 }
16741}
16742
16743fn int_value_for(n: i64) -> Value {
16747 if let Ok(small) = i32::try_from(n) {
16748 Value::Int(small)
16749 } else {
16750 Value::BigInt(n)
16751 }
16752}
16753
16754#[allow(clippy::too_many_lines)]
16760fn check_unsigned_range(
16765 v: &Value,
16766 schema: &ColumnSchema,
16767 position: usize,
16768) -> Result<(), EngineError> {
16769 if !schema.is_unsigned {
16770 return Ok(());
16771 }
16772 let n = match v {
16773 Value::SmallInt(x) => i64::from(*x),
16774 Value::Int(x) => i64::from(*x),
16775 Value::BigInt(x) => *x,
16776 _ => return Ok(()), };
16778 if n < 0 {
16779 return Err(EngineError::Unsupported(alloc::format!(
16780 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
16781 schema.name
16782 )));
16783 }
16784 Ok(())
16785}
16786
16787fn coerce_value(
16788 v: Value,
16789 expected: DataType,
16790 col_name: &str,
16791 position: usize,
16792) -> Result<Value, EngineError> {
16793 if v.is_null() {
16794 return Ok(Value::Null);
16795 }
16796 let actual = v.data_type().expect("non-null");
16797 if actual == expected {
16798 return Ok(v);
16799 }
16800 let coerced = match (v, expected) {
16801 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16802 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16803 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16804 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16805 i128::from(n),
16806 precision,
16807 scale,
16808 col_name,
16809 )?),
16810 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
16811 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16812 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16813 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16814 i128::from(n),
16815 precision,
16816 scale,
16817 col_name,
16818 )?),
16819 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
16820 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16821 #[allow(clippy::cast_precision_loss)]
16822 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
16823 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16824 i128::from(n),
16825 precision,
16826 scale,
16827 col_name,
16828 )?),
16829 (Value::Float(x), DataType::Numeric { precision, scale }) => {
16830 Some(numeric_from_float(x, precision, scale, col_name)?)
16831 }
16832 (Value::Text(s), DataType::Numeric { precision, scale }) => {
16843 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
16844 return Err(EngineError::Eval(EvalError::TypeMismatch {
16845 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
16846 }));
16847 };
16848 Some(numeric_rescale(
16849 mantissa, src_scale, precision, scale, col_name,
16850 )?)
16851 }
16852 (Value::Text(s), DataType::Date) => {
16854 let d = eval::parse_date_literal(&s).ok_or_else(|| {
16855 EngineError::Eval(EvalError::TypeMismatch {
16856 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
16857 })
16858 })?;
16859 Some(Value::Date(d))
16860 }
16861 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
16868 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
16869 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
16870 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
16871 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
16872 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
16873 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
16874 _ => None,
16875 },
16876 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16885 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16886 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16887 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
16891 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
16892 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
16900 (Value::Text(s), DataType::Bytes) => {
16907 let bytes = decode_bytea_literal(&s).map_err(|e| {
16908 EngineError::Eval(EvalError::TypeMismatch {
16909 detail: alloc::format!(
16910 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
16911 ),
16912 })
16913 })?;
16914 Some(Value::Bytes(bytes))
16915 }
16916 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
16920 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
16928 Some(b) => Some(Value::Uuid(b)),
16929 None => {
16930 return Err(EngineError::Eval(EvalError::TypeMismatch {
16931 detail: alloc::format!(
16932 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
16933 ),
16934 }));
16935 }
16936 },
16937 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
16942 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
16948 Some(us) => Some(Value::Time(us)),
16949 None => {
16950 return Err(EngineError::Eval(EvalError::TypeMismatch {
16951 detail: alloc::format!(
16952 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
16953 ),
16954 }));
16955 }
16956 },
16957 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
16959 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
16964 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
16965 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
16966 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
16970 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
16971 Err(_) => {
16972 return Err(EngineError::Eval(EvalError::TypeMismatch {
16973 detail: alloc::format!(
16974 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
16975 ),
16976 }));
16977 }
16978 },
16979 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
16981 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
16985 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
16986 None => {
16987 return Err(EngineError::Eval(EvalError::TypeMismatch {
16988 detail: alloc::format!(
16989 "invalid input syntax for type time with time zone: \
16990 {s:?} (column `{col_name}`)"
16991 ),
16992 }));
16993 }
16994 },
16995 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
16997 Some(Value::Text(eval::format_timetz(us, offset_secs)))
16998 }
16999 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17003 Some(c) => Some(Value::Money(c)),
17004 None => {
17005 return Err(EngineError::Eval(EvalError::TypeMismatch {
17006 detail: alloc::format!(
17007 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17008 ),
17009 }));
17010 }
17011 },
17012 (Value::SmallInt(n), DataType::Money) => {
17016 Some(Value::Money(i64::from(n).saturating_mul(100)))
17017 }
17018 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17019 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17020 (Value::Float(x), DataType::Money) => {
17021 let scaled = x * 100.0;
17024 let cents = if scaled >= 0.0 {
17025 (scaled + 0.5) as i64
17026 } else {
17027 (scaled - 0.5) as i64
17028 };
17029 Some(Value::Money(cents))
17030 }
17031 (Value::Numeric { scaled, scale }, DataType::Money) => {
17032 let cents = if scale == 2 {
17035 scaled
17036 } else if scale < 2 {
17037 let mult = 10_i128.pow(u32::from(2 - scale));
17038 scaled.saturating_mul(mult)
17039 } else {
17040 let div = 10_i128.pow(u32::from(scale - 2));
17041 let half = div / 2;
17042 let bias = if scaled >= 0 { half } else { -half };
17043 (scaled + bias) / div
17044 };
17045 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17046 }
17047 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17049 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17053 Some(v) => Some(v),
17054 None => {
17055 return Err(EngineError::Eval(EvalError::TypeMismatch {
17056 detail: alloc::format!(
17057 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17058 ),
17059 }));
17060 }
17061 },
17062 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17064 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17066 Some(pairs) => Some(Value::Hstore(pairs)),
17067 None => {
17068 return Err(EngineError::Eval(EvalError::TypeMismatch {
17069 detail: alloc::format!(
17070 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17071 ),
17072 }));
17073 }
17074 },
17075 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17077 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17080 Ok(m) => Some(Value::IntArray2D(m)),
17081 Err(e) => {
17082 return Err(EngineError::Eval(EvalError::TypeMismatch {
17083 detail: alloc::format!(
17084 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17085 ),
17086 }));
17087 }
17088 },
17089 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17090 Ok(m) => Some(Value::BigIntArray2D(m)),
17091 Err(e) => {
17092 return Err(EngineError::Eval(EvalError::TypeMismatch {
17093 detail: alloc::format!(
17094 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17095 ),
17096 }));
17097 }
17098 },
17099 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17100 Ok(m) => Some(Value::TextArray2D(m)),
17101 Err(e) => {
17102 return Err(EngineError::Eval(EvalError::TypeMismatch {
17103 detail: alloc::format!(
17104 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17105 ),
17106 }));
17107 }
17108 },
17109 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17111 (Value::BigIntArray2D(rows), DataType::Text) => {
17112 Some(Value::Text(format_bigint_2d_text(&rows)))
17113 }
17114 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17115 (Value::Text(s), DataType::TextArray) => {
17120 let arr = decode_text_array_literal(&s).map_err(|e| {
17121 EngineError::Eval(EvalError::TypeMismatch {
17122 detail: alloc::format!(
17123 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17124 ),
17125 })
17126 })?;
17127 Some(Value::TextArray(arr))
17128 }
17129 (Value::Text(s), DataType::IntArray) => {
17135 let arr = decode_text_array_literal(&s).map_err(|e| {
17136 EngineError::Eval(EvalError::TypeMismatch {
17137 detail: alloc::format!(
17138 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17139 ),
17140 })
17141 })?;
17142 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17143 for elem in arr {
17144 match elem {
17145 None => out.push(None),
17146 Some(t) => {
17147 let n: i32 = t.parse().map_err(|_| {
17148 EngineError::Eval(EvalError::TypeMismatch {
17149 detail: alloc::format!(
17150 "cannot parse {t:?} as INT element for `{col_name}`"
17151 ),
17152 })
17153 })?;
17154 out.push(Some(n));
17155 }
17156 }
17157 }
17158 Some(Value::IntArray(out))
17159 }
17160 (Value::Text(s), DataType::BigIntArray) => {
17161 let arr = decode_text_array_literal(&s).map_err(|e| {
17162 EngineError::Eval(EvalError::TypeMismatch {
17163 detail: alloc::format!(
17164 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17165 ),
17166 })
17167 })?;
17168 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17169 for elem in arr {
17170 match elem {
17171 None => out.push(None),
17172 Some(t) => {
17173 let n: i64 = t.parse().map_err(|_| {
17174 EngineError::Eval(EvalError::TypeMismatch {
17175 detail: alloc::format!(
17176 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17177 ),
17178 })
17179 })?;
17180 out.push(Some(n));
17181 }
17182 }
17183 }
17184 Some(Value::BigIntArray(out))
17185 }
17186 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17190 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17199 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17200 EngineError::Eval(EvalError::TypeMismatch {
17201 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17202 })
17203 })?;
17204 if parsed.len() != dim as usize {
17205 return Err(EngineError::Eval(EvalError::TypeMismatch {
17206 detail: alloc::format!(
17207 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17208 parsed.len()
17209 ),
17210 }));
17211 }
17212 Some(match encoding {
17213 VecEncoding::F32 => Value::Vector(parsed),
17214 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17215 VecEncoding::F16 => {
17216 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17217 }
17218 })
17219 }
17220 (Value::Text(s), DataType::TsVector) => {
17230 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17231 EngineError::Eval(EvalError::TypeMismatch {
17232 detail: alloc::format!(
17233 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17234 ),
17235 })
17236 })?;
17237 Some(Value::TsVector(lexs))
17238 }
17239 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17240 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17241 EngineError::Eval(EvalError::TypeMismatch {
17242 detail: alloc::format!(
17243 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17244 ),
17245 })
17246 })?;
17247 Some(Value::Timestamp(t))
17248 }
17249 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17252 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17253 }
17254 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17258 (Value::Timestamp(t), DataType::Date) => {
17259 let days = t.div_euclid(86_400_000_000);
17260 i32::try_from(days).ok().map(Value::Date)
17261 }
17262 (
17263 Value::Numeric {
17264 scaled,
17265 scale: src_scale,
17266 },
17267 DataType::Numeric { precision, scale },
17268 ) => Some(numeric_rescale(
17269 scaled, src_scale, precision, scale, col_name,
17270 )?),
17271 #[allow(clippy::cast_precision_loss)]
17272 (Value::Numeric { scaled, scale }, DataType::Float) => {
17273 let mut div = 1.0_f64;
17274 for _ in 0..scale {
17275 div *= 10.0;
17276 }
17277 Some(Value::Float((scaled as f64) / div))
17278 }
17279 (Value::Numeric { scaled, scale }, DataType::Int) => {
17280 let truncated = numeric_truncate_to_integer(scaled, scale);
17281 i32::try_from(truncated).ok().map(Value::Int)
17282 }
17283 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17284 let truncated = numeric_truncate_to_integer(scaled, scale);
17285 i64::try_from(truncated).ok().map(Value::BigInt)
17286 }
17287 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17288 let truncated = numeric_truncate_to_integer(scaled, scale);
17289 i16::try_from(truncated).ok().map(Value::SmallInt)
17290 }
17291 (Value::Text(s), DataType::Varchar(max)) => {
17293 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17294 Some(Value::Text(s))
17295 } else {
17296 return Err(EngineError::Unsupported(alloc::format!(
17297 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17298 {} chars",
17299 s.chars().count()
17300 )));
17301 }
17302 }
17303 (
17311 Value::Vector(v),
17312 DataType::Vector {
17313 dim,
17314 encoding: VecEncoding::Sq8,
17315 },
17316 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17317 (
17322 Value::Vector(v),
17323 DataType::Vector {
17324 dim,
17325 encoding: VecEncoding::F16,
17326 },
17327 ) if v.len() == dim as usize => Some(Value::HalfVector(
17328 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17329 )),
17330 (Value::Text(s), DataType::Char(size)) => {
17334 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17335 if len > size {
17336 return Err(EngineError::Unsupported(alloc::format!(
17337 "value for CHAR({size}) column `{col_name}` exceeds length: \
17338 {len} chars"
17339 )));
17340 }
17341 let need = (size - len) as usize;
17342 let mut padded = s;
17343 padded.reserve(need);
17344 for _ in 0..need {
17345 padded.push(' ');
17346 }
17347 Some(Value::Text(padded))
17348 }
17349 _ => None,
17350 };
17351 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17352 column: col_name.into(),
17353 expected,
17354 actual,
17355 position,
17356 }))
17357}
17358
17359fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17365 use core::fmt::Write;
17366 let mut out = alloc::string::String::from("(");
17367 for (i, a) in args.iter().enumerate() {
17368 if i > 0 {
17369 out.push_str(", ");
17370 }
17371 match a.mode {
17372 spg_sql::ast::FunctionArgMode::In => {}
17373 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17374 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17375 }
17376 if let Some(n) = &a.name {
17377 out.push_str(n);
17378 out.push(' ');
17379 }
17380 match &a.ty {
17381 spg_sql::ast::FunctionArgType::Typed(t) => {
17382 let _ = write!(out, "{t}");
17383 }
17384 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17385 }
17386 }
17387 out.push(')');
17388 out
17389}
17390
17391#[cfg(test)]
17392mod tests {
17393 use super::*;
17394 use alloc::vec;
17395
17396 fn unwrap_command_ok(r: &QueryResult) -> usize {
17397 match r {
17398 QueryResult::CommandOk { affected, .. } => *affected,
17399 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17400 }
17401 }
17402
17403 #[test]
17404 fn create_table_registers_schema() {
17405 let mut e = Engine::new();
17406 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17407 .unwrap();
17408 assert_eq!(e.catalog().table_count(), 1);
17409 let t = e.catalog().get("foo").unwrap();
17410 assert_eq!(t.schema().columns.len(), 2);
17411 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17412 assert!(!t.schema().columns[0].nullable);
17413 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17414 }
17415
17416 #[test]
17417 fn create_table_vector_default_is_f32_encoded() {
17418 let mut e = Engine::new();
17419 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17420 let t = e.catalog().get("t").unwrap();
17421 assert_eq!(
17422 t.schema().columns[0].ty,
17423 DataType::Vector {
17424 dim: 8,
17425 encoding: VecEncoding::F32,
17426 },
17427 );
17428 }
17429
17430 #[test]
17431 fn create_table_vector_using_sq8_succeeds() {
17432 let mut e = Engine::new();
17436 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17437 let t = e.catalog().get("t").unwrap();
17438 assert_eq!(
17439 t.schema().columns[0].ty,
17440 DataType::Vector {
17441 dim: 8,
17442 encoding: VecEncoding::Sq8,
17443 },
17444 );
17445 }
17446
17447 #[test]
17448 fn insert_into_sq8_column_quantises_f32_payload() {
17449 let mut e = Engine::new();
17456 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17457 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17458 .unwrap();
17459 let t = e.catalog().get("t").unwrap();
17460 assert_eq!(t.rows().len(), 1);
17461 match &t.rows()[0].values[0] {
17462 Value::Sq8Vector(q) => {
17463 assert_eq!(q.bytes.len(), 4);
17464 assert!((q.min - 0.0).abs() < 1e-6);
17466 assert!((q.max - 1.0).abs() < 1e-6);
17467 }
17468 other => panic!("expected Sq8Vector cell, got {other:?}"),
17469 }
17470 }
17471
17472 #[test]
17473 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17474 let mut e = Engine::new();
17481 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17482 .unwrap();
17483 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17484 .unwrap();
17485 let t = e.catalog().get("t").unwrap();
17486 assert_eq!(t.rows().len(), 1);
17487 match &t.rows()[0].values[0] {
17488 Value::HalfVector(h) => {
17489 assert_eq!(h.dim(), 4);
17490 let back = h.to_f32_vec();
17491 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17492 for (g, e) in back.iter().zip(expected.iter()) {
17493 assert!(
17494 (g - e).abs() < 1e-6,
17495 "{g} vs {e} should be exact on f16 grid"
17496 );
17497 }
17498 }
17499 other => panic!("expected HalfVector cell, got {other:?}"),
17500 }
17501 }
17502
17503 #[test]
17504 fn alter_index_rebuild_in_place_succeeds() {
17505 let mut e = Engine::new();
17510 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17511 .unwrap();
17512 for i in 0..8_i32 {
17513 #[allow(clippy::cast_precision_loss)]
17514 let base = (i as f32) * 0.1;
17515 e.execute(&alloc::format!(
17516 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
17517 b1 = base + 0.01,
17518 b2 = base + 0.02,
17519 ))
17520 .unwrap();
17521 }
17522 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17523 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
17524 assert_eq!(
17526 e.catalog().get("t").unwrap().schema().columns[1].ty,
17527 DataType::Vector {
17528 dim: 3,
17529 encoding: VecEncoding::F32,
17530 },
17531 );
17532 }
17533
17534 #[test]
17535 fn alter_index_rebuild_with_encoding_switches_cell_type() {
17536 let mut e = Engine::new();
17541 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
17542 .unwrap();
17543 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
17544 .unwrap();
17545 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17546 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
17547 .unwrap();
17548 let t = e.catalog().get("t").unwrap();
17549 assert_eq!(
17550 t.schema().columns[1].ty,
17551 DataType::Vector {
17552 dim: 4,
17553 encoding: VecEncoding::Sq8,
17554 },
17555 );
17556 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
17557 }
17558
17559 #[test]
17560 fn alter_index_rebuild_unknown_index_errors() {
17561 let mut e = Engine::new();
17562 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
17563 assert!(
17564 matches!(
17565 &err,
17566 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
17567 ),
17568 "got: {err}"
17569 );
17570 }
17571
17572 #[test]
17573 fn alter_index_rebuild_on_btree_index_errors() {
17574 let mut e = Engine::new();
17577 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17578 e.execute("INSERT INTO t VALUES (1)").unwrap();
17579 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
17580 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
17581 assert!(
17582 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
17583 "got: {err}"
17584 );
17585 }
17586
17587 #[test]
17588 fn prepared_insert_substitutes_placeholders() {
17589 let mut e = Engine::new();
17595 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17596 .unwrap();
17597 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
17598 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
17599 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
17600 .unwrap();
17601 }
17602 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
17604 let QueryResult::Rows { rows, .. } = rows_result else {
17605 panic!("expected Rows")
17606 };
17607 assert_eq!(rows.len(), 3);
17608 }
17609
17610 #[test]
17611 fn prepared_select_with_placeholder_filters_rows() {
17612 let mut e = Engine::new();
17613 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17614 .unwrap();
17615 for i in 0..10_i32 {
17616 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17617 .unwrap();
17618 }
17619 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17620 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
17621 else {
17622 panic!("expected Rows")
17623 };
17624 assert_eq!(rows.len(), 1);
17626 assert_eq!(rows[0].values[0], Value::Int(5));
17627 }
17628
17629 #[test]
17630 fn prepared_too_few_params_errors() {
17631 let mut e = Engine::new();
17632 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17633 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17634 let err = e.execute_prepared(stmt, &[]).unwrap_err();
17635 assert!(
17636 matches!(
17637 &err,
17638 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
17639 ),
17640 "got: {err}"
17641 );
17642 }
17643
17644 #[test]
17645 fn bytea_cast_round_trips_text_input() {
17646 let e = Engine::new();
17649 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
17650 let QueryResult::Rows { rows, .. } = r else { panic!("expected Rows") };
17651 assert_eq!(rows.len(), 1);
17652 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
17653 }
17654
17655 #[test]
17656 fn bytea_cast_pg_escape_hex_form() {
17657 let e = Engine::new();
17661 let r = e
17662 .execute_readonly(r"SELECT E'\\xdeadbeef'::bytea")
17663 .unwrap();
17664 let QueryResult::Rows { rows, .. } = r else { panic!("expected Rows") };
17665 assert_eq!(
17666 rows[0].values[0],
17667 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
17668 );
17669 }
17670
17671 #[test]
17672 fn bytea_cast_chains_through_octet_length() {
17673 let e = Engine::new();
17677 let r = e
17678 .execute_readonly("SELECT octet_length('hello'::bytea)")
17679 .unwrap();
17680 let QueryResult::Rows { rows, .. } = r else { panic!("expected Rows") };
17681 match &rows[0].values[0] {
17682 Value::Int(n) => assert_eq!(*n, 5),
17683 Value::BigInt(n) => assert_eq!(*n, 5),
17684 other => panic!("expected integer length, got {other:?}"),
17685 }
17686 }
17687
17688 #[test]
17689 fn readonly_prepared_on_snapshot_select_with_placeholder() {
17690 let mut e = Engine::new();
17696 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17697 .unwrap();
17698 for i in 0..10_i32 {
17699 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17700 .unwrap();
17701 }
17702 let snapshot = e.clone_snapshot();
17703 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17704 let QueryResult::Rows { rows, .. } =
17705 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
17706 .unwrap()
17707 else {
17708 panic!("expected Rows")
17709 };
17710 assert_eq!(rows.len(), 1);
17711 assert_eq!(rows[0].values[0], Value::Int(5));
17712 }
17713
17714 #[test]
17715 fn readonly_prepared_on_snapshot_rejects_writes() {
17716 let mut e = Engine::new();
17720 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17721 let snapshot = e.clone_snapshot();
17722 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17723 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
17724 .unwrap_err();
17725 assert!(
17726 matches!(&err, EngineError::WriteRequired),
17727 "got: {err}"
17728 );
17729 }
17730
17731 #[test]
17732 fn readonly_prepared_on_snapshot_frozen_view() {
17733 let mut e = Engine::new();
17739 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17740 e.execute("INSERT INTO t VALUES (1)").unwrap();
17741 let snapshot = e.clone_snapshot();
17742 e.execute("INSERT INTO t VALUES (2)").unwrap();
17743 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
17744 let QueryResult::Rows { rows, .. } =
17745 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
17746 .unwrap()
17747 else {
17748 panic!("expected Rows")
17749 };
17750 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
17751 }
17752
17753 #[test]
17754 fn describe_prepared_on_snapshot_resolves_columns() {
17755 let mut e = Engine::new();
17760 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17761 .unwrap();
17762 let snapshot = e.clone_snapshot();
17763 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
17764 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
17765 assert_eq!(cols.len(), 2);
17766 assert_eq!(cols[0].name, "id");
17767 assert_eq!(cols[0].ty, DataType::Int);
17768 assert_eq!(cols[1].name, "name");
17769 assert_eq!(cols[1].ty, DataType::Text);
17770 }
17771
17772 #[test]
17773 fn insert_into_half_column_dim_mismatch_errors() {
17774 let mut e = Engine::new();
17775 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17776 .unwrap();
17777 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
17778 assert!(matches!(
17779 &err,
17780 EngineError::Storage(StorageError::TypeMismatch { .. })
17781 ));
17782 }
17783
17784 #[test]
17785 fn insert_into_sq8_column_dim_mismatch_errors() {
17786 let mut e = Engine::new();
17791 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17792 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
17793 assert!(
17794 matches!(
17795 &err,
17796 EngineError::Storage(StorageError::TypeMismatch { .. })
17797 ),
17798 "got: {err}",
17799 );
17800 }
17801
17802 #[test]
17803 fn create_table_duplicate_errors() {
17804 let mut e = Engine::new();
17805 e.execute("CREATE TABLE foo (a INT)").unwrap();
17806 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
17807 assert!(matches!(
17808 err,
17809 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
17810 ));
17811 }
17812
17813 #[test]
17814 fn insert_into_unknown_table_errors() {
17815 let mut e = Engine::new();
17816 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
17817 assert!(matches!(
17818 err,
17819 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
17820 ));
17821 }
17822
17823 #[test]
17824 fn insert_happy_path_reports_one_affected() {
17825 let mut e = Engine::new();
17826 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17827 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
17828 assert_eq!(unwrap_command_ok(&r), 1);
17829 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
17830 }
17831
17832 #[test]
17833 fn insert_arity_mismatch_propagates() {
17834 let mut e = Engine::new();
17835 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
17836 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
17837 assert!(matches!(
17838 err,
17839 EngineError::Storage(StorageError::ArityMismatch { .. })
17840 ));
17841 }
17842
17843 #[test]
17844 fn insert_negative_integer_via_unary_minus() {
17845 let mut e = Engine::new();
17846 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17847 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
17848 let rows = e.catalog().get("foo").unwrap().rows();
17849 assert_eq!(rows[0].values[0], Value::Int(-7));
17850 }
17851
17852 #[test]
17853 fn insert_expression_evaluated_against_empty_context() {
17854 let mut e = Engine::new();
17859 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17860 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
17861 let rows = e.catalog().get("foo").unwrap().rows();
17862 assert_eq!(rows[0].values[0], Value::Int(3));
17863 }
17864
17865 #[test]
17866 fn select_star_returns_all_rows_in_insertion_order() {
17867 let mut e = Engine::new();
17868 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
17869 .unwrap();
17870 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
17871 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
17872 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
17873
17874 let r = e.execute("SELECT * FROM foo").unwrap();
17875 let QueryResult::Rows { columns, rows } = r else {
17876 panic!("expected Rows")
17877 };
17878 assert_eq!(columns.len(), 2);
17879 assert_eq!(columns[0].name, "a");
17880 assert_eq!(rows.len(), 3);
17881 assert_eq!(
17882 rows[1].values,
17883 vec![Value::Int(2), Value::Text("two".into())]
17884 );
17885 }
17886
17887 #[test]
17888 fn select_star_on_empty_table_returns_zero_rows() {
17889 let mut e = Engine::new();
17890 e.execute("CREATE TABLE foo (a INT)").unwrap();
17891 let r = e.execute("SELECT * FROM foo").unwrap();
17892 match r {
17893 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
17894 QueryResult::CommandOk { .. } => panic!("expected Rows"),
17895 }
17896 }
17897
17898 fn make_three_row_users(e: &mut Engine) {
17901 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
17902 .unwrap();
17903 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
17904 .unwrap();
17905 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
17906 .unwrap();
17907 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
17908 .unwrap();
17909 }
17910
17911 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
17912 match r {
17913 QueryResult::Rows { columns, rows } => (columns, rows),
17914 QueryResult::CommandOk { .. } => panic!("expected Rows"),
17915 }
17916 }
17917
17918 #[test]
17919 fn where_filter_passes_only_true_rows() {
17920 let mut e = Engine::new();
17921 make_three_row_users(&mut e);
17922 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
17923 let (_, rows) = unwrap_rows(r);
17924 assert_eq!(rows.len(), 2);
17925 assert_eq!(rows[0].values[0], Value::Int(2));
17926 assert_eq!(rows[1].values[0], Value::Int(3));
17927 }
17928
17929 #[test]
17930 fn where_with_null_result_filters_out_row() {
17931 let mut e = Engine::new();
17932 make_three_row_users(&mut e);
17933 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
17935 let (_, rows) = unwrap_rows(r);
17936 assert_eq!(rows.len(), 1);
17937 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
17938 }
17939
17940 #[test]
17941 fn projection_named_columns() {
17942 let mut e = Engine::new();
17943 make_three_row_users(&mut e);
17944 let r = e.execute("SELECT name, score FROM users").unwrap();
17945 let (cols, rows) = unwrap_rows(r);
17946 assert_eq!(cols.len(), 2);
17947 assert_eq!(cols[0].name, "name");
17948 assert_eq!(cols[1].name, "score");
17949 assert_eq!(rows.len(), 3);
17950 assert_eq!(
17951 rows[0].values,
17952 vec![Value::Text("alice".into()), Value::Int(90)]
17953 );
17954 }
17955
17956 #[test]
17957 fn projection_with_column_alias() {
17958 let mut e = Engine::new();
17959 make_three_row_users(&mut e);
17960 let r = e
17961 .execute("SELECT name AS who FROM users WHERE id = 1")
17962 .unwrap();
17963 let (cols, rows) = unwrap_rows(r);
17964 assert_eq!(cols[0].name, "who");
17965 assert_eq!(rows.len(), 1);
17966 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
17967 }
17968
17969 #[test]
17970 fn qualified_column_with_table_alias_resolves() {
17971 let mut e = Engine::new();
17972 make_three_row_users(&mut e);
17973 let r = e
17974 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
17975 .unwrap();
17976 let (cols, rows) = unwrap_rows(r);
17977 assert_eq!(cols.len(), 2);
17978 assert_eq!(rows.len(), 2);
17979 }
17980
17981 #[test]
17982 fn qualified_column_with_wrong_alias_errors() {
17983 let mut e = Engine::new();
17984 make_three_row_users(&mut e);
17985 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
17986 assert!(matches!(
17987 err,
17988 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
17989 ));
17990 }
17991
17992 #[test]
17993 fn select_unknown_column_errors_in_projection() {
17994 let mut e = Engine::new();
17995 make_three_row_users(&mut e);
17996 let err = e.execute("SELECT ghost FROM users").unwrap_err();
17997 assert!(matches!(
17998 err,
17999 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18000 ));
18001 }
18002
18003 #[test]
18004 fn where_unknown_column_errors() {
18005 let mut e = Engine::new();
18006 make_three_row_users(&mut e);
18007 let err = e
18008 .execute("SELECT * FROM users WHERE ghost = 1")
18009 .unwrap_err();
18010 assert!(matches!(
18011 err,
18012 EngineError::Eval(EvalError::ColumnNotFound { .. })
18013 ));
18014 }
18015
18016 #[test]
18017 fn expression_projection_evaluates_and_renders() {
18018 let mut e = Engine::new();
18021 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18022 e.execute("INSERT INTO t VALUES (3)").unwrap();
18023 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18024 assert_eq!(rows.len(), 1);
18025 assert_eq!(rows[0].values[0], Value::Int(3));
18028 }
18029
18030 #[test]
18031 fn select_unknown_table_errors() {
18032 let mut e = Engine::new();
18033 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18034 assert!(matches!(
18035 err,
18036 EngineError::Storage(StorageError::TableNotFound { .. })
18037 ));
18038 }
18039
18040 #[test]
18041 fn invalid_sql_returns_parse_error() {
18042 let mut e = Engine::new();
18045 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18046 assert!(matches!(err, EngineError::Parse(_)));
18047 }
18048
18049 #[test]
18052 fn create_index_registers_on_table() {
18053 let mut e = Engine::new();
18054 make_three_row_users(&mut e);
18055 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18056 let t = e.catalog().get("users").unwrap();
18057 assert_eq!(t.indices().len(), 1);
18058 assert_eq!(t.indices()[0].name, "by_name");
18059 }
18060
18061 #[test]
18062 fn create_index_on_unknown_table_errors() {
18063 let mut e = Engine::new();
18064 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18065 assert!(matches!(
18066 err,
18067 EngineError::Storage(StorageError::TableNotFound { .. })
18068 ));
18069 }
18070
18071 #[test]
18072 fn create_index_on_unknown_column_errors() {
18073 let mut e = Engine::new();
18074 make_three_row_users(&mut e);
18075 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18076 assert!(matches!(
18077 err,
18078 EngineError::Storage(StorageError::ColumnNotFound { .. })
18079 ));
18080 }
18081
18082 #[test]
18083 fn select_eq_uses_index_returns_same_rows_as_scan() {
18084 let mut without = Engine::new();
18088 make_three_row_users(&mut without);
18089 let mut with = Engine::new();
18090 make_three_row_users(&mut with);
18091 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18092
18093 let q = "SELECT * FROM users WHERE id = 2";
18094 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18095 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18096 assert_eq!(no_idx_rows, idx_rows);
18097 assert_eq!(idx_rows.len(), 1);
18098 }
18099
18100 #[test]
18101 fn select_eq_with_no_matching_index_value_returns_empty() {
18102 let mut e = Engine::new();
18103 make_three_row_users(&mut e);
18104 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18105 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18106 assert_eq!(rows.len(), 0);
18107 }
18108
18109 #[test]
18112 fn begin_sets_in_transaction_flag() {
18113 let mut e = Engine::new();
18114 assert!(!e.in_transaction());
18115 e.execute("BEGIN").unwrap();
18116 assert!(e.in_transaction());
18117 }
18118
18119 #[test]
18120 fn double_begin_errors() {
18121 let mut e = Engine::new();
18122 e.execute("BEGIN").unwrap();
18123 let err = e.execute("BEGIN").unwrap_err();
18124 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18125 }
18126
18127 #[test]
18128 fn commit_without_begin_errors() {
18129 let mut e = Engine::new();
18130 let err = e.execute("COMMIT").unwrap_err();
18131 assert_eq!(err, EngineError::NoActiveTransaction);
18132 }
18133
18134 #[test]
18135 fn rollback_without_begin_errors() {
18136 let mut e = Engine::new();
18137 let err = e.execute("ROLLBACK").unwrap_err();
18138 assert_eq!(err, EngineError::NoActiveTransaction);
18139 }
18140
18141 #[test]
18142 fn commit_applies_shadow_to_committed_catalog() {
18143 let mut e = Engine::new();
18144 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18145 e.execute("BEGIN").unwrap();
18146 e.execute("INSERT INTO t VALUES (1)").unwrap();
18147 e.execute("INSERT INTO t VALUES (2)").unwrap();
18148 e.execute("COMMIT").unwrap();
18149 assert!(!e.in_transaction());
18150 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18151 }
18152
18153 #[test]
18154 fn rollback_discards_shadow() {
18155 let mut e = Engine::new();
18156 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18157 e.execute("BEGIN").unwrap();
18158 e.execute("INSERT INTO t VALUES (1)").unwrap();
18159 e.execute("INSERT INTO t VALUES (2)").unwrap();
18160 e.execute("ROLLBACK").unwrap();
18161 assert!(!e.in_transaction());
18162 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18163 }
18164
18165 #[test]
18166 fn select_during_tx_sees_uncommitted_writes_own_session() {
18167 let mut e = Engine::new();
18170 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18171 e.execute("BEGIN").unwrap();
18172 e.execute("INSERT INTO t VALUES (42)").unwrap();
18173 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18174 assert_eq!(rows.len(), 1);
18175 assert_eq!(rows[0].values[0], Value::Int(42));
18176 }
18177
18178 #[test]
18179 fn snapshot_with_no_users_is_bare_catalog_format() {
18180 let mut e = Engine::new();
18181 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18182 let bytes = e.snapshot();
18183 assert_eq!(
18184 &bytes[..8],
18185 b"SPGDB001",
18186 "must be the bare v3.x catalog magic"
18187 );
18188 let e2 = Engine::restore_envelope(&bytes).unwrap();
18189 assert!(e2.users().is_empty());
18190 assert_eq!(e2.catalog().table_count(), 1);
18191 }
18192
18193 #[test]
18194 fn snapshot_with_users_round_trips_both_via_envelope() {
18195 let mut e = Engine::new();
18196 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18197 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18198 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18199 .unwrap();
18200 let bytes = e.snapshot();
18201 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18202 let e2 = Engine::restore_envelope(&bytes).unwrap();
18203 assert_eq!(e2.users().len(), 2);
18204 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18205 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18206 assert_eq!(e2.verify_user("alice", "wrong"), None);
18207 assert_eq!(e2.catalog().table_count(), 1);
18208 }
18209
18210 #[test]
18211 fn ddl_inside_tx_also_rolled_back() {
18212 let mut e = Engine::new();
18213 e.execute("BEGIN").unwrap();
18214 e.execute("CREATE TABLE t (v INT)").unwrap();
18215 e.execute("SELECT * FROM t").unwrap();
18217 e.execute("ROLLBACK").unwrap();
18218 let err = e.execute("SELECT * FROM t").unwrap_err();
18220 assert!(matches!(
18221 err,
18222 EngineError::Storage(StorageError::TableNotFound { .. })
18223 ));
18224 }
18225
18226 #[test]
18229 fn create_publication_lands_in_catalog() {
18230 let mut e = Engine::new();
18231 assert!(e.publications().is_empty());
18232 e.execute("CREATE PUBLICATION pub_a").unwrap();
18233 assert_eq!(e.publications().len(), 1);
18234 assert!(e.publications().contains("pub_a"));
18235 }
18236
18237 #[test]
18238 fn create_publication_duplicate_errors() {
18239 let mut e = Engine::new();
18240 e.execute("CREATE PUBLICATION pub_a").unwrap();
18241 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18242 assert!(
18243 alloc::format!("{err:?}").contains("DuplicateName"),
18244 "got {err:?}"
18245 );
18246 }
18247
18248 #[test]
18249 fn drop_publication_silent_when_absent() {
18250 let mut e = Engine::new();
18251 let r = e.execute("DROP PUBLICATION nope").unwrap();
18254 match r {
18255 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18256 other => panic!("expected CommandOk, got {other:?}"),
18257 }
18258 }
18259
18260 #[test]
18261 fn drop_publication_present_reports_one_affected() {
18262 let mut e = Engine::new();
18263 e.execute("CREATE PUBLICATION pub_a").unwrap();
18264 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18265 match r {
18266 QueryResult::CommandOk {
18267 affected,
18268 modified_catalog,
18269 } => {
18270 assert_eq!(affected, 1);
18271 assert!(modified_catalog);
18272 }
18273 other => panic!("expected CommandOk, got {other:?}"),
18274 }
18275 assert!(e.publications().is_empty());
18276 }
18277
18278 #[test]
18279 fn publications_persist_across_snapshot_restore() {
18280 let mut e = Engine::new();
18285 e.execute("CREATE PUBLICATION pub_a").unwrap();
18286 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18287 .unwrap();
18288 let snap = e.snapshot();
18289 let e2 = Engine::restore_envelope(&snap).unwrap();
18290 assert_eq!(e2.publications().len(), 2);
18291 assert!(e2.publications().contains("pub_a"));
18292 assert!(e2.publications().contains("pub_b"));
18293 }
18294
18295 #[test]
18296 fn create_publication_allowed_inside_transaction() {
18297 let mut e = Engine::new();
18301 e.execute("BEGIN").unwrap();
18302 e.execute("CREATE PUBLICATION pub_a").unwrap();
18303 e.execute("COMMIT").unwrap();
18304 assert!(e.publications().contains("pub_a"));
18305 }
18306
18307 #[test]
18310 fn create_publication_for_table_list_lands_with_scope() {
18311 let mut e = Engine::new();
18312 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18313 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18314 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18315 .unwrap();
18316 let scope = e.publications().get("pub_a").cloned();
18317 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18318 panic!("expected ForTables scope, got {scope:?}")
18319 };
18320 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18321 }
18322
18323 #[test]
18324 fn create_publication_all_tables_except_lands_with_scope() {
18325 let mut e = Engine::new();
18326 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18327 .unwrap();
18328 let scope = e.publications().get("pub_a").cloned();
18329 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18330 panic!("expected AllTablesExcept scope, got {scope:?}")
18331 };
18332 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18333 }
18334
18335 #[test]
18336 fn show_publications_empty_returns_zero_rows() {
18337 let e = Engine::new();
18338 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18339 let QueryResult::Rows { rows, columns } = r else {
18340 panic!()
18341 };
18342 assert!(rows.is_empty());
18343 assert_eq!(columns.len(), 3);
18344 assert_eq!(columns[0].name, "name");
18345 assert_eq!(columns[1].name, "scope");
18346 assert_eq!(columns[2].name, "table_count");
18347 }
18348
18349 #[test]
18350 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18351 let mut e = Engine::new();
18352 e.execute("CREATE PUBLICATION z_pub").unwrap();
18353 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18354 .unwrap();
18355 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18356 .unwrap();
18357 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18358 let QueryResult::Rows { rows, .. } = r else {
18359 panic!()
18360 };
18361 assert_eq!(rows.len(), 3);
18362 let names: Vec<&str> = rows
18364 .iter()
18365 .map(|r| {
18366 if let Value::Text(s) = &r.values[0] {
18367 s.as_str()
18368 } else {
18369 panic!()
18370 }
18371 })
18372 .collect();
18373 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18374 match &rows[0].values[1] {
18376 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18377 other => panic!("expected Text, got {other:?}"),
18378 }
18379 assert_eq!(rows[0].values[2], Value::Int(2));
18380 match &rows[1].values[1] {
18382 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18383 other => panic!("expected Text, got {other:?}"),
18384 }
18385 assert_eq!(rows[1].values[2], Value::Int(1));
18386 match &rows[2].values[1] {
18388 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18389 other => panic!("expected Text, got {other:?}"),
18390 }
18391 assert_eq!(rows[2].values[2], Value::Null);
18392 }
18393
18394 #[test]
18395 fn for_list_scopes_persist_across_snapshot() {
18396 let mut e = Engine::new();
18399 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18400 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18401 .unwrap();
18402 let snap = e.snapshot();
18403 let e2 = Engine::restore_envelope(&snap).unwrap();
18404 assert_eq!(e2.publications().len(), 2);
18405 let p1 = e2.publications().get("p1").cloned();
18406 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18407 panic!("p1 scope lost: {p1:?}")
18408 };
18409 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18410 let p2 = e2.publications().get("p2").cloned();
18411 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18412 panic!("p2 scope lost: {p2:?}")
18413 };
18414 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18415 }
18416
18417 #[test]
18420 fn create_subscription_lands_in_catalog_with_defaults() {
18421 let mut e = Engine::new();
18422 e.execute(
18423 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18424 )
18425 .unwrap();
18426 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18427 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18428 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18429 assert!(s.enabled);
18430 assert_eq!(s.last_received_pos, 0);
18431 }
18432
18433 #[test]
18434 fn create_subscription_duplicate_name_errors() {
18435 let mut e = Engine::new();
18436 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18437 .unwrap();
18438 let err = e
18439 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18440 .unwrap_err();
18441 assert!(
18442 alloc::format!("{err:?}").contains("DuplicateName"),
18443 "got {err:?}"
18444 );
18445 }
18446
18447 #[test]
18448 fn drop_subscription_silent_when_absent() {
18449 let mut e = Engine::new();
18450 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18451 match r {
18452 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18453 other => panic!("expected CommandOk, got {other:?}"),
18454 }
18455 }
18456
18457 #[test]
18458 fn subscription_advance_updates_last_pos_monotone() {
18459 let mut e = Engine::new();
18460 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18461 .unwrap();
18462 assert!(e.subscription_advance("s", 100));
18463 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18464 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18466 assert!(e.subscription_advance("s", 200));
18467 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18468 assert!(!e.subscription_advance("missing", 1));
18469 }
18470
18471 #[test]
18472 fn show_subscriptions_returns_rows_ordered_by_name() {
18473 let mut e = Engine::new();
18474 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18475 .unwrap();
18476 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18477 .unwrap();
18478 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18479 let QueryResult::Rows { rows, columns } = r else {
18480 panic!()
18481 };
18482 assert_eq!(rows.len(), 2);
18483 assert_eq!(columns.len(), 5);
18484 assert_eq!(columns[0].name, "name");
18485 assert_eq!(columns[4].name, "last_received_pos");
18486 let names: Vec<&str> = rows
18488 .iter()
18489 .map(|r| {
18490 if let Value::Text(s) = &r.values[0] {
18491 s.as_str()
18492 } else {
18493 panic!()
18494 }
18495 })
18496 .collect();
18497 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18498 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18500 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18501 assert_eq!(rows[0].values[3], Value::Bool(true));
18502 assert_eq!(rows[0].values[4], Value::BigInt(0));
18503 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18505 }
18506
18507 #[test]
18508 fn subscriptions_persist_across_snapshot_envelope_v4() {
18509 let mut e = Engine::new();
18510 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18511 .unwrap();
18512 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18513 .unwrap();
18514 e.subscription_advance("s2", 42);
18515 let snap = e.snapshot();
18516 let e2 = Engine::restore_envelope(&snap).unwrap();
18517 assert_eq!(e2.subscriptions().len(), 2);
18518 let s1 = e2.subscriptions().get("s1").unwrap();
18519 assert_eq!(s1.conn_str, "h=A");
18520 assert_eq!(
18521 s1.publications,
18522 alloc::vec!["p1".to_string(), "p2".to_string()]
18523 );
18524 assert_eq!(s1.last_received_pos, 0);
18525 let s2 = e2.subscriptions().get("s2").unwrap();
18526 assert_eq!(s2.last_received_pos, 42);
18527 }
18528
18529 #[test]
18530 fn v3_envelope_loads_with_empty_subscriptions() {
18531 let mut e = Engine::new();
18535 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
18536 let catalog = e.catalog.serialize();
18537 let users = crate::users::serialize_users(&e.users);
18538 let pubs = e.publications.serialize();
18539 let mut buf = Vec::new();
18540 buf.extend_from_slice(b"SPGENV01");
18541 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18543 buf.extend_from_slice(&catalog);
18544 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18545 buf.extend_from_slice(&users);
18546 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18547 buf.extend_from_slice(&pubs);
18548 let crc = spg_crypto::crc32::crc32(&buf);
18549 buf.extend_from_slice(&crc.to_le_bytes());
18550
18551 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
18552 assert!(e2.subscriptions().is_empty());
18553 assert!(e2.publications().contains("pub_legacy"));
18554 }
18555
18556 #[test]
18557 fn create_subscription_allowed_inside_transaction() {
18558 let mut e = Engine::new();
18559 e.execute("BEGIN").unwrap();
18560 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18561 .unwrap();
18562 e.execute("COMMIT").unwrap();
18563 assert!(e.subscriptions().contains("s"));
18564 }
18565
18566 #[test]
18568 fn analyze_populates_histogram_bounds() {
18569 let mut e = Engine::new();
18570 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
18571 .unwrap();
18572 for i in 0..50 {
18573 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
18574 .unwrap();
18575 }
18576 e.execute("ANALYZE t").unwrap();
18577 let stats = e.statistics();
18578 let id_stats = stats.get("t", "id").unwrap();
18579 assert!(id_stats.histogram_bounds.len() >= 2);
18580 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
18581 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
18582 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
18583 assert_eq!(id_stats.n_distinct, 50);
18584 }
18585
18586 #[test]
18587 fn reanalyze_overwrites_prior_stats() {
18588 let mut e = Engine::new();
18589 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18590 for i in 0..10 {
18591 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18592 .unwrap();
18593 }
18594 e.execute("ANALYZE t").unwrap();
18595 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
18596 assert_eq!(n1, 10);
18597 for i in 10..30 {
18598 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18599 .unwrap();
18600 }
18601 e.execute("ANALYZE t").unwrap();
18602 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
18603 assert_eq!(n2, 30);
18604 }
18605
18606 #[test]
18607 fn analyze_unknown_table_errors() {
18608 let mut e = Engine::new();
18609 let err = e.execute("ANALYZE nonexistent").unwrap_err();
18610 assert!(matches!(
18611 err,
18612 EngineError::Storage(StorageError::TableNotFound { .. })
18613 ));
18614 }
18615
18616 #[test]
18617 fn bare_analyze_covers_all_user_tables() {
18618 let mut e = Engine::new();
18619 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18620 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
18621 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
18622 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
18623 let r = e.execute("ANALYZE").unwrap();
18624 match r {
18625 QueryResult::CommandOk {
18626 affected,
18627 modified_catalog,
18628 } => {
18629 assert_eq!(affected, 2);
18630 assert!(modified_catalog);
18631 }
18632 other => panic!("expected CommandOk, got {other:?}"),
18633 }
18634 assert!(e.statistics().get("t1", "id").is_some());
18635 assert!(e.statistics().get("t2", "name").is_some());
18636 }
18637
18638 #[test]
18639 fn select_from_spg_statistic_returns_rows_per_column() {
18640 let mut e = Engine::new();
18641 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18642 .unwrap();
18643 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
18644 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
18645 e.execute("ANALYZE t").unwrap();
18646 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
18647 let QueryResult::Rows { rows, columns } = r else {
18648 panic!()
18649 };
18650 assert_eq!(columns.len(), 6);
18652 assert_eq!(columns[0].name, "table_name");
18653 assert_eq!(columns[4].name, "histogram_bounds");
18654 assert_eq!(columns[5].name, "cold_row_count");
18655 assert_eq!(rows.len(), 2, "one row per column of t");
18656 match (&rows[0].values[0], &rows[0].values[1]) {
18658 (Value::Text(t), Value::Text(c)) => {
18659 assert_eq!(t, "t");
18660 assert_eq!(c, "id");
18662 }
18663 _ => panic!(),
18664 }
18665 }
18666
18667 #[test]
18668 fn analyze_skips_vector_columns() {
18669 let mut e = Engine::new();
18672 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18673 .unwrap();
18674 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
18675 e.execute("ANALYZE t").unwrap();
18676 assert!(e.statistics().get("t", "id").is_some());
18677 assert!(e.statistics().get("t", "v").is_none());
18678 }
18679
18680 #[test]
18681 fn statistics_persist_across_envelope_v5_round_trip() {
18682 let mut e = Engine::new();
18683 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18684 for i in 0..20 {
18685 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18686 .unwrap();
18687 }
18688 e.execute("ANALYZE").unwrap();
18689 let snap = e.snapshot();
18690 let e2 = Engine::restore_envelope(&snap).unwrap();
18691 let s = e2.statistics().get("t", "id").unwrap();
18692 assert_eq!(s.n_distinct, 20);
18693 }
18694
18695 #[test]
18698 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
18699 let mut e = Engine::new();
18703 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18704 for i in 0..9 {
18705 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18706 .unwrap();
18707 }
18708 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
18709 e.execute("INSERT INTO t VALUES (9)").unwrap();
18710 let needs = e.tables_needing_analyze();
18711 assert_eq!(needs, alloc::vec!["t".to_string()]);
18712 }
18713
18714 #[test]
18715 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
18716 let mut e = Engine::new();
18722 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18723 for i in 0..1000 {
18724 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18725 .unwrap();
18726 }
18727 e.execute("ANALYZE t").unwrap();
18728 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
18729 for i in 1000..1050 {
18730 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18731 .unwrap();
18732 }
18733 assert!(
18734 e.tables_needing_analyze().is_empty(),
18735 "50 inserts < threshold of ~105"
18736 );
18737 for i in 1050..1200 {
18738 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18739 .unwrap();
18740 }
18741 assert_eq!(
18742 e.tables_needing_analyze(),
18743 alloc::vec!["t".to_string()],
18744 "200 inserts > 0.1 × 1200 threshold"
18745 );
18746 }
18747
18748 #[test]
18749 fn auto_analyze_threshold_resets_after_analyze() {
18750 let mut e = Engine::new();
18751 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18752 for i in 0..200 {
18753 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18754 .unwrap();
18755 }
18756 assert!(!e.tables_needing_analyze().is_empty());
18757 e.execute("ANALYZE").unwrap();
18758 assert!(
18759 e.tables_needing_analyze().is_empty(),
18760 "ANALYZE must reset the counter"
18761 );
18762 }
18763
18764 #[test]
18765 fn auto_analyze_threshold_tracks_updates_and_deletes() {
18766 let mut e = Engine::new();
18767 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18768 .unwrap();
18769 for i in 0..50 {
18770 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
18771 .unwrap();
18772 }
18773 e.execute("ANALYZE t").unwrap();
18774 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
18777 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
18778 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
18779 }
18780
18781 #[test]
18782 fn v4_envelope_loads_with_empty_statistics() {
18783 let mut e = Engine::new();
18787 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
18788 .unwrap();
18789 let catalog = e.catalog.serialize();
18790 let users = crate::users::serialize_users(&e.users);
18791 let pubs = e.publications.serialize();
18792 let subs = e.subscriptions.serialize();
18793 let mut buf = Vec::new();
18794 buf.extend_from_slice(b"SPGENV01");
18795 buf.push(4u8);
18796 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18797 buf.extend_from_slice(&catalog);
18798 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18799 buf.extend_from_slice(&users);
18800 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18801 buf.extend_from_slice(&pubs);
18802 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
18803 buf.extend_from_slice(&subs);
18804 let crc = spg_crypto::crc32::crc32(&buf);
18805 buf.extend_from_slice(&crc.to_le_bytes());
18806 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
18807 assert!(e2.statistics().is_empty());
18808 }
18809
18810 #[test]
18811 fn v1_v2_envelope_loads_with_empty_publications() {
18812 let mut e = Engine::new();
18819 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
18822 .unwrap();
18823
18824 let catalog = e.catalog.serialize();
18826 let users = crate::users::serialize_users(&e.users);
18827 let mut buf = Vec::new();
18828 buf.extend_from_slice(b"SPGENV01");
18829 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18831 buf.extend_from_slice(&catalog);
18832 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18833 buf.extend_from_slice(&users);
18834 let crc = spg_crypto::crc32::crc32(&buf);
18835 buf.extend_from_slice(&crc.to_le_bytes());
18836
18837 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
18838 assert!(e2.publications().is_empty());
18839 }
18840}