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(mut s) => {
1974 for (_, e) in &mut s.assignments {
1980 self.resolve_expr_subqueries(e, cancel)?;
1981 }
1982 if let Some(w) = &mut s.where_ {
1983 self.resolve_expr_subqueries(w, cancel)?;
1984 }
1985 self.exec_update_cancel(&s, cancel)
1986 }
1987 Statement::Delete(mut s) => {
1988 if let Some(w) = &mut s.where_ {
1989 self.resolve_expr_subqueries(w, cancel)?;
1990 }
1991 self.exec_delete_cancel(&s, cancel)
1992 }
1993 Statement::Merge(s) => self.exec_merge_cancel(&s, cancel),
1994 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1995 Statement::Begin => self.exec_begin(),
1996 Statement::Commit => self.exec_commit(),
1997 Statement::Rollback => self.exec_rollback(),
1998 Statement::Savepoint(name) => self.exec_savepoint(name),
1999 Statement::RollbackToSavepoint(name) => self.exec_rollback_to_savepoint(&name),
2000 Statement::ReleaseSavepoint(name) => self.exec_release_savepoint(&name),
2001 Statement::ShowTables => Ok(self.exec_show_tables()),
2002 Statement::ShowDatabases => Ok(self.exec_show_databases()),
2003 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
2004 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
2005 Statement::ShowStatus => Ok(self.exec_show_status()),
2006 Statement::ShowVariables => Ok(self.exec_show_variables()),
2007 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
2008 Statement::ShowColumns(table) => self.exec_show_columns(&table),
2009 Statement::ShowUsers => Ok(self.exec_show_users()),
2010 Statement::ShowPublications => Ok(self.exec_show_publications()),
2011 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
2012 Statement::CreateUser(s) => self.exec_create_user(&s),
2013 Statement::DropUser(name) => self.exec_drop_user(&name),
2014 Statement::Explain(e) => self.exec_explain(&e, cancel),
2015 Statement::AlterIndex(s) => self.exec_alter_index(s),
2016 Statement::AlterTable(s) => self.exec_alter_table(s),
2017 Statement::CreatePublication(s) => self.exec_create_publication(s),
2018 Statement::DropPublication(name) => self.exec_drop_publication(&name),
2019 Statement::CreateSubscription(s) => self.exec_create_subscription(s),
2020 Statement::DropSubscription(name) => self.exec_drop_subscription(&name),
2021 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
2028 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
2029 )),
2030 Statement::Analyze(target) => self.exec_analyze(target.as_deref()),
2032 Statement::CompactColdSegments => self.exec_compact_cold_segments(),
2034 Statement::SetParameter { name, value } => {
2039 self.set_session_param(name, value);
2040 Ok(QueryResult::CommandOk {
2041 affected: 0,
2042 modified_catalog: false,
2043 })
2044 }
2045 Statement::SetParameterList(pairs) => {
2051 for (name, value) in pairs {
2052 self.set_session_param(name, value);
2053 }
2054 Ok(QueryResult::CommandOk {
2055 affected: 0,
2056 modified_catalog: false,
2057 })
2058 }
2059 Statement::CreateFunction(s) => self.exec_create_function(s),
2063 Statement::CreateTrigger(s) => self.exec_create_trigger(s),
2064 Statement::DropTrigger {
2065 name,
2066 table,
2067 if_exists,
2068 } => self.exec_drop_trigger(&name, &table, if_exists),
2069 Statement::DropFunction { name, if_exists } => {
2070 self.exec_drop_function(&name, if_exists)
2071 }
2072 Statement::CreateSequence(s) => self.exec_create_sequence(s),
2073 Statement::AlterSequence(s) => self.exec_alter_sequence(s),
2074 Statement::DropSequence { names, if_exists } => {
2075 self.exec_drop_sequence(&names, if_exists)
2076 }
2077 Statement::CreateView(s) => self.exec_create_view(s),
2078 Statement::DropView { names, if_exists } => self.exec_drop_view(&names, if_exists),
2079 Statement::CreateMaterializedView(s) => self.exec_create_materialized_view(s),
2080 Statement::RefreshMaterializedView { name, with_data } => {
2081 self.exec_refresh_materialized_view(&name, with_data)
2082 }
2083 Statement::DropMaterializedView { names, if_exists } => {
2084 self.exec_drop_materialized_view(&names, if_exists)
2085 }
2086 Statement::CreateType(s) => self.exec_create_type(s),
2087 Statement::DropType { names, if_exists } => self.exec_drop_type(&names, if_exists),
2088 Statement::CreateDomain(s) => self.exec_create_domain(s),
2089 Statement::DropDomain { names, if_exists } => self.exec_drop_domain(&names, if_exists),
2090 Statement::CreateSchema {
2091 name,
2092 if_not_exists,
2093 } => self.exec_create_schema(name, if_not_exists),
2094 Statement::DropSchema { names, if_exists } => self.exec_drop_schema(&names, if_exists),
2095 Statement::ResetParameter(target) => {
2096 match target {
2097 None => self.session_params.clear(),
2098 Some(name) => {
2099 self.session_params.remove(&name.to_ascii_lowercase());
2100 }
2101 }
2102 Ok(QueryResult::CommandOk {
2103 affected: 0,
2104 modified_catalog: false,
2105 })
2106 }
2107 };
2108 self.enforce_row_limit(result)
2109 }
2110
2111 fn exec_create_publication(
2119 &mut self,
2120 s: CreatePublicationStatement,
2121 ) -> Result<QueryResult, EngineError> {
2122 self.publications
2128 .create(s.name, s.scope)
2129 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE PUBLICATION: {e:?}")))?;
2130 Ok(QueryResult::CommandOk {
2131 affected: 1,
2132 modified_catalog: true,
2133 })
2134 }
2135
2136 fn exec_drop_publication(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2141 let removed = self.publications.drop(name);
2142 Ok(QueryResult::CommandOk {
2143 affected: usize::from(removed),
2144 modified_catalog: removed,
2145 })
2146 }
2147
2148 pub const fn publications(&self) -> &publications::Publications {
2153 &self.publications
2154 }
2155
2156 fn exec_create_subscription(
2161 &mut self,
2162 s: CreateSubscriptionStatement,
2163 ) -> Result<QueryResult, EngineError> {
2164 let sub = subscriptions::Subscription {
2168 conn_str: s.conn_str,
2169 publications: s.publications,
2170 enabled: true,
2171 last_received_pos: 0,
2172 };
2173 self.subscriptions
2174 .create(s.name, sub)
2175 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE SUBSCRIPTION: {e:?}")))?;
2176 Ok(QueryResult::CommandOk {
2177 affected: 1,
2178 modified_catalog: true,
2179 })
2180 }
2181
2182 fn exec_drop_subscription(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2190 let removed = self.subscriptions.drop(name);
2191 Ok(QueryResult::CommandOk {
2192 affected: usize::from(removed),
2193 modified_catalog: removed,
2194 })
2195 }
2196
2197 pub const fn subscriptions(&self) -> &subscriptions::Subscriptions {
2202 &self.subscriptions
2203 }
2204
2205 pub fn subscription_advance(&mut self, name: &str, pos: u64) -> bool {
2211 self.subscriptions.update_last_received_pos(name, pos)
2212 }
2213
2214 fn exec_show_subscriptions(&self) -> QueryResult {
2220 let columns = alloc::vec![
2221 ColumnSchema::new("name", DataType::Text, false),
2222 ColumnSchema::new("conn_str", DataType::Text, false),
2223 ColumnSchema::new("publications", DataType::Text, false),
2224 ColumnSchema::new("enabled", DataType::Bool, false),
2225 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2226 ];
2227 let rows: Vec<Row> = self
2228 .subscriptions
2229 .iter()
2230 .map(|(name, sub)| {
2231 Row::new(alloc::vec![
2232 Value::Text(name.clone()),
2233 Value::Text(sub.conn_str.clone()),
2234 Value::Text(sub.publications.join(", ")),
2235 Value::Bool(sub.enabled),
2236 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2237 ])
2238 })
2239 .collect();
2240 QueryResult::Rows { columns, rows }
2241 }
2242
2243 fn exec_spg_statistic(&self) -> QueryResult {
2248 let columns = alloc::vec![
2249 ColumnSchema::new("table_name", DataType::Text, false),
2250 ColumnSchema::new("column_name", DataType::Text, false),
2251 ColumnSchema::new("null_frac", DataType::Float, false),
2252 ColumnSchema::new("n_distinct", DataType::BigInt, false),
2253 ColumnSchema::new("histogram_bounds", DataType::Text, false),
2254 ColumnSchema::new("cold_row_count", DataType::BigInt, false),
2259 ];
2260 let rows: Vec<Row> = self
2261 .statistics
2262 .iter()
2263 .map(|((t, c), s)| {
2264 let cold = self
2265 .catalog
2266 .get(t)
2267 .map_or(0, |table| table.cold_row_count());
2268 Row::new(alloc::vec![
2269 Value::Text(t.clone()),
2270 Value::Text(c.clone()),
2271 Value::Float(f64::from(s.null_frac)),
2272 Value::BigInt(i64::try_from(s.n_distinct).unwrap_or(i64::MAX)),
2273 Value::Text(render_histogram_bounds(&s.histogram_bounds)),
2274 Value::BigInt(i64::try_from(cold).unwrap_or(i64::MAX)),
2275 ])
2276 })
2277 .collect();
2278 QueryResult::Rows { columns, rows }
2279 }
2280
2281 fn exec_spg_stat_replication(&self) -> QueryResult {
2288 let columns = alloc::vec![
2289 ColumnSchema::new("name", DataType::Text, false),
2290 ColumnSchema::new("conn_str", DataType::Text, false),
2291 ColumnSchema::new("publications", DataType::Text, false),
2292 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2293 ColumnSchema::new("enabled", DataType::Bool, false),
2294 ];
2295 let rows: Vec<Row> = self
2296 .subscriptions
2297 .iter()
2298 .map(|(name, sub)| {
2299 Row::new(alloc::vec![
2300 Value::Text(name.clone()),
2301 Value::Text(sub.conn_str.clone()),
2302 Value::Text(sub.publications.join(",")),
2303 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2304 Value::Bool(sub.enabled),
2305 ])
2306 })
2307 .collect();
2308 QueryResult::Rows { columns, rows }
2309 }
2310
2311 fn exec_spg_stat_segment(&self) -> QueryResult {
2323 let columns = alloc::vec![
2324 ColumnSchema::new("segment_id", DataType::BigInt, false),
2325 ColumnSchema::new("table_name", DataType::Text, false),
2326 ColumnSchema::new("num_rows", DataType::BigInt, false),
2327 ColumnSchema::new("num_pages", DataType::BigInt, false),
2328 ColumnSchema::new("total_bytes", DataType::BigInt, false),
2329 ];
2330 let mut segment_owners: alloc::collections::BTreeMap<u32, String> = BTreeMap::new();
2336 for tname in self.catalog.table_names() {
2337 if is_internal_table_name(&tname) {
2338 continue;
2339 }
2340 let Some(t) = self.catalog.get(&tname) else {
2341 continue;
2342 };
2343 for idx in t.indices() {
2344 if let spg_storage::IndexKind::BTree(map) = &idx.kind {
2345 for (_, locs) in map.iter() {
2346 for loc in locs {
2347 if let spg_storage::RowLocator::Cold { segment_id, .. } = loc {
2348 segment_owners
2349 .entry(*segment_id)
2350 .or_insert_with(|| tname.clone());
2351 }
2352 }
2353 }
2354 }
2355 }
2356 }
2357 let rows: Vec<Row> = self
2358 .catalog
2359 .cold_segment_ids_global()
2360 .iter()
2361 .filter_map(|&id| {
2362 let seg = self.catalog.cold_segment(id)?;
2363 let meta = seg.meta();
2364 let owner = segment_owners.get(&id).cloned().unwrap_or_default();
2365 Some(Row::new(alloc::vec![
2366 Value::BigInt(i64::from(id)),
2367 Value::Text(owner),
2368 Value::BigInt(i64::try_from(meta.num_rows).unwrap_or(i64::MAX)),
2369 Value::BigInt(i64::from(meta.num_pages)),
2370 Value::BigInt(i64::try_from(meta.total_bytes).unwrap_or(i64::MAX)),
2371 ]))
2372 })
2373 .collect();
2374 QueryResult::Rows { columns, rows }
2375 }
2376
2377 fn exec_spg_stat_query(&self) -> QueryResult {
2383 let columns = alloc::vec![
2384 ColumnSchema::new("sql", DataType::Text, false),
2385 ColumnSchema::new("exec_count", DataType::BigInt, false),
2386 ColumnSchema::new("total_us", DataType::BigInt, false),
2387 ColumnSchema::new("mean_us", DataType::BigInt, false),
2388 ColumnSchema::new("max_us", DataType::BigInt, false),
2389 ColumnSchema::new("last_seen_us", DataType::BigInt, false),
2390 ];
2391 let rows: Vec<Row> = self
2392 .query_stats
2393 .snapshot()
2394 .into_iter()
2395 .map(|(sql, s)| {
2396 let mean = if s.exec_count == 0 {
2397 0
2398 } else {
2399 s.total_us / s.exec_count
2400 };
2401 Row::new(alloc::vec![
2402 Value::Text(sql),
2403 Value::BigInt(i64::try_from(s.exec_count).unwrap_or(i64::MAX)),
2404 Value::BigInt(i64::try_from(s.total_us).unwrap_or(i64::MAX)),
2405 Value::BigInt(i64::try_from(mean).unwrap_or(i64::MAX)),
2406 Value::BigInt(i64::try_from(s.max_us).unwrap_or(i64::MAX)),
2407 Value::BigInt(i64::try_from(s.last_seen_us).unwrap_or(i64::MAX)),
2408 ])
2409 })
2410 .collect();
2411 QueryResult::Rows { columns, rows }
2412 }
2413
2414 #[must_use]
2419 pub const fn with_activity_provider(mut self, f: ActivityProvider) -> Self {
2420 self.activity_provider = Some(f);
2421 self
2422 }
2423
2424 #[must_use]
2426 pub const fn with_audit_providers(
2427 mut self,
2428 chain: AuditChainProvider,
2429 verify: AuditVerifier,
2430 ) -> Self {
2431 self.audit_chain_provider = Some(chain);
2432 self.audit_verifier = Some(verify);
2433 self
2434 }
2435
2436 #[must_use]
2441 pub const fn with_slow_query_log(mut self, threshold_us: u64, logger: SlowQueryLogger) -> Self {
2442 self.slow_query_threshold_us = Some(threshold_us);
2443 self.slow_query_logger = Some(logger);
2444 self
2445 }
2446
2447 pub fn set_plan_cache_max(&mut self, n: usize) {
2451 self.plan_cache.set_max_entries(n);
2452 }
2453
2454 fn exec_spg_stat_activity(&self) -> QueryResult {
2459 let columns = alloc::vec![
2460 ColumnSchema::new("pid", DataType::Int, false),
2461 ColumnSchema::new("user", DataType::Text, false),
2462 ColumnSchema::new("started_at_us", DataType::BigInt, false),
2463 ColumnSchema::new("current_sql", DataType::Text, false),
2464 ColumnSchema::new("wait_event", DataType::Text, false),
2465 ColumnSchema::new("elapsed_us", DataType::BigInt, false),
2466 ColumnSchema::new("in_transaction", DataType::Bool, false),
2467 ColumnSchema::new("application_name", DataType::Text, false),
2468 ];
2469 let rows: Vec<Row> = self
2470 .activity_provider
2471 .map(|f| f())
2472 .unwrap_or_default()
2473 .into_iter()
2474 .map(|r| {
2475 Row::new(alloc::vec![
2476 Value::Int(i32::try_from(r.pid).unwrap_or(i32::MAX)),
2477 Value::Text(r.user),
2478 Value::BigInt(r.started_at_us),
2479 Value::Text(r.current_sql),
2480 Value::Text(r.wait_event),
2481 Value::BigInt(r.elapsed_us),
2482 Value::Bool(r.in_transaction),
2483 Value::Text(r.application_name),
2484 ])
2485 })
2486 .collect();
2487 QueryResult::Rows { columns, rows }
2488 }
2489
2490 fn exec_spg_table_ddl(&self) -> QueryResult {
2494 let columns = alloc::vec![
2495 ColumnSchema::new("table_name", DataType::Text, false),
2496 ColumnSchema::new("ddl", DataType::Text, false),
2497 ];
2498 let rows: Vec<Row> = self
2499 .catalog
2500 .table_names()
2501 .into_iter()
2502 .filter(|n| !is_internal_table_name(n))
2503 .filter_map(|name| {
2504 let table = self.catalog.get(&name)?;
2505 let ddl = render_create_table(&name, &table.schema().columns);
2506 Some(Row::new(alloc::vec![Value::Text(name), Value::Text(ddl),]))
2507 })
2508 .collect();
2509 QueryResult::Rows { columns, rows }
2510 }
2511
2512 fn exec_spg_role_ddl(&self) -> QueryResult {
2516 let columns = alloc::vec![
2517 ColumnSchema::new("role_name", DataType::Text, false),
2518 ColumnSchema::new("ddl", DataType::Text, false),
2519 ];
2520 let rows: Vec<Row> = self
2521 .users
2522 .iter()
2523 .map(|(name, rec)| {
2524 let ddl = alloc::format!(
2525 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}'",
2526 rec.role.as_str(),
2527 );
2528 Row::new(alloc::vec![
2529 Value::Text(String::from(name)),
2530 Value::Text(ddl)
2531 ])
2532 })
2533 .collect();
2534 QueryResult::Rows { columns, rows }
2535 }
2536
2537 fn exec_spg_database_ddl(&self) -> QueryResult {
2543 let columns = alloc::vec![ColumnSchema::new("ddl", DataType::Text, false)];
2544 let mut out = String::new();
2545 for (name, rec) in self.users.iter() {
2546 out.push_str(&alloc::format!(
2547 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}';\n",
2548 rec.role.as_str(),
2549 ));
2550 }
2551 for name in self.catalog.table_names() {
2552 if is_internal_table_name(&name) {
2553 continue;
2554 }
2555 if let Some(table) = self.catalog.get(&name) {
2556 out.push_str(&render_create_table(&name, &table.schema().columns));
2557 out.push_str(";\n");
2558 }
2559 }
2560 QueryResult::Rows {
2561 columns,
2562 rows: alloc::vec![Row::new(alloc::vec![Value::Text(out)])],
2563 }
2564 }
2565
2566 fn exec_spg_audit_chain(&self) -> QueryResult {
2570 let columns = alloc::vec![
2571 ColumnSchema::new("seq", DataType::BigInt, false),
2572 ColumnSchema::new("ts_ms", DataType::BigInt, false),
2573 ColumnSchema::new("prev_hash", DataType::Text, false),
2574 ColumnSchema::new("entry_hash", DataType::Text, false),
2575 ColumnSchema::new("sql", DataType::Text, false),
2576 ];
2577 let rows: Vec<Row> = self
2578 .audit_chain_provider
2579 .map(|f| f())
2580 .unwrap_or_default()
2581 .into_iter()
2582 .map(|r| {
2583 Row::new(alloc::vec![
2584 Value::BigInt(r.seq),
2585 Value::BigInt(r.ts_ms),
2586 Value::Text(r.prev_hash_hex),
2587 Value::Text(r.entry_hash_hex),
2588 Value::Text(r.sql),
2589 ])
2590 })
2591 .collect();
2592 QueryResult::Rows { columns, rows }
2593 }
2594
2595 fn exec_spg_audit_verify(&self) -> QueryResult {
2601 let columns = alloc::vec![
2602 ColumnSchema::new("verified_count", DataType::BigInt, false),
2603 ColumnSchema::new("broken_at_seq", DataType::BigInt, false),
2604 ];
2605 let (verified, broken) = self.audit_verifier.map(|f| f()).unwrap_or((0, -1));
2606 let row = Row::new(alloc::vec![Value::BigInt(verified), Value::BigInt(broken),]);
2607 QueryResult::Rows {
2608 columns,
2609 rows: alloc::vec![row],
2610 }
2611 }
2612
2613 pub fn query_stats(&self) -> &query_stats::QueryStats {
2615 &self.query_stats
2616 }
2617
2618 pub fn query_stats_mut(&mut self) -> &mut query_stats::QueryStats {
2620 &mut self.query_stats
2621 }
2622
2623 pub const fn statistics(&self) -> &statistics::Statistics {
2627 &self.statistics
2628 }
2629
2630 pub fn tables_needing_analyze(&self) -> Vec<String> {
2643 const MIN_ROWS: u64 = 100;
2644 let mut out = Vec::new();
2645 for name in self.catalog.table_names() {
2646 if is_internal_table_name(&name) {
2647 continue;
2648 }
2649 let Some(table) = self.catalog.get(&name) else {
2650 continue;
2651 };
2652 let row_count = table.rows().len() as u64;
2653 let modified = self.statistics.modified_since_last_analyze(&name);
2654 let base = row_count.max(MIN_ROWS);
2659 let threshold = base.saturating_add(9) / 10;
2660 if modified >= threshold {
2661 out.push(name);
2662 }
2663 }
2664 out
2665 }
2666
2667 fn exec_analyze(&mut self, target: Option<&str>) -> Result<QueryResult, EngineError> {
2678 let names: Vec<String> = if let Some(name) = target {
2679 if self.catalog.get(name).is_none() {
2681 return Err(EngineError::Storage(StorageError::TableNotFound {
2682 name: name.to_string(),
2683 }));
2684 }
2685 alloc::vec![name.to_string()]
2686 } else {
2687 self.catalog
2688 .table_names()
2689 .into_iter()
2690 .filter(|n| !is_internal_table_name(n))
2691 .collect()
2692 };
2693 let mut analysed = 0usize;
2694 for table_name in &names {
2695 self.analyze_one_table(table_name)?;
2696 analysed += 1;
2697 }
2698 if analysed > 0 {
2704 self.statistics.bump_version();
2705 if target.is_some() {
2706 for t in &names {
2707 self.plan_cache.evict_referencing(t);
2708 }
2709 } else {
2710 self.plan_cache.clear();
2711 }
2712 }
2713 Ok(QueryResult::CommandOk {
2714 affected: analysed,
2715 modified_catalog: true,
2716 })
2717 }
2718
2719 fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
2732 let normalised = match value {
2733 spg_sql::ast::SetValue::String(s) => s,
2734 spg_sql::ast::SetValue::Ident(s) => s,
2735 spg_sql::ast::SetValue::Number(s) => s,
2736 spg_sql::ast::SetValue::Default => String::new(),
2737 };
2738 let key = name.to_ascii_lowercase();
2739 let value_off = matches!(
2750 normalised.to_ascii_lowercase().as_str(),
2751 "0" | "off" | "false"
2752 );
2753 let value_on = matches!(
2754 normalised.to_ascii_lowercase().as_str(),
2755 "1" | "on" | "true"
2756 );
2757 if key == "foreign_key_checks"
2758 || key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
2759 {
2760 if value_off || key == "session_replication_role" {
2761 self.foreign_key_checks = false;
2762 } else if value_on
2763 || (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
2764 {
2765 self.foreign_key_checks = true;
2766 let _ = self.drain_pending_foreign_keys();
2770 }
2771 }
2772 self.session_params.insert(key, normalised);
2773 }
2774
2775 fn drain_pending_foreign_keys(&mut self) -> Result<(), EngineError> {
2782 let pending = core::mem::take(&mut self.pending_foreign_keys);
2783 for (child, fk) in pending {
2784 let cols_snapshot = match self.active_catalog().get(&child) {
2788 Some(t) => t.schema().columns.clone(),
2789 None => continue,
2790 };
2791 let storage_fk =
2792 resolve_foreign_key(&child, &cols_snapshot, fk, self.active_catalog())?;
2793 let table = self
2794 .active_catalog_mut()
2795 .get_mut(&child)
2796 .expect("checked above");
2797 table.schema_mut().foreign_keys.push(storage_fk);
2798 }
2799 Ok(())
2800 }
2801
2802 #[must_use]
2806 pub fn session_param(&self, name: &str) -> Option<&str> {
2807 self.session_params
2808 .get(&name.to_ascii_lowercase())
2809 .map(String::as_str)
2810 }
2811
2812 fn ev_ctx<'a>(
2817 &'a self,
2818 columns: &'a [ColumnSchema],
2819 alias: Option<&'a str>,
2820 ) -> EvalContext<'a> {
2821 EvalContext::new(columns, alias)
2822 .with_default_text_search_config(self.session_param("default_text_search_config"))
2823 }
2824
2825 fn exec_compact_cold_segments(&mut self) -> Result<QueryResult, EngineError> {
2829 let target = COMPACTION_TARGET_DEFAULT_BYTES;
2830 let reports = self.compact_cold_segments_with_target(target)?;
2831 let columns = alloc::vec![
2832 ColumnSchema::new("table_name", DataType::Text, false),
2833 ColumnSchema::new("index_name", DataType::Text, false),
2834 ColumnSchema::new("sources_merged", DataType::BigInt, false),
2835 ColumnSchema::new("merged_segment_id", DataType::BigInt, false),
2836 ColumnSchema::new("merged_rows", DataType::BigInt, false),
2837 ColumnSchema::new("deleted_rows_pruned", DataType::BigInt, false),
2838 ColumnSchema::new("bytes_reclaimed_estimate", DataType::BigInt, false),
2839 ];
2840 let rows: Vec<Row> = reports
2841 .into_iter()
2842 .map(|(tname, iname, report)| {
2843 Row::new(alloc::vec![
2844 Value::Text(tname),
2845 Value::Text(iname),
2846 Value::BigInt(i64::try_from(report.sources.len()).unwrap_or(i64::MAX)),
2847 Value::BigInt(i64::from(report.merged_segment_id.unwrap_or(0))),
2848 Value::BigInt(i64::try_from(report.merged_rows).unwrap_or(i64::MAX)),
2849 Value::BigInt(i64::try_from(report.deleted_rows_pruned).unwrap_or(i64::MAX),),
2850 Value::BigInt(
2851 i64::try_from(report.bytes_reclaimed_estimate).unwrap_or(i64::MAX),
2852 ),
2853 ])
2854 })
2855 .collect();
2856 Ok(QueryResult::Rows { columns, rows })
2857 }
2858
2859 fn analyze_one_table(&mut self, table_name: &str) -> Result<(), EngineError> {
2864 let table = self.catalog.get(table_name).ok_or_else(|| {
2865 EngineError::Storage(StorageError::TableNotFound {
2866 name: table_name.to_string(),
2867 })
2868 })?;
2869 let schema = table.schema().clone();
2870 let row_count = table.rows().len();
2871 self.statistics.clear_table(table_name);
2876 for (col_pos, col_schema) in schema.columns.iter().enumerate() {
2877 if matches!(col_schema.ty, DataType::Vector { .. }) {
2880 continue;
2881 }
2882 let mut non_null_values: Vec<Value> = Vec::with_capacity(row_count);
2883 let mut nulls: u64 = 0;
2884 for row in table.rows() {
2885 match row.values.get(col_pos) {
2886 Some(Value::Null) | None => nulls += 1,
2887 Some(v) => non_null_values.push(v.clone()),
2888 }
2889 }
2890 non_null_values.sort_by(|a, b| sort_values_for_histogram(a, b));
2895 let non_null: Vec<String> = non_null_values.iter().map(canonical_value_repr).collect();
2896 let null_frac = if row_count == 0 {
2897 0.0
2898 } else {
2899 #[allow(clippy::cast_precision_loss)]
2900 let f = nulls as f32 / row_count as f32;
2901 f
2902 };
2903 let n_distinct = statistics::estimate_n_distinct(&non_null);
2904 let histogram_bounds = statistics::build_histogram(&non_null);
2905 self.statistics.set(
2906 table_name.to_string(),
2907 col_schema.name.clone(),
2908 statistics::ColumnStats {
2909 null_frac,
2910 n_distinct,
2911 histogram_bounds,
2912 },
2913 );
2914 }
2915 self.statistics.reset_modified(table_name);
2916 let cold_count = {
2922 let table = self
2923 .active_catalog()
2924 .get(table_name)
2925 .expect("table still present");
2926 table.count_cold_locators()
2927 };
2928 let table_mut = self
2929 .active_catalog_mut()
2930 .get_mut(table_name)
2931 .expect("table still present");
2932 table_mut.set_cold_row_count(cold_count);
2933 Ok(())
2934 }
2935
2936 fn exec_show_publications(&self) -> QueryResult {
2948 let columns = alloc::vec![
2949 ColumnSchema::new("name", DataType::Text, false),
2950 ColumnSchema::new("scope", DataType::Text, false),
2951 ColumnSchema::new("table_count", DataType::Int, true),
2952 ];
2953 let rows: Vec<Row> = self
2954 .publications
2955 .iter()
2956 .map(|(name, scope)| {
2957 let (scope_str, count_val) = match scope {
2958 spg_sql::ast::PublicationScope::AllTables => {
2959 ("FOR ALL TABLES".to_string(), Value::Null)
2960 }
2961 spg_sql::ast::PublicationScope::ForTables(ts) => (
2962 alloc::format!("FOR TABLE {}", ts.join(", ")),
2963 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2964 ),
2965 spg_sql::ast::PublicationScope::AllTablesExcept(ts) => (
2966 alloc::format!("FOR ALL TABLES EXCEPT {}", ts.join(", ")),
2967 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2968 ),
2969 };
2970 Row::new(alloc::vec![
2971 Value::Text(name.clone()),
2972 Value::Text(scope_str),
2973 count_val,
2974 ])
2975 })
2976 .collect();
2977 QueryResult::Rows { columns, rows }
2978 }
2979
2980 fn exec_show_users(&self) -> QueryResult {
2982 let columns = alloc::vec![
2983 ColumnSchema::new("name", DataType::Text, false),
2984 ColumnSchema::new("role", DataType::Text, false),
2985 ];
2986 let rows: Vec<Row> = self
2987 .users
2988 .iter()
2989 .map(|(name, rec)| {
2990 Row::new(alloc::vec![
2991 Value::Text(name.to_string()),
2992 Value::Text(rec.role.as_str().to_string()),
2993 ])
2994 })
2995 .collect();
2996 QueryResult::Rows { columns, rows }
2997 }
2998
2999 fn exec_create_user(&mut self, s: &CreateUserStatement) -> Result<QueryResult, EngineError> {
3000 if self.in_transaction() {
3001 return Err(EngineError::Unsupported(
3002 "CREATE USER is not allowed inside a transaction".into(),
3003 ));
3004 }
3005 let role = users::Role::parse(&s.role).ok_or_else(|| {
3006 EngineError::Unsupported(alloc::format!("invalid role: {:?}", s.role))
3007 })?;
3008 let salt = self.salt_fn.map_or_else(
3012 || {
3013 let mut s_bytes = [0u8; 16];
3014 let digest = spg_crypto::hash(s.name.as_bytes());
3015 s_bytes.copy_from_slice(&digest[..16]);
3016 s_bytes
3017 },
3018 |f| f(),
3019 );
3020 self.users
3021 .create(&s.name, &s.password, role, salt)
3022 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE USER: {e}")))?;
3023 Ok(QueryResult::CommandOk {
3024 affected: 1,
3025 modified_catalog: true,
3026 })
3027 }
3028
3029 fn exec_drop_user(&mut self, name: &str) -> Result<QueryResult, EngineError> {
3030 if self.in_transaction() {
3031 return Err(EngineError::Unsupported(
3032 "DROP USER is not allowed inside a transaction".into(),
3033 ));
3034 }
3035 self.users
3036 .drop(name)
3037 .map_err(|e| EngineError::Unsupported(alloc::format!("DROP USER: {e}")))?;
3038 Ok(QueryResult::CommandOk {
3039 affected: 1,
3040 modified_catalog: true,
3041 })
3042 }
3043
3044 fn exec_create_function(
3050 &mut self,
3051 s: spg_sql::ast::CreateFunctionStatement,
3052 ) -> Result<QueryResult, EngineError> {
3053 let args_repr = render_function_args(&s.args);
3054 let returns = match &s.returns {
3055 spg_sql::ast::FunctionReturn::Trigger => alloc::string::String::from("TRIGGER"),
3056 spg_sql::ast::FunctionReturn::Void => alloc::string::String::from("VOID"),
3057 spg_sql::ast::FunctionReturn::Type(t) => alloc::format!("{t}"),
3058 spg_sql::ast::FunctionReturn::Other(s) => s.clone(),
3059 };
3060 let body_text = match &s.body {
3061 spg_sql::ast::FunctionBody::PlPgSql(b) => alloc::format!("{b}"),
3062 spg_sql::ast::FunctionBody::Raw(s) => s.clone(),
3063 };
3064 let def = spg_storage::FunctionDef {
3065 name: s.name.clone(),
3066 args_repr,
3067 returns,
3068 language: s.language.clone(),
3069 body: body_text,
3070 };
3071 self.active_catalog_mut()
3072 .create_function(def, s.or_replace)
3073 .map_err(EngineError::Storage)?;
3074 Ok(QueryResult::CommandOk {
3075 affected: 0,
3076 modified_catalog: true,
3077 })
3078 }
3079
3080 fn exec_create_trigger(
3085 &mut self,
3086 s: spg_sql::ast::CreateTriggerStatement,
3087 ) -> Result<QueryResult, EngineError> {
3088 let timing = match s.timing {
3089 spg_sql::ast::TriggerTiming::Before => "BEFORE",
3090 spg_sql::ast::TriggerTiming::After => "AFTER",
3091 spg_sql::ast::TriggerTiming::InsteadOf => "INSTEAD OF",
3092 };
3093 let events: Vec<alloc::string::String> = s
3094 .events
3095 .iter()
3096 .map(|e| match e {
3097 spg_sql::ast::TriggerEvent::Insert => alloc::string::String::from("INSERT"),
3098 spg_sql::ast::TriggerEvent::Update => alloc::string::String::from("UPDATE"),
3099 spg_sql::ast::TriggerEvent::Delete => alloc::string::String::from("DELETE"),
3100 spg_sql::ast::TriggerEvent::Truncate => alloc::string::String::from("TRUNCATE"),
3101 })
3102 .collect();
3103 let for_each = match s.for_each {
3104 spg_sql::ast::TriggerForEach::Row => "ROW",
3105 spg_sql::ast::TriggerForEach::Statement => "STATEMENT",
3106 };
3107 let def = spg_storage::TriggerDef {
3108 name: s.name.clone(),
3109 table: s.table.clone(),
3110 timing: alloc::string::String::from(timing),
3111 events,
3112 for_each: alloc::string::String::from(for_each),
3113 function: s.function.clone(),
3114 update_columns: s.update_columns.clone(),
3115 enabled: true,
3118 };
3119 self.active_catalog_mut()
3120 .create_trigger(def, s.or_replace)
3121 .map_err(EngineError::Storage)?;
3122 Ok(QueryResult::CommandOk {
3123 affected: 0,
3124 modified_catalog: true,
3125 })
3126 }
3127
3128 fn exec_drop_trigger(
3129 &mut self,
3130 name: &str,
3131 table: &str,
3132 if_exists: bool,
3133 ) -> Result<QueryResult, EngineError> {
3134 let removed = self.active_catalog_mut().drop_trigger(name, table);
3135 if !removed && !if_exists {
3136 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3137 alloc::format!("trigger {name:?} on {table:?} does not exist"),
3138 )));
3139 }
3140 Ok(QueryResult::CommandOk {
3141 affected: usize::from(removed),
3142 modified_catalog: removed,
3143 })
3144 }
3145
3146 fn exec_drop_function(
3147 &mut self,
3148 name: &str,
3149 if_exists: bool,
3150 ) -> Result<QueryResult, EngineError> {
3151 let removed = self.active_catalog_mut().drop_function(name);
3152 if !removed && !if_exists {
3153 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3154 alloc::format!("function {name:?} does not exist"),
3155 )));
3156 }
3157 Ok(QueryResult::CommandOk {
3158 affected: usize::from(removed),
3159 modified_catalog: removed,
3160 })
3161 }
3162
3163 fn exec_create_sequence(
3167 &mut self,
3168 s: spg_sql::ast::CreateSequenceStatement,
3169 ) -> Result<QueryResult, EngineError> {
3170 use spg_sql::ast::{SeqBound, SequenceDataType as AstDt};
3171 use spg_storage::{SequenceDataType, SequenceDef};
3172 let dt = match s.data_type {
3173 None => SequenceDataType::BigInt,
3174 Some(AstDt::SmallInt) => SequenceDataType::SmallInt,
3175 Some(AstDt::Int) => SequenceDataType::Int,
3176 Some(AstDt::BigInt) => SequenceDataType::BigInt,
3177 };
3178 let increment = s.options.increment.unwrap_or(1);
3179 if increment == 0 {
3180 return Err(EngineError::Unsupported(
3181 "INCREMENT must not be zero".into(),
3182 ));
3183 }
3184 let (def_min, def_max) = dt.default_bounds(increment > 0);
3185 let min_value = match s.options.min_value {
3186 None | Some(SeqBound::NoBound) => def_min,
3187 Some(SeqBound::Value(n)) => n,
3188 };
3189 let max_value = match s.options.max_value {
3190 None | Some(SeqBound::NoBound) => def_max,
3191 Some(SeqBound::Value(n)) => n,
3192 };
3193 if min_value > max_value {
3194 return Err(EngineError::Unsupported(alloc::format!(
3195 "MINVALUE ({min_value}) must be <= MAXVALUE ({max_value})"
3196 )));
3197 }
3198 let start = s
3199 .options
3200 .start
3201 .unwrap_or(if increment > 0 { min_value } else { max_value });
3202 if start < min_value || start > max_value {
3203 return Err(EngineError::Unsupported(alloc::format!(
3204 "START WITH ({start}) is outside MINVALUE..MAXVALUE ({min_value}..{max_value})"
3205 )));
3206 }
3207 let cache = s.options.cache.unwrap_or(1);
3208 if cache < 1 {
3209 return Err(EngineError::Unsupported("CACHE must be >= 1".into()));
3210 }
3211 let cycle = s.options.cycle.unwrap_or(false);
3212 let owned_by = match s.options.owned_by {
3213 None | Some(spg_sql::ast::SequenceOwnedBy::None) => None,
3214 Some(spg_sql::ast::SequenceOwnedBy::Column { table, column }) => Some((table, column)),
3215 };
3216 let def = SequenceDef {
3217 name: s.name.clone(),
3218 data_type: dt,
3219 start,
3220 increment,
3221 min_value,
3222 max_value,
3223 cache,
3224 cycle,
3225 owned_by,
3226 last_value: start,
3227 is_called: false,
3228 };
3229 self.active_catalog_mut()
3230 .create_sequence(def, s.if_not_exists)
3231 .map_err(EngineError::Storage)?;
3232 Ok(QueryResult::CommandOk {
3233 affected: 0,
3234 modified_catalog: !self.in_transaction(),
3235 })
3236 }
3237
3238 fn exec_alter_sequence(
3241 &mut self,
3242 s: spg_sql::ast::AlterSequenceStatement,
3243 ) -> Result<QueryResult, EngineError> {
3244 use spg_sql::ast::SeqBound;
3245 let cat = self.active_catalog_mut();
3246 if !cat.sequences().contains_key(&s.name) {
3247 if s.if_exists {
3248 return Ok(QueryResult::CommandOk {
3249 affected: 0,
3250 modified_catalog: false,
3251 });
3252 }
3253 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3254 alloc::format!("sequence {:?} does not exist", s.name),
3255 )));
3256 }
3257 let min_value = match s.options.min_value {
3258 None => None,
3259 Some(SeqBound::NoBound) => None, Some(SeqBound::Value(n)) => Some(n),
3261 };
3262 let max_value = match s.options.max_value {
3263 None => None,
3264 Some(SeqBound::NoBound) => None,
3265 Some(SeqBound::Value(n)) => Some(n),
3266 };
3267 let owned_by = s.options.owned_by.map(|ob| match ob {
3268 spg_sql::ast::SequenceOwnedBy::None => None,
3269 spg_sql::ast::SequenceOwnedBy::Column { table, column } => Some((table, column)),
3270 });
3271 cat.alter_sequence(
3272 &s.name,
3273 s.options.increment,
3274 min_value,
3275 max_value,
3276 s.options.start,
3277 s.options.restart,
3278 s.options.cache,
3279 s.options.cycle,
3280 owned_by,
3281 )
3282 .map_err(EngineError::Storage)?;
3283 Ok(QueryResult::CommandOk {
3284 affected: 0,
3285 modified_catalog: !self.in_transaction(),
3286 })
3287 }
3288
3289 fn pre_resolve_sequence_calls_in_statement(
3294 &mut self,
3295 stmt: &mut Statement,
3296 ) -> Result<(), EngineError> {
3297 match stmt {
3298 Statement::Select(s) => self.pre_resolve_sequence_calls_in_select(s),
3299 Statement::Insert(s) => {
3300 for tuple in &mut s.rows {
3301 for cell in tuple.iter_mut() {
3302 self.resolve_sequence_calls_in_expr(cell)?;
3303 }
3304 }
3305 Ok(())
3306 }
3307 Statement::Update(s) => {
3308 for (_col, expr) in &mut s.assignments {
3309 self.resolve_sequence_calls_in_expr(expr)?;
3310 }
3311 if let Some(w) = &mut s.where_ {
3312 self.resolve_sequence_calls_in_expr(w)?;
3313 }
3314 Ok(())
3315 }
3316 Statement::Delete(s) => {
3317 if let Some(w) = &mut s.where_ {
3318 self.resolve_sequence_calls_in_expr(w)?;
3319 }
3320 Ok(())
3321 }
3322 _ => Ok(()),
3323 }
3324 }
3325
3326 fn pre_resolve_sequence_calls_in_select(
3327 &mut self,
3328 s: &mut spg_sql::ast::SelectStatement,
3329 ) -> Result<(), EngineError> {
3330 for item in &mut s.items {
3331 match item {
3332 spg_sql::ast::SelectItem::Expr { expr, .. } => {
3333 self.resolve_sequence_calls_in_expr(expr)?;
3334 }
3335 spg_sql::ast::SelectItem::Wildcard => {}
3336 }
3337 }
3338 if let Some(w) = &mut s.where_ {
3339 self.resolve_sequence_calls_in_expr(w)?;
3340 }
3341 Ok(())
3342 }
3343
3344 #[allow(clippy::too_many_lines)]
3352 fn resolve_sequence_calls_in_expr(&mut self, expr: &mut Expr) -> Result<(), EngineError> {
3353 match expr {
3354 Expr::Literal(_) | Expr::Column(_) | Expr::Placeholder(_) => Ok(()),
3355 Expr::FunctionCall { name, args } => {
3356 for a in args.iter_mut() {
3360 self.resolve_sequence_calls_in_expr(a)?;
3361 }
3362 let lc = name.to_ascii_lowercase();
3363 if lc == "nextval" || lc == "currval" || lc == "setval" {
3364 let v = self.eval_sequence_call(&lc, args)?;
3365 *expr = Expr::Literal(value_to_literal(v));
3366 }
3367 Ok(())
3368 }
3369 Expr::Binary { lhs, rhs, .. } => {
3370 self.resolve_sequence_calls_in_expr(lhs)?;
3371 self.resolve_sequence_calls_in_expr(rhs)
3372 }
3373 Expr::Unary { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3374 Expr::Cast { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3375 Expr::IsNull { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3376 Expr::Like { expr, pattern, .. } => {
3377 self.resolve_sequence_calls_in_expr(expr)?;
3378 self.resolve_sequence_calls_in_expr(pattern)
3379 }
3380 Expr::Extract { source, .. } => self.resolve_sequence_calls_in_expr(source),
3381 Expr::Array(items) => {
3382 for it in items.iter_mut() {
3383 self.resolve_sequence_calls_in_expr(it)?;
3384 }
3385 Ok(())
3386 }
3387 _ => Ok(()),
3392 }
3393 }
3394
3395 fn eval_sequence_call(&mut self, op: &str, args: &[Expr]) -> Result<Value, EngineError> {
3399 if args.is_empty() {
3400 return Err(EngineError::Unsupported(alloc::format!(
3401 "{op}() takes at least one argument"
3402 )));
3403 }
3404 let seq_name = match &args[0] {
3405 Expr::Literal(spg_sql::ast::Literal::String(s)) => {
3406 let trimmed = s
3412 .strip_prefix("public.")
3413 .or_else(|| s.strip_prefix("pg_catalog."))
3414 .unwrap_or(s);
3415 trimmed.to_string()
3416 }
3417 Expr::Cast { expr, .. } => {
3422 if let Expr::Literal(spg_sql::ast::Literal::String(s)) = expr.as_ref() {
3423 let trimmed = s
3424 .strip_prefix("public.")
3425 .or_else(|| s.strip_prefix("pg_catalog."))
3426 .unwrap_or(s);
3427 trimmed.to_string()
3428 } else {
3429 return Err(EngineError::Unsupported(alloc::format!(
3430 "{op}() first argument must be a literal sequence name"
3431 )));
3432 }
3433 }
3434 other => {
3435 return Err(EngineError::Unsupported(alloc::format!(
3436 "{op}() first argument must be a literal sequence name, got {other:?}"
3437 )));
3438 }
3439 };
3440 match op {
3441 "nextval" => {
3442 let v = self
3443 .active_catalog_mut()
3444 .sequence_next_value(&seq_name)
3445 .map_err(EngineError::Storage)?;
3446 Ok(Value::BigInt(v))
3447 }
3448 "currval" => {
3449 let v = self
3450 .active_catalog()
3451 .sequence_current_value(&seq_name)
3452 .map_err(EngineError::Storage)?;
3453 Ok(Value::BigInt(v))
3454 }
3455 "setval" => {
3456 if args.len() < 2 || args.len() > 3 {
3457 return Err(EngineError::Unsupported(alloc::format!(
3458 "setval() takes 2 or 3 arguments, got {}",
3459 args.len()
3460 )));
3461 }
3462 let value = match &args[1] {
3463 Expr::Literal(spg_sql::ast::Literal::Integer(n)) => *n,
3464 other => {
3465 return Err(EngineError::Unsupported(alloc::format!(
3466 "setval() value argument must be a literal integer, got {other:?}"
3467 )));
3468 }
3469 };
3470 let is_called = if args.len() == 3 {
3471 match &args[2] {
3472 Expr::Literal(spg_sql::ast::Literal::Bool(b)) => *b,
3473 other => {
3474 return Err(EngineError::Unsupported(alloc::format!(
3475 "setval() is_called argument must be a literal BOOL, got {other:?}"
3476 )));
3477 }
3478 }
3479 } else {
3480 true
3481 };
3482 let v = self
3483 .active_catalog_mut()
3484 .sequence_set_value(&seq_name, value, is_called)
3485 .map_err(EngineError::Storage)?;
3486 Ok(Value::BigInt(v))
3487 }
3488 other => Err(EngineError::Unsupported(alloc::format!(
3489 "unknown sequence op {other:?}"
3490 ))),
3491 }
3492 }
3493
3494 fn expand_views_in_select(
3503 &self,
3504 stmt: &SelectStatement,
3505 ) -> Result<Option<SelectStatement>, EngineError> {
3506 let cat = self.active_catalog();
3507 let mut referenced: Vec<String> = Vec::new();
3508 if let Some(from) = &stmt.from {
3509 collect_view_refs(&from.primary, cat, &mut referenced);
3510 for j in &from.joins {
3511 collect_view_refs(&j.table, cat, &mut referenced);
3512 }
3513 }
3514 referenced.retain(|n| !stmt.ctes.iter().any(|c| c.name == *n));
3517 if referenced.is_empty() {
3518 return Ok(None);
3519 }
3520 let mut new_ctes: Vec<spg_sql::ast::Cte> = Vec::with_capacity(referenced.len());
3521 for name in &referenced {
3522 let view = cat.views().get(name).ok_or_else(|| {
3523 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3524 "view {name:?} disappeared mid-expansion"
3525 )))
3526 })?;
3527 let parsed = spg_sql::parser::parse_statement(&view.body).map_err(|e| {
3528 EngineError::Unsupported(alloc::format!("view {name:?} body re-parse failed: {e}"))
3529 })?;
3530 let Statement::Select(body) = parsed else {
3531 return Err(EngineError::Unsupported(alloc::format!(
3532 "view {name:?} body is not a SELECT (catalog corruption)"
3533 )));
3534 };
3535 new_ctes.push(spg_sql::ast::Cte {
3536 name: name.clone(),
3537 body,
3538 recursive: false,
3539 column_overrides: view.columns.clone(),
3540 });
3541 }
3542 let mut out = stmt.clone();
3543 new_ctes.extend(out.ctes);
3545 out.ctes = new_ctes;
3546 Ok(Some(out))
3547 }
3548
3549 fn exec_create_view(
3553 &mut self,
3554 s: spg_sql::ast::CreateViewStatement,
3555 ) -> Result<QueryResult, EngineError> {
3556 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body));
3560 let def = spg_storage::ViewDef {
3561 name: s.name.clone(),
3562 columns: s.columns,
3563 body: body_repr,
3564 };
3565 self.active_catalog_mut()
3566 .create_view(def, s.or_replace, s.if_not_exists)
3567 .map_err(EngineError::Storage)?;
3568 Ok(QueryResult::CommandOk {
3569 affected: 0,
3570 modified_catalog: !self.in_transaction(),
3571 })
3572 }
3573
3574 fn exec_create_type(
3579 &mut self,
3580 s: spg_sql::ast::CreateTypeStatement,
3581 ) -> Result<QueryResult, EngineError> {
3582 let cat = self.active_catalog();
3585 if cat.get(&s.name).is_some() {
3586 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3587 alloc::format!("type {:?} would shadow an existing table", s.name),
3588 )));
3589 }
3590 if cat.sequences().contains_key(&s.name) {
3591 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3592 alloc::format!("type {:?} would shadow an existing sequence", s.name),
3593 )));
3594 }
3595 if cat.views().contains_key(&s.name) {
3596 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3597 alloc::format!("type {:?} would shadow an existing view", s.name),
3598 )));
3599 }
3600 let def = match s.kind {
3601 spg_sql::ast::TypeKind::Enum { labels } => {
3602 if labels.is_empty() {
3603 return Err(EngineError::Unsupported(
3604 "CREATE TYPE … AS ENUM requires at least one label".into(),
3605 ));
3606 }
3607 for i in 0..labels.len() {
3609 for j in (i + 1)..labels.len() {
3610 if labels[i] == labels[j] {
3611 return Err(EngineError::Unsupported(alloc::format!(
3612 "CREATE TYPE {:?}: duplicate ENUM label {:?}",
3613 s.name,
3614 labels[i]
3615 )));
3616 }
3617 }
3618 }
3619 spg_storage::EnumDef {
3620 name: s.name.clone(),
3621 labels,
3622 }
3623 }
3624 };
3625 self.active_catalog_mut()
3626 .create_enum_type(def)
3627 .map_err(EngineError::Storage)?;
3628 Ok(QueryResult::CommandOk {
3629 affected: 0,
3630 modified_catalog: !self.in_transaction(),
3631 })
3632 }
3633
3634 fn exec_create_domain(
3639 &mut self,
3640 s: spg_sql::ast::CreateDomainStatement,
3641 ) -> Result<QueryResult, EngineError> {
3642 let cat = self.active_catalog();
3643 if cat.domain_types().contains_key(&s.name) {
3644 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3645 alloc::format!("domain {:?} already exists", s.name),
3646 )));
3647 }
3648 if cat.get(&s.name).is_some()
3649 || cat.sequences().contains_key(&s.name)
3650 || cat.views().contains_key(&s.name)
3651 || cat.enum_types().contains_key(&s.name)
3652 {
3653 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3654 alloc::format!("domain {:?} would shadow an existing object", s.name),
3655 )));
3656 }
3657 let base_type = column_type_to_data_type(s.base_type);
3658 let default = s.default.as_ref().map(|e| alloc::format!("{e}"));
3659 let checks = s
3660 .checks
3661 .iter()
3662 .map(|e| alloc::format!("{e}"))
3663 .collect::<Vec<_>>();
3664 let def = spg_storage::DomainDef {
3665 name: s.name.clone(),
3666 base_type,
3667 nullable: !s.not_null,
3668 default,
3669 checks,
3670 };
3671 self.active_catalog_mut()
3672 .create_domain_type(def)
3673 .map_err(EngineError::Storage)?;
3674 Ok(QueryResult::CommandOk {
3675 affected: 0,
3676 modified_catalog: !self.in_transaction(),
3677 })
3678 }
3679
3680 fn exec_drop_domain(
3682 &mut self,
3683 names: &[String],
3684 if_exists: bool,
3685 ) -> Result<QueryResult, EngineError> {
3686 let mut removed = 0usize;
3687 for name in names {
3688 let was_present = self.active_catalog_mut().drop_domain_type(name);
3689 if was_present {
3690 removed += 1;
3691 } else if !if_exists {
3692 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3693 alloc::format!("domain {name:?} does not exist"),
3694 )));
3695 }
3696 }
3697 Ok(QueryResult::CommandOk {
3698 affected: removed,
3699 modified_catalog: removed > 0 && !self.in_transaction(),
3700 })
3701 }
3702
3703 fn exec_create_schema(
3709 &mut self,
3710 name: String,
3711 if_not_exists: bool,
3712 ) -> Result<QueryResult, EngineError> {
3713 self.active_catalog_mut()
3714 .create_schema(name, if_not_exists)
3715 .map_err(EngineError::Storage)?;
3716 Ok(QueryResult::CommandOk {
3717 affected: 0,
3718 modified_catalog: !self.in_transaction(),
3719 })
3720 }
3721
3722 fn exec_drop_schema(
3726 &mut self,
3727 names: &[String],
3728 if_exists: bool,
3729 ) -> Result<QueryResult, EngineError> {
3730 let mut removed = 0usize;
3731 for name in names {
3732 let was_present = self
3733 .active_catalog_mut()
3734 .drop_schema(name)
3735 .map_err(EngineError::Storage)?;
3736 if was_present {
3737 removed += 1;
3738 } else if !if_exists {
3739 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3740 alloc::format!("schema {name:?} does not exist"),
3741 )));
3742 }
3743 }
3744 Ok(QueryResult::CommandOk {
3745 affected: removed,
3746 modified_catalog: removed > 0 && !self.in_transaction(),
3747 })
3748 }
3749
3750 fn exec_drop_type(
3755 &mut self,
3756 names: &[String],
3757 if_exists: bool,
3758 ) -> Result<QueryResult, EngineError> {
3759 let mut removed = 0usize;
3760 for name in names {
3761 let was_present = self.active_catalog_mut().drop_enum_type(name);
3762 if was_present {
3763 removed += 1;
3764 } else if !if_exists {
3765 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3766 alloc::format!("type {name:?} does not exist"),
3767 )));
3768 }
3769 }
3770 Ok(QueryResult::CommandOk {
3771 affected: removed,
3772 modified_catalog: removed > 0 && !self.in_transaction(),
3773 })
3774 }
3775
3776 fn exec_create_materialized_view(
3781 &mut self,
3782 s: spg_sql::ast::CreateMaterializedViewStatement,
3783 ) -> Result<QueryResult, EngineError> {
3784 let cat = self.active_catalog();
3786 if cat.materialized_views().contains_key(&s.name) || cat.get(&s.name).is_some() {
3787 if s.if_not_exists {
3788 return Ok(QueryResult::CommandOk {
3789 affected: 0,
3790 modified_catalog: false,
3791 });
3792 }
3793 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3794 alloc::format!("materialized view {:?} already exists", s.name),
3795 )));
3796 }
3797 if cat.views().contains_key(&s.name) {
3798 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3799 alloc::format!(
3800 "materialized view {:?} would shadow an existing view",
3801 s.name
3802 ),
3803 )));
3804 }
3805 if cat.sequences().contains_key(&s.name) {
3806 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3807 alloc::format!(
3808 "materialized view {:?} would shadow an existing sequence",
3809 s.name
3810 ),
3811 )));
3812 }
3813 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body.clone()));
3815 let result = self.exec_select_cancel(&s.body, CancelToken::none())?;
3820 let (mut cols, rows) = match result {
3821 QueryResult::Rows { columns, rows } => (columns, rows),
3822 other => {
3823 return Err(EngineError::Unsupported(alloc::format!(
3824 "CREATE MATERIALIZED VIEW body did not return rows: {other:?}"
3825 )));
3826 }
3827 };
3828 if !s.columns.is_empty() {
3830 if s.columns.len() != cols.len() {
3831 return Err(EngineError::Unsupported(alloc::format!(
3832 "CREATE MATERIALIZED VIEW {:?}: column list has {} names but body returns {}",
3833 s.name,
3834 s.columns.len(),
3835 cols.len()
3836 )));
3837 }
3838 for (c, name) in cols.iter_mut().zip(s.columns.iter()) {
3839 c.name.clone_from(name);
3840 }
3841 }
3842 cols = infer_column_types(&cols, &rows);
3845 let schema = spg_storage::TableSchema::new(s.name.clone(), cols);
3846 let cat = self.active_catalog_mut();
3847 cat.create_table(schema).map_err(EngineError::Storage)?;
3848 if s.with_data {
3849 let table = cat
3850 .get_mut(&s.name)
3851 .expect("just-created materialized-view backing table must exist");
3852 for row in rows {
3853 table.insert(row).map_err(EngineError::Storage)?;
3854 }
3855 }
3856 cat.register_materialized_view(s.name.clone(), body_repr);
3857 Ok(QueryResult::CommandOk {
3858 affected: 0,
3859 modified_catalog: !self.in_transaction(),
3860 })
3861 }
3862
3863 fn exec_refresh_materialized_view(
3867 &mut self,
3868 name: &str,
3869 with_data: bool,
3870 ) -> Result<QueryResult, EngineError> {
3871 let source = self
3872 .active_catalog()
3873 .materialized_views()
3874 .get(name)
3875 .cloned()
3876 .ok_or_else(|| {
3877 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3878 "materialized view {name:?} does not exist"
3879 )))
3880 })?;
3881 {
3884 let cat = self.active_catalog_mut();
3885 let table = cat.get_mut(name).ok_or_else(|| {
3886 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3887 "materialized view {name:?} backing table missing"
3888 )))
3889 })?;
3890 table.truncate();
3891 }
3892 if !with_data {
3893 return Ok(QueryResult::CommandOk {
3894 affected: 0,
3895 modified_catalog: !self.in_transaction(),
3896 });
3897 }
3898 let parsed = spg_sql::parser::parse_statement(&source).map_err(|e| {
3899 EngineError::Unsupported(alloc::format!(
3900 "materialized view {name:?} body re-parse failed: {e}"
3901 ))
3902 })?;
3903 let Statement::Select(body) = parsed else {
3904 return Err(EngineError::Unsupported(alloc::format!(
3905 "materialized view {name:?} body is not a SELECT (catalog corruption)"
3906 )));
3907 };
3908 let rows = match self.exec_select_cancel(&body, CancelToken::none())? {
3909 QueryResult::Rows { rows, .. } => rows,
3910 other => {
3911 return Err(EngineError::Unsupported(alloc::format!(
3912 "REFRESH MATERIALIZED VIEW {name:?} body did not return rows: {other:?}"
3913 )));
3914 }
3915 };
3916 let cat = self.active_catalog_mut();
3917 let table = cat.get_mut(name).expect("backing table verified above");
3918 let affected = rows.len();
3919 for row in rows {
3920 table.insert(row).map_err(EngineError::Storage)?;
3921 }
3922 Ok(QueryResult::CommandOk {
3923 affected,
3924 modified_catalog: !self.in_transaction(),
3925 })
3926 }
3927
3928 fn exec_drop_materialized_view(
3931 &mut self,
3932 names: &[String],
3933 if_exists: bool,
3934 ) -> Result<QueryResult, EngineError> {
3935 let mut removed = 0usize;
3936 for name in names {
3937 let was_present = self
3938 .active_catalog_mut()
3939 .drop_materialized_view_source(name);
3940 if was_present {
3941 self.active_catalog_mut().drop_table(name);
3943 removed += 1;
3944 } else if !if_exists {
3945 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3946 alloc::format!("materialized view {name:?} does not exist"),
3947 )));
3948 }
3949 }
3950 Ok(QueryResult::CommandOk {
3951 affected: removed,
3952 modified_catalog: removed > 0 && !self.in_transaction(),
3953 })
3954 }
3955
3956 fn exec_drop_view(
3958 &mut self,
3959 names: &[String],
3960 if_exists: bool,
3961 ) -> Result<QueryResult, EngineError> {
3962 let mut removed = 0usize;
3963 for name in names {
3964 let was_present = self.active_catalog_mut().drop_view(name);
3965 if !was_present && !if_exists {
3966 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3967 alloc::format!("view {name:?} does not exist"),
3968 )));
3969 }
3970 if was_present {
3971 removed += 1;
3972 }
3973 }
3974 Ok(QueryResult::CommandOk {
3975 affected: removed,
3976 modified_catalog: removed > 0 && !self.in_transaction(),
3977 })
3978 }
3979
3980 fn exec_drop_sequence(
3982 &mut self,
3983 names: &[String],
3984 if_exists: bool,
3985 ) -> Result<QueryResult, EngineError> {
3986 let mut removed = 0usize;
3987 for name in names {
3988 let was_present = self.active_catalog_mut().drop_sequence(name);
3989 if !was_present && !if_exists {
3990 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3991 alloc::format!("sequence {name:?} does not exist"),
3992 )));
3993 }
3994 if was_present {
3995 removed += 1;
3996 }
3997 }
3998 Ok(QueryResult::CommandOk {
3999 affected: removed,
4000 modified_catalog: removed > 0 && !self.in_transaction(),
4001 })
4002 }
4003
4004 fn exec_update_cancel(
4011 &mut self,
4012 stmt: &spg_sql::ast::UpdateStatement,
4013 cancel: CancelToken<'_>,
4014 ) -> Result<QueryResult, EngineError> {
4015 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
4024 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
4025 let trigger_session_cfg: Option<String> = self
4026 .session_params
4027 .get("default_text_search_config")
4028 .cloned();
4029 if let Some(w) = &stmt.where_ {
4037 let schema_cols = self
4038 .active_catalog()
4039 .get(&stmt.table)
4040 .ok_or_else(|| {
4041 EngineError::Storage(StorageError::TableNotFound {
4042 name: stmt.table.clone(),
4043 })
4044 })?
4045 .schema()
4046 .columns
4047 .clone();
4048 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4049 && let Some(idx_name) = self
4050 .active_catalog()
4051 .get(&stmt.table)
4052 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4053 {
4054 let _ = self
4058 .active_catalog_mut()
4059 .promote_cold_row(&stmt.table, &idx_name, &key);
4060 }
4061 }
4062
4063 let ts_cfg: Option<String> = self
4066 .session_param("default_text_search_config")
4067 .map(String::from);
4068 let clock_for_on_update = self.clock;
4072 let table = self
4073 .active_catalog_mut()
4074 .get_mut(&stmt.table)
4075 .ok_or_else(|| {
4076 EngineError::Storage(StorageError::TableNotFound {
4077 name: stmt.table.clone(),
4078 })
4079 })?;
4080 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4081 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
4085 for (col, expr) in &stmt.assignments {
4086 let pos = schema_cols
4087 .iter()
4088 .position(|c| c.name == *col)
4089 .ok_or_else(|| {
4090 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
4091 })?;
4092 targets.push((pos, expr));
4093 }
4094 let mut on_update_overrides: Vec<(usize, String)> = Vec::new();
4102 for (i, col) in schema_cols.iter().enumerate() {
4103 if targets.iter().any(|(p, _)| *p == i) {
4104 continue;
4105 }
4106 if let Some(src) = &col.on_update_runtime {
4107 on_update_overrides.push((i, src.clone()));
4108 }
4109 }
4110 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4111 .with_default_text_search_config(ts_cfg.as_deref());
4112 let seek_positions: Option<Vec<usize>> = stmt
4127 .where_
4128 .as_ref()
4129 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4130 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
4131 let candidate_positions: Vec<usize> = match &seek_positions {
4132 Some(list) => list.clone(),
4133 None => (0..table.row_count()).collect(),
4134 };
4135 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4136 if loop_n.is_multiple_of(256) {
4140 cancel.check()?;
4141 }
4142 let Some(row) = table.rows().get(i) else {
4143 continue;
4144 };
4145 if let Some(w) = &stmt.where_ {
4146 let cond = eval::eval_expr(w, row, &ctx)?;
4147 if !matches!(cond, Value::Bool(true)) {
4148 continue;
4149 }
4150 }
4151 let mut new_vals = row.values.clone();
4152 for (pos, expr) in &targets {
4153 let v = eval::eval_expr(expr, row, &ctx)?;
4154 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
4155 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
4156 new_vals[*pos] = coerced;
4157 }
4158 for (pos, src) in &on_update_overrides {
4161 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4162 new_vals[*pos] = v;
4163 }
4164 planned.push((i, new_vals));
4165 }
4166 planned.sort_by_key(|(i, _)| *i);
4171 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4175 .iter()
4176 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4177 .collect();
4178 let self_fks = table.schema().foreign_keys.clone();
4179 let _ = table;
4184 if !self_fks.is_empty() {
4188 let new_rows: Vec<Vec<Value>> = planned
4189 .iter()
4190 .map(|(_pos, new_vals)| new_vals.clone())
4191 .collect();
4192 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4193 }
4194 {
4198 let new_rows: Vec<Vec<Value>> = planned
4199 .iter()
4200 .map(|(_pos, new_vals)| new_vals.clone())
4201 .collect();
4202 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4203 }
4204 let child_plan =
4208 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4209 for step in &child_plan {
4211 apply_fk_child_step(self.active_catalog_mut(), step)?;
4212 }
4213 let table = self
4215 .active_catalog_mut()
4216 .get_mut(&stmt.table)
4217 .ok_or_else(|| {
4218 EngineError::Storage(StorageError::TableNotFound {
4219 name: stmt.table.clone(),
4220 })
4221 })?;
4222 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4232 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4234 for (pos, new_vals) in &planned {
4235 let old_row = table.rows()[*pos].clone();
4236 let mut new_row = Row::new(new_vals.clone());
4237 let mut skip = false;
4238 for (fd, filter) in &before_update_triggers {
4239 if !filter.is_empty()
4244 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4245 {
4246 continue;
4247 }
4248 let (outcome, deferred) = triggers::fire_row_trigger(
4249 fd,
4250 Some(new_row.clone()),
4251 Some(&old_row),
4252 &stmt.table,
4253 &schema_cols,
4254 &[],
4255 trigger_session_cfg.as_deref(),
4256 false,
4257 )
4258 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4259 deferred_embedded.extend(deferred);
4260 match outcome {
4261 triggers::TriggerOutcome::Row(r) => new_row = r,
4262 triggers::TriggerOutcome::Skip => {
4263 skip = true;
4264 break;
4265 }
4266 }
4267 }
4268 if !skip {
4269 applied_after_before.push((*pos, new_row, old_row));
4270 }
4271 }
4272 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4275 applied_after_before
4276 .iter()
4277 .map(|(_pos, new_row, _old)| new_row.values.clone())
4278 .collect()
4279 } else {
4280 Vec::new()
4281 };
4282 let affected = applied_after_before.len();
4283 for (pos, new_row, old_row) in applied_after_before {
4287 table.update_row(pos, new_row.values.clone())?;
4288 for (fd, filter) in &after_update_triggers {
4289 if !filter.is_empty()
4290 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4291 {
4292 continue;
4293 }
4294 let (_outcome, deferred) = triggers::fire_row_trigger(
4295 fd,
4296 Some(new_row.clone()),
4297 Some(&old_row),
4298 &stmt.table,
4299 &schema_cols,
4300 &[],
4301 trigger_session_cfg.as_deref(),
4302 true,
4303 )
4304 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4305 deferred_embedded.extend(deferred);
4306 }
4307 }
4308 let _ = table;
4309 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4311 if !self.in_transaction() && affected > 0 {
4313 self.statistics
4314 .record_modifications(&stmt.table, affected as u64);
4315 }
4316 if let Some(items) = &stmt.returning {
4318 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4319 }
4320 Ok(QueryResult::CommandOk {
4321 affected,
4322 modified_catalog: !self.in_transaction(),
4323 })
4324 }
4325
4326 fn exec_merge_cancel(
4357 &mut self,
4358 stmt: &spg_sql::ast::MergeStatement,
4359 cancel: CancelToken<'_>,
4360 ) -> Result<QueryResult, EngineError> {
4361 let target_alias = stmt
4362 .target_alias
4363 .clone()
4364 .unwrap_or_else(|| stmt.target.clone());
4365 let source_alias = stmt
4366 .source_alias
4367 .clone()
4368 .unwrap_or_else(|| stmt.source.clone());
4369 let (target_cols, target_rows_snapshot) = {
4370 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4371 EngineError::Storage(StorageError::TableNotFound {
4372 name: stmt.target.clone(),
4373 })
4374 })?;
4375 (
4376 t.schema().columns.clone(),
4377 t.rows().iter().cloned().collect::<Vec<Row>>(),
4378 )
4379 };
4380 let (source_cols, source_rows) = {
4381 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4382 EngineError::Storage(StorageError::TableNotFound {
4383 name: stmt.source.clone(),
4384 })
4385 })?;
4386 (
4387 s.schema().columns.clone(),
4388 s.rows().iter().cloned().collect::<Vec<Row>>(),
4389 )
4390 };
4391 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4393 for col in &target_cols {
4394 combined_schema.push(ColumnSchema::new(
4395 alloc::format!("{target_alias}.{}", col.name),
4396 col.ty,
4397 col.nullable,
4398 ));
4399 }
4400 for col in &source_cols {
4401 combined_schema.push(ColumnSchema::new(
4402 alloc::format!("{source_alias}.{}", col.name),
4403 col.ty,
4404 col.nullable,
4405 ));
4406 }
4407 let combined_ctx = EvalContext::new(&combined_schema, None);
4408 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4412 for col in &target_cols {
4413 source_only_schema.push(ColumnSchema::new(
4414 alloc::format!("{target_alias}.{}", col.name),
4415 col.ty,
4416 col.nullable,
4417 ));
4418 }
4419 for col in &source_cols {
4420 source_only_schema.push(ColumnSchema::new(
4421 alloc::format!("{source_alias}.{}", col.name),
4422 col.ty,
4423 col.nullable,
4424 ));
4425 }
4426 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4427 let target_arity = target_cols.len();
4428 let source_arity = source_cols.len();
4429
4430 let mut delete_indices: Vec<usize> = Vec::new();
4433 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4434 let mut inserts: Vec<Vec<Value>> = Vec::new();
4435 let mut affected: usize = 0;
4436
4437 for (src_idx, src_row) in source_rows.iter().enumerate() {
4438 if src_idx.is_multiple_of(256) {
4439 cancel.check()?;
4440 }
4441 let mut matched_targets: Vec<usize> = Vec::new();
4443 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4444 let mut combined_vals = t_row.values.clone();
4445 combined_vals.extend(src_row.values.iter().cloned());
4446 let combined_row = Row::new(combined_vals);
4447 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4448 if matches!(cond, Value::Bool(true)) {
4449 matched_targets.push(t_idx);
4450 }
4451 }
4452 let is_matched = !matched_targets.is_empty();
4453 let fired_clause = stmt.clauses.iter().find(|c| {
4459 let kind_ok = match c.matched {
4460 spg_sql::ast::MergeMatched::Matched => is_matched,
4461 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4462 };
4463 if !kind_ok {
4464 return false;
4465 }
4466 let Some(cond_expr) = &c.condition else {
4467 return true;
4468 };
4469 let row = if is_matched {
4470 let t = &target_rows_snapshot[matched_targets[0]];
4471 let mut vals = t.values.clone();
4472 vals.extend(src_row.values.iter().cloned());
4473 Row::new(vals)
4474 } else {
4475 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4476 vals.extend(src_row.values.iter().cloned());
4477 Row::new(vals)
4478 };
4479 let ctx_ref = if is_matched {
4480 &combined_ctx
4481 } else {
4482 &source_only_ctx
4483 };
4484 matches!(
4485 eval::eval_expr(cond_expr, &row, ctx_ref),
4486 Ok(Value::Bool(true))
4487 )
4488 });
4489 let Some(clause) = fired_clause else { continue };
4490 match &clause.action {
4491 spg_sql::ast::MergeAction::DoNothing => {}
4492 spg_sql::ast::MergeAction::Delete => {
4493 for &t_idx in &matched_targets {
4494 if !delete_indices.contains(&t_idx) {
4495 delete_indices.push(t_idx);
4496 affected += 1;
4497 }
4498 }
4499 }
4500 spg_sql::ast::MergeAction::Update { assignments } => {
4501 let mut planned_sets: Vec<(usize, &Expr)> =
4503 Vec::with_capacity(assignments.len());
4504 for (col, expr) in assignments {
4505 let pos =
4506 target_cols
4507 .iter()
4508 .position(|c| c.name == *col)
4509 .ok_or_else(|| {
4510 EngineError::Eval(EvalError::ColumnNotFound {
4511 name: col.clone(),
4512 })
4513 })?;
4514 planned_sets.push((pos, expr));
4515 }
4516 for &t_idx in &matched_targets {
4517 let t_row = &target_rows_snapshot[t_idx];
4518 let mut new_values = t_row.values.clone();
4519 let mut combined_vals = t_row.values.clone();
4520 combined_vals.extend(src_row.values.iter().cloned());
4521 let combined_row = Row::new(combined_vals);
4522 for (pos, expr) in &planned_sets {
4523 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4524 let coerced = coerce_value(
4525 raw,
4526 target_cols[*pos].ty,
4527 &target_cols[*pos].name,
4528 *pos,
4529 )?;
4530 new_values[*pos] = coerced;
4531 }
4532 updates.push((t_idx, new_values));
4533 affected += 1;
4534 }
4535 }
4536 spg_sql::ast::MergeAction::Insert { columns, values } => {
4537 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4539 vals.extend(src_row.values.iter().cloned());
4540 let synth_row = Row::new(vals);
4541 let mut new_row_values: Vec<Value> =
4542 (0..target_arity).map(|_| Value::Null).collect();
4543 for (col, expr) in columns.iter().zip(values.iter()) {
4544 let pos =
4545 target_cols
4546 .iter()
4547 .position(|c| c.name == *col)
4548 .ok_or_else(|| {
4549 EngineError::Eval(EvalError::ColumnNotFound {
4550 name: col.clone(),
4551 })
4552 })?;
4553 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4554 let coerced =
4555 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4556 new_row_values[pos] = coerced;
4557 }
4558 inserts.push(new_row_values);
4559 affected += 1;
4560 }
4561 }
4562 }
4563 let _ = source_arity; let table = self
4567 .active_catalog_mut()
4568 .get_mut(&stmt.target)
4569 .ok_or_else(|| {
4570 EngineError::Storage(StorageError::TableNotFound {
4571 name: stmt.target.clone(),
4572 })
4573 })?;
4574 for (idx, new_vals) in &updates {
4578 table
4579 .update_row(*idx, new_vals.clone())
4580 .map_err(EngineError::Storage)?;
4581 }
4582 if !delete_indices.is_empty() {
4583 table.delete_rows(&delete_indices);
4584 }
4585 for vals in inserts {
4586 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4587 }
4588 Ok(QueryResult::CommandOk {
4589 affected,
4590 modified_catalog: affected > 0,
4591 })
4592 }
4593
4594 fn exec_delete_cancel(
4595 &mut self,
4596 stmt: &spg_sql::ast::DeleteStatement,
4597 cancel: CancelToken<'_>,
4598 ) -> Result<QueryResult, EngineError> {
4599 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4603 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4604 let trigger_session_cfg: Option<String> = self
4605 .session_params
4606 .get("default_text_search_config")
4607 .cloned();
4608 let mut cold_shadow_count: usize = 0;
4616 if let Some(w) = &stmt.where_ {
4617 let schema_cols = self
4618 .active_catalog()
4619 .get(&stmt.table)
4620 .ok_or_else(|| {
4621 EngineError::Storage(StorageError::TableNotFound {
4622 name: stmt.table.clone(),
4623 })
4624 })?
4625 .schema()
4626 .columns
4627 .clone();
4628 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4629 && let Some(idx_name) = self
4630 .active_catalog()
4631 .get(&stmt.table)
4632 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4633 {
4634 cold_shadow_count = self
4635 .active_catalog_mut()
4636 .shadow_cold_row(&stmt.table, &idx_name, &key)
4637 .unwrap_or(0);
4638 }
4639 }
4640
4641 let ts_cfg: Option<String> = self
4647 .session_param("default_text_search_config")
4648 .map(String::from);
4649 let table = self
4650 .active_catalog_mut()
4651 .get_mut(&stmt.table)
4652 .ok_or_else(|| {
4653 EngineError::Storage(StorageError::TableNotFound {
4654 name: stmt.table.clone(),
4655 })
4656 })?;
4657 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4658 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4659 .with_default_text_search_config(ts_cfg.as_deref());
4660 let mut positions: Vec<usize> = Vec::new();
4661 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4665 let seek_positions: Option<Vec<usize>> = stmt
4671 .where_
4672 .as_ref()
4673 .and_then(|w| try_index_seek_positions(w, &schema_cols, table, stmt.table.as_str()));
4674 let candidate_positions: Vec<usize> = match seek_positions {
4675 Some(mut list) => {
4676 list.sort_unstable();
4677 list
4678 }
4679 None => (0..table.row_count()).collect(),
4680 };
4681 for (loop_n, &i) in candidate_positions.iter().enumerate() {
4682 if loop_n.is_multiple_of(256) {
4683 cancel.check()?;
4684 }
4685 let Some(row) = table.rows().get(i) else {
4686 continue;
4687 };
4688 let keep = if let Some(w) = &stmt.where_ {
4689 let cond = eval::eval_expr(w, row, &ctx)?;
4690 !matches!(cond, Value::Bool(true))
4691 } else {
4692 false
4693 };
4694 if !keep {
4695 positions.push(i);
4696 to_delete_rows.push(row.values.clone());
4697 }
4698 }
4699 let _ = table;
4706 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4714 if !before_delete_triggers.is_empty() {
4715 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4716 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4717 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4718 let old_row = Row::new(old_vals.clone());
4719 let mut cancel_this = false;
4720 for fd in &before_delete_triggers {
4721 let (outcome, deferred) = triggers::fire_row_trigger(
4722 fd,
4723 None,
4724 Some(&old_row),
4725 &stmt.table,
4726 &schema_cols,
4727 &[],
4728 trigger_session_cfg.as_deref(),
4729 false,
4730 )
4731 .map_err(|e| {
4732 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4733 })?;
4734 deferred_embedded.extend(deferred);
4735 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4736 cancel_this = true;
4737 break;
4738 }
4739 }
4740 if !cancel_this {
4741 filtered_positions.push(*pos);
4742 filtered_old_rows.push(old_vals.clone());
4743 }
4744 }
4745 positions = filtered_positions;
4746 to_delete_rows = filtered_old_rows;
4747 }
4748 let cascade_plan = plan_fk_parent_deletions(
4749 self.active_catalog(),
4750 &stmt.table,
4751 &positions,
4752 &to_delete_rows,
4753 )?;
4754 for step in &cascade_plan {
4761 apply_fk_child_step(self.active_catalog_mut(), step)?;
4762 }
4763 let table = self
4765 .active_catalog_mut()
4766 .get_mut(&stmt.table)
4767 .ok_or_else(|| {
4768 EngineError::Storage(StorageError::TableNotFound {
4769 name: stmt.table.clone(),
4770 })
4771 })?;
4772 let affected = table.delete_rows(&positions) + cold_shadow_count;
4773 let _ = table;
4774 if !after_delete_triggers.is_empty() {
4779 for old_vals in &to_delete_rows {
4780 let old_row = Row::new(old_vals.clone());
4781 for fd in &after_delete_triggers {
4782 let (_outcome, deferred) = triggers::fire_row_trigger(
4783 fd,
4784 None,
4785 Some(&old_row),
4786 &stmt.table,
4787 &schema_cols,
4788 &[],
4789 trigger_session_cfg.as_deref(),
4790 true,
4791 )
4792 .map_err(|e| {
4793 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4794 })?;
4795 deferred_embedded.extend(deferred);
4796 }
4797 }
4798 }
4799 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4801 if !self.in_transaction() && affected > 0 {
4803 self.statistics
4804 .record_modifications(&stmt.table, affected as u64);
4805 }
4806 if let Some(items) = &stmt.returning {
4812 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4813 }
4814 Ok(QueryResult::CommandOk {
4815 affected,
4816 modified_catalog: !self.in_transaction(),
4817 })
4818 }
4819
4820 #[allow(clippy::format_push_string)]
4830 fn exec_explain(
4831 &self,
4832 e: &spg_sql::ast::ExplainStatement,
4833 cancel: CancelToken<'_>,
4834 ) -> Result<QueryResult, EngineError> {
4835 let mut lines = Vec::<String>::new();
4836 explain_select(&e.inner, self, 0, &mut lines);
4837 if e.suggest {
4838 let suggestions = build_index_suggestions(&e.inner, self);
4847 for s in suggestions {
4848 lines.push(s);
4849 }
4850 } else if e.analyze {
4851 let started = self.clock.map(|f| f());
4868 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4869 let elapsed_micros = match (self.clock, started) {
4870 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4871 _ => None,
4872 };
4873 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4874 rows.len()
4875 } else {
4876 0
4877 };
4878 annotate_explain_lines(&mut lines, row_count, self);
4879 let mut total = alloc::format!("Total: rows={row_count}");
4880 if let Some(us) = elapsed_micros {
4881 total.push_str(&alloc::format!(" elapsed={us}us"));
4882 }
4883 lines.push(total);
4884 }
4885 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
4886 let rows: Vec<Row> = lines
4887 .into_iter()
4888 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
4889 .collect();
4890 Ok(QueryResult::Rows { columns, rows })
4891 }
4892
4893 fn exec_show_tables(&self) -> QueryResult {
4894 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
4895 let rows: Vec<Row> = self
4896 .active_catalog()
4897 .table_names()
4898 .into_iter()
4899 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
4900 .collect();
4901 QueryResult::Rows { columns, rows }
4902 }
4903
4904 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
4909 let t = self.active_catalog().get(name).ok_or_else(|| {
4910 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4911 })?;
4912 let cols: Vec<String> = t
4913 .schema()
4914 .columns
4915 .iter()
4916 .map(|c| {
4917 let ty = render_data_type(c.ty);
4918 let nullable = if c.nullable { "" } else { " NOT NULL" };
4919 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
4920 })
4921 .collect();
4922 let mut body = cols.join(",\n");
4923 for uc in &t.schema().uniqueness_constraints {
4925 let col_names: Vec<String> = uc
4926 .columns
4927 .iter()
4928 .map(|&p| {
4929 t.schema().columns.get(p).map_or_else(
4930 || alloc::format!("col{p}"),
4931 |c| alloc::format!("`{}`", c.name),
4932 )
4933 })
4934 .collect();
4935 let kw = if uc.is_primary_key {
4936 "PRIMARY KEY"
4937 } else {
4938 "UNIQUE KEY"
4939 };
4940 body.push_str(",\n ");
4941 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
4942 }
4943 for fk in &t.schema().foreign_keys {
4945 let local: Vec<String> = fk
4946 .local_columns
4947 .iter()
4948 .map(|&p| {
4949 t.schema().columns.get(p).map_or_else(
4950 || alloc::format!("col{p}"),
4951 |c| alloc::format!("`{}`", c.name),
4952 )
4953 })
4954 .collect();
4955 let parent_cols: Vec<String> =
4956 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
4957 fk.parent_columns
4958 .iter()
4959 .map(|&p| {
4960 parent.schema().columns.get(p).map_or_else(
4961 || alloc::format!("col{p}"),
4962 |c| alloc::format!("`{}`", c.name),
4963 )
4964 })
4965 .collect()
4966 } else {
4967 fk.parent_columns
4968 .iter()
4969 .map(|p| alloc::format!("col{p}"))
4970 .collect()
4971 };
4972 body.push_str(",\n ");
4973 body.push_str(&alloc::format!(
4974 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
4975 local.join(", "),
4976 fk.parent_table,
4977 parent_cols.join(", ")
4978 ));
4979 }
4980 let ddl = alloc::format!(
4981 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
4982 name,
4983 body
4984 );
4985 let columns = alloc::vec![
4986 ColumnSchema::new("Table", DataType::Text, false),
4987 ColumnSchema::new("Create Table", DataType::Text, false),
4988 ];
4989 let rows = alloc::vec![Row::new(alloc::vec![
4990 Value::Text(name.into()),
4991 Value::Text(ddl),
4992 ])];
4993 Ok(QueryResult::Rows { columns, rows })
4994 }
4995
4996 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
5002 let t = self.active_catalog().get(name).ok_or_else(|| {
5003 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
5004 })?;
5005 let columns = alloc::vec![
5006 ColumnSchema::new("Table", DataType::Text, false),
5007 ColumnSchema::new("Non_unique", DataType::Int, false),
5008 ColumnSchema::new("Key_name", DataType::Text, false),
5009 ColumnSchema::new("Seq_in_index", DataType::Int, false),
5010 ColumnSchema::new("Column_name", DataType::Text, false),
5011 ColumnSchema::new("Null", DataType::Text, false),
5012 ColumnSchema::new("Index_type", DataType::Text, false),
5013 ];
5014 let mut rows: Vec<Row> = Vec::new();
5015 for idx in t.indices() {
5016 let col = t
5017 .schema()
5018 .columns
5019 .get(idx.column_position)
5020 .map_or("?".into(), |c| c.name.clone());
5021 let nullable = t
5022 .schema()
5023 .columns
5024 .get(idx.column_position)
5025 .map_or(true, |c| c.nullable);
5026 rows.push(Row::new(alloc::vec![
5027 Value::Text(name.into()),
5028 Value::Int(i32::from(!idx.is_unique)),
5029 Value::Text(idx.name.clone()),
5030 Value::Int(1),
5031 Value::Text(col),
5032 Value::Text(if nullable {
5033 "YES".into()
5034 } else {
5035 String::new()
5036 }),
5037 Value::Text("BTREE".into()),
5038 ]));
5039 }
5040 Ok(QueryResult::Rows { columns, rows })
5041 }
5042
5043 fn exec_show_status(&self) -> QueryResult {
5047 let columns = alloc::vec![
5048 ColumnSchema::new("Variable_name", DataType::Text, false),
5049 ColumnSchema::new("Value", DataType::Text, false),
5050 ];
5051 let pairs: &[(&str, &str)] = &[
5052 ("Uptime", "0"),
5053 ("Threads_connected", "1"),
5054 ("Threads_running", "1"),
5055 ("Questions", "0"),
5056 ("Slow_queries", "0"),
5057 ("Opened_tables", "0"),
5058 ("Innodb_buffer_pool_pages_total", "0"),
5059 ];
5060 let rows: Vec<Row> = pairs
5061 .iter()
5062 .map(|(k, v)| {
5063 Row::new(alloc::vec![
5064 Value::Text((*k).into()),
5065 Value::Text((*v).into())
5066 ])
5067 })
5068 .collect();
5069 QueryResult::Rows { columns, rows }
5070 }
5071
5072 fn exec_show_variables(&self) -> QueryResult {
5075 let columns = alloc::vec![
5076 ColumnSchema::new("Variable_name", DataType::Text, false),
5077 ColumnSchema::new("Value", DataType::Text, false),
5078 ];
5079 let mut rows: Vec<Row> = Vec::new();
5080 let canonical: &[(&str, &str)] = &[
5081 ("version", "8.0.35-spg"),
5082 ("version_comment", "SPG dual-stack engine"),
5083 ("character_set_server", "utf8mb4"),
5084 ("collation_server", "utf8mb4_0900_ai_ci"),
5085 ("max_allowed_packet", "67108864"),
5086 ("autocommit", "ON"),
5087 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
5088 ("time_zone", "SYSTEM"),
5089 ("transaction_isolation", "REPEATABLE-READ"),
5090 ];
5091 for &(k, v) in canonical {
5092 rows.push(Row::new(alloc::vec![
5093 Value::Text(k.into()),
5094 Value::Text(v.into()),
5095 ]));
5096 }
5097 for (k, v) in &self.session_params {
5099 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
5100 rows.push(Row::new(alloc::vec![
5101 Value::Text(k.clone()),
5102 Value::Text(v.clone()),
5103 ]));
5104 }
5105 }
5106 QueryResult::Rows { columns, rows }
5107 }
5108
5109 fn exec_show_processlist(&self) -> QueryResult {
5114 let columns = alloc::vec![
5115 ColumnSchema::new("Id", DataType::Int, false),
5116 ColumnSchema::new("User", DataType::Text, false),
5117 ColumnSchema::new("Host", DataType::Text, false),
5118 ColumnSchema::new("db", DataType::Text, true),
5119 ColumnSchema::new("Command", DataType::Text, false),
5120 ColumnSchema::new("Time", DataType::Int, false),
5121 ColumnSchema::new("State", DataType::Text, true),
5122 ColumnSchema::new("Info", DataType::Text, true),
5123 ];
5124 let rows = alloc::vec![Row::new(alloc::vec![
5125 Value::Int(1),
5126 Value::Text("postgres".into()),
5127 Value::Text("localhost".into()),
5128 Value::Text("postgres".into()),
5129 Value::Text("Query".into()),
5130 Value::Int(0),
5131 Value::Text("executing".into()),
5132 Value::Text("SHOW PROCESSLIST".into()),
5133 ])];
5134 QueryResult::Rows { columns, rows }
5135 }
5136
5137 fn exec_show_databases(&self) -> QueryResult {
5144 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
5145 let names = [
5146 "information_schema",
5147 "mysql",
5148 "performance_schema",
5149 "sys",
5150 "postgres",
5151 ];
5152 let rows: Vec<Row> = names
5153 .iter()
5154 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
5155 .collect();
5156 QueryResult::Rows { columns, rows }
5157 }
5158
5159 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
5162 let table =
5163 self.active_catalog()
5164 .get(table_name)
5165 .ok_or_else(|| StorageError::TableNotFound {
5166 name: table_name.into(),
5167 })?;
5168 let columns = alloc::vec![
5169 ColumnSchema::new("name", DataType::Text, false),
5170 ColumnSchema::new("type", DataType::Text, false),
5171 ColumnSchema::new("nullable", DataType::Bool, false),
5172 ];
5173 let rows: Vec<Row> = table
5174 .schema()
5175 .columns
5176 .iter()
5177 .map(|c| {
5178 Row::new(alloc::vec![
5179 Value::Text(c.name.clone()),
5180 Value::Text(alloc::format!("{}", c.ty)),
5181 Value::Bool(c.nullable),
5182 ])
5183 })
5184 .collect();
5185 Ok(QueryResult::Rows { columns, rows })
5186 }
5187
5188 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5189 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5190 if self.tx_catalogs.contains_key(&tx_id) {
5191 return Err(EngineError::TransactionAlreadyOpen);
5192 }
5193 self.tx_catalogs.insert(
5194 tx_id,
5195 TxState {
5196 catalog: self.catalog.clone(),
5197 savepoints: Vec::new(),
5198 },
5199 );
5200 Ok(QueryResult::CommandOk {
5201 affected: 0,
5202 modified_catalog: false,
5203 })
5204 }
5205
5206 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5207 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5208 let state = self
5209 .tx_catalogs
5210 .remove(&tx_id)
5211 .ok_or(EngineError::NoActiveTransaction)?;
5212 self.catalog = state.catalog;
5213 Ok(QueryResult::CommandOk {
5217 affected: 0,
5218 modified_catalog: true,
5219 })
5220 }
5221
5222 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5223 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5224 if self.tx_catalogs.remove(&tx_id).is_none() {
5225 return Err(EngineError::NoActiveTransaction);
5226 }
5227 Ok(QueryResult::CommandOk {
5229 affected: 0,
5230 modified_catalog: false,
5231 })
5232 }
5233
5234 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5235 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5236 let state = self
5237 .tx_catalogs
5238 .get_mut(&tx_id)
5239 .ok_or(EngineError::NoActiveTransaction)?;
5240 state.savepoints.retain(|(n, _)| n != &name);
5244 let snapshot = state.catalog.clone();
5245 state.savepoints.push((name, snapshot));
5246 Ok(QueryResult::CommandOk {
5247 affected: 0,
5248 modified_catalog: false,
5249 })
5250 }
5251
5252 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5253 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5254 let state = self
5255 .tx_catalogs
5256 .get_mut(&tx_id)
5257 .ok_or(EngineError::NoActiveTransaction)?;
5258 let pos = state
5259 .savepoints
5260 .iter()
5261 .rposition(|(n, _)| n == name)
5262 .ok_or_else(|| {
5263 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5264 })?;
5265 let snapshot = state.savepoints[pos].1.clone();
5269 state.savepoints.truncate(pos + 1);
5270 state.catalog = snapshot;
5271 Ok(QueryResult::CommandOk {
5272 affected: 0,
5273 modified_catalog: false,
5274 })
5275 }
5276
5277 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5278 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5279 let state = self
5280 .tx_catalogs
5281 .get_mut(&tx_id)
5282 .ok_or(EngineError::NoActiveTransaction)?;
5283 let pos = state
5284 .savepoints
5285 .iter()
5286 .rposition(|(n, _)| n == name)
5287 .ok_or_else(|| {
5288 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5289 })?;
5290 state.savepoints.truncate(pos);
5293 Ok(QueryResult::CommandOk {
5294 affected: 0,
5295 modified_catalog: false,
5296 })
5297 }
5298
5299 fn exec_alter_table(
5310 &mut self,
5311 s: spg_sql::ast::AlterTableStatement,
5312 ) -> Result<QueryResult, EngineError> {
5313 let table_name = s.name.clone();
5318 for target in s.targets {
5319 self.exec_alter_table_subaction(&table_name, target)?;
5320 }
5321 Ok(QueryResult::CommandOk {
5322 affected: 0,
5323 modified_catalog: !self.in_transaction(),
5324 })
5325 }
5326
5327 fn exec_alter_table_subaction(
5328 &mut self,
5329 table_name_outer: &str,
5330 target: spg_sql::ast::AlterTableTarget,
5331 ) -> Result<(), EngineError> {
5332 struct S<'a> {
5335 name: &'a str,
5336 }
5337 let s = S {
5338 name: table_name_outer,
5339 };
5340 match target {
5341 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5342 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5343 EngineError::Storage(StorageError::TableNotFound {
5344 name: s.name.into(),
5345 })
5346 })?;
5347 table.schema_mut().hot_tier_bytes = Some(n);
5348 }
5349 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5350 let cols_snapshot = self
5355 .active_catalog()
5356 .get(s.name)
5357 .ok_or_else(|| {
5358 EngineError::Storage(StorageError::TableNotFound {
5359 name: s.name.into(),
5360 })
5361 })?
5362 .schema()
5363 .columns
5364 .clone();
5365 let storage_fk =
5366 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5367 let existing_rows: Vec<Vec<Value>> = self
5370 .active_catalog()
5371 .get(s.name)
5372 .expect("checked above")
5373 .rows()
5374 .iter()
5375 .map(|r| r.values.clone())
5376 .collect();
5377 enforce_fk_inserts(
5378 self.active_catalog(),
5379 s.name,
5380 core::slice::from_ref(&storage_fk),
5381 &existing_rows,
5382 )?;
5383 let table = self
5385 .active_catalog_mut()
5386 .get_mut(s.name)
5387 .expect("checked above");
5388 if let Some(name) = &storage_fk.name
5389 && table
5390 .schema()
5391 .foreign_keys
5392 .iter()
5393 .any(|f| f.name.as_ref() == Some(name))
5394 {
5395 return Err(EngineError::Unsupported(alloc::format!(
5396 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5397 )));
5398 }
5399 table.schema_mut().foreign_keys.push(storage_fk);
5400 }
5401 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5402 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5403 EngineError::Storage(StorageError::TableNotFound {
5404 name: s.name.into(),
5405 })
5406 })?;
5407 let fks = &mut table.schema_mut().foreign_keys;
5408 let before = fks.len();
5409 fks.retain(|f| f.name.as_ref() != Some(&name));
5410 if fks.len() == before && !if_exists {
5411 return Err(EngineError::Unsupported(alloc::format!(
5412 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5413 s.name
5414 )));
5415 }
5416 }
5418 spg_sql::ast::AlterTableTarget::AddColumn {
5419 column,
5420 if_not_exists,
5421 } => {
5422 let clock = self.clock;
5427 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5428 EngineError::Storage(StorageError::TableNotFound {
5429 name: s.name.into(),
5430 })
5431 })?;
5432 if table
5433 .schema()
5434 .columns
5435 .iter()
5436 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5437 {
5438 if if_not_exists {
5439 return Ok(());
5440 }
5441 return Err(EngineError::Unsupported(alloc::format!(
5442 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5443 column.name,
5444 s.name
5445 )));
5446 }
5447 let col_name = column.name.clone();
5448 let nullable = column.nullable;
5449 let has_default = column.default.is_some() || column.auto_increment;
5450 let col_schema = column_def_to_schema(column)?;
5451 let row_count = table.row_count();
5452 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5459 resolve_column_default_free(&col_schema, clock)?
5460 } else if nullable || row_count == 0 {
5461 Value::Null
5462 } else {
5463 return Err(EngineError::Unsupported(alloc::format!(
5464 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5465 when the table has existing rows"
5466 )));
5467 };
5468 table.add_column(col_schema, fill_value);
5469 }
5470 spg_sql::ast::AlterTableTarget::AlterColumnType {
5471 column,
5472 new_type,
5473 using,
5474 } => {
5475 let new_data_type = column_type_to_data_type(new_type);
5481 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5482 EngineError::Storage(StorageError::TableNotFound {
5483 name: s.name.into(),
5484 })
5485 })?;
5486 let col_pos = table
5487 .schema()
5488 .columns
5489 .iter()
5490 .position(|c| c.name.eq_ignore_ascii_case(&column))
5491 .ok_or_else(|| {
5492 EngineError::Unsupported(alloc::format!(
5493 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5494 s.name
5495 ))
5496 })?;
5497 let schema_cols = table.schema().columns.clone();
5498 let ctx = eval::EvalContext::new(&schema_cols, None);
5499 let mut new_values: alloc::vec::Vec<Value> =
5500 alloc::vec::Vec::with_capacity(table.row_count());
5501 for row in table.rows().iter() {
5502 let raw = match &using {
5503 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5504 EngineError::Unsupported(alloc::format!(
5505 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5506 ))
5507 })?,
5508 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5509 };
5510 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5511 new_values.push(coerced);
5512 }
5513 table.schema_mut().columns[col_pos].ty = new_data_type;
5514 for (i, v) in new_values.into_iter().enumerate() {
5515 let mut row_values = table
5516 .rows()
5517 .get(i)
5518 .expect("bounds-checked above")
5519 .values
5520 .clone();
5521 row_values[col_pos] = v;
5522 table.update_row(i, row_values)?;
5523 }
5524 }
5525 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5526 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5532 EngineError::Storage(StorageError::TableNotFound {
5533 name: s.name.into(),
5534 })
5535 })?;
5536 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5537 match tc {
5538 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5539 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5540 let positions: Vec<usize> = columns
5541 .iter()
5542 .map(|c| {
5543 table
5544 .schema()
5545 .columns
5546 .iter()
5547 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5548 .ok_or_else(|| {
5549 EngineError::Unsupported(alloc::format!(
5550 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5551 s.name
5552 ))
5553 })
5554 })
5555 .collect::<Result<Vec<_>, _>>()?;
5556 let already = table
5560 .schema()
5561 .uniqueness_constraints
5562 .iter()
5563 .any(|u| u.columns == positions);
5564 if !already {
5565 table.schema_mut().uniqueness_constraints.push(
5566 spg_storage::UniquenessConstraint {
5567 is_primary_key: is_pk,
5568 columns: positions.clone(),
5569 nulls_not_distinct: false,
5570 },
5571 );
5572 if is_pk {
5574 for p in &positions {
5575 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5576 c.nullable = false;
5577 }
5578 }
5579 }
5580 let leading = &columns[0];
5583 let already_idx = table.indices().iter().any(|idx| {
5584 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5585 && table.schema().columns[idx.column_position].name == *leading
5586 });
5587 if !already_idx {
5588 let suffix = if is_pk { "pkey" } else { "key" };
5589 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5590 let _ = table.add_index(idx_name, leading);
5591 }
5592 }
5593 }
5594 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5595 table.schema_mut().checks.push(alloc::format!("{expr}"));
5596 }
5597 spg_sql::ast::TableConstraint::Index { name, columns } => {
5598 let leading = &columns[0];
5604 let already_idx = table.indices().iter().any(|idx| {
5605 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5606 && table.schema().columns[idx.column_position].name == *leading
5607 });
5608 if !already_idx {
5609 let idx_name = name
5610 .clone()
5611 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5612 let _ = table.add_index(idx_name, leading);
5613 }
5614 }
5615 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5616 for (k, col) in columns.iter().enumerate() {
5624 let already_idx = table.indices().iter().any(|idx| {
5625 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5626 && table.schema().columns[idx.column_position].name == *col
5627 });
5628 if already_idx {
5629 continue;
5630 }
5631 let idx_name = match (&name, columns.len(), k) {
5632 (Some(n), 1, _) => n.clone(),
5633 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5634 (None, _, _) => {
5635 alloc::format!("{}_{col}_ftidx", s.name)
5636 }
5637 };
5638 let _ = table.add_gin_fulltext_index(idx_name, col);
5639 }
5640 }
5641 }
5642 }
5643 spg_sql::ast::AlterTableTarget::DropColumn {
5644 column,
5645 if_exists,
5646 cascade,
5647 } => {
5648 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5655 EngineError::Storage(StorageError::TableNotFound {
5656 name: s.name.into(),
5657 })
5658 })?;
5659 let col_pos = match table
5660 .schema()
5661 .columns
5662 .iter()
5663 .position(|c| c.name.eq_ignore_ascii_case(&column))
5664 {
5665 Some(p) => p,
5666 None => {
5667 if if_exists {
5668 return Ok(());
5669 }
5670 return Err(EngineError::Unsupported(alloc::format!(
5671 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5672 s.name
5673 )));
5674 }
5675 };
5676 let dependent_fks: Vec<usize> = table
5679 .schema()
5680 .foreign_keys
5681 .iter()
5682 .enumerate()
5683 .filter_map(|(i, fk)| {
5684 if fk.local_columns.contains(&col_pos) {
5685 Some(i)
5686 } else {
5687 None
5688 }
5689 })
5690 .collect();
5691 if !dependent_fks.is_empty() && !cascade {
5692 return Err(EngineError::Unsupported(alloc::format!(
5693 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5694 use DROP COLUMN ... CASCADE to remove them"
5695 )));
5696 }
5697 if cascade {
5699 let mut sorted = dependent_fks.clone();
5701 sorted.sort();
5702 sorted.reverse();
5703 let fks = &mut table.schema_mut().foreign_keys;
5704 for i in sorted {
5705 fks.remove(i);
5706 }
5707 }
5708 table.drop_column(col_pos);
5711 }
5712 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5713 let table_name = s.name.to_string();
5721 let trigs = self.active_catalog_mut().triggers_mut();
5722 let mut touched = false;
5723 for t in trigs.iter_mut() {
5724 if !t.table.eq_ignore_ascii_case(&table_name) {
5725 continue;
5726 }
5727 match &which {
5728 spg_sql::ast::TriggerSelector::All => {
5729 t.enabled = enabled;
5730 touched = true;
5731 }
5732 spg_sql::ast::TriggerSelector::Named(name) => {
5733 if t.name.eq_ignore_ascii_case(name) {
5734 t.enabled = enabled;
5735 touched = true;
5736 }
5737 }
5738 }
5739 }
5740 if !touched {
5746 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5747 return Err(EngineError::Unsupported(alloc::format!(
5748 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5749 if enabled { "ENABLE" } else { "DISABLE" },
5750 )));
5751 }
5752 }
5753 }
5754 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5755 let old = s.name.to_string();
5762 self.active_catalog_mut()
5763 .rename_table(&old, &new)
5764 .map_err(EngineError::Storage)?;
5765 }
5766 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5767 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5781 EngineError::Storage(StorageError::TableNotFound {
5782 name: s.name.into(),
5783 })
5784 })?;
5785 let col_pos = table
5786 .schema()
5787 .columns
5788 .iter()
5789 .position(|c| c.name.eq_ignore_ascii_case(&old))
5790 .ok_or_else(|| {
5791 EngineError::Unsupported(alloc::format!(
5792 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5793 s.name
5794 ))
5795 })?;
5796 if table
5798 .schema()
5799 .columns
5800 .iter()
5801 .enumerate()
5802 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5803 {
5804 return Err(EngineError::Unsupported(alloc::format!(
5805 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5806 s.name
5807 )));
5808 }
5809 if old.eq_ignore_ascii_case(&new) {
5813 return Ok(());
5814 }
5815 table.rename_column(col_pos, &new);
5816 let n_cols = table.schema().columns.len();
5822 for i in 0..n_cols {
5823 let rt = table.schema().columns[i].runtime_default.clone();
5824 if let Some(src) = rt {
5825 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5826 table.schema_mut().columns[i].runtime_default = Some(rewritten);
5827 }
5828 }
5829 let checks = table.schema().checks.clone();
5831 let mut new_checks = Vec::with_capacity(checks.len());
5832 for chk in checks {
5833 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
5834 }
5835 table.schema_mut().checks = new_checks;
5836 let n_idx = table.indices().len();
5838 for i in 0..n_idx {
5839 let pred = table.indices()[i].partial_predicate.clone();
5840 if let Some(src) = pred {
5841 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5842 table.set_partial_predicate(i, Some(rewritten));
5846 }
5847 }
5848 let table_name = s.name.to_string();
5851 for trig in self.active_catalog_mut().triggers_mut() {
5852 if !trig.table.eq_ignore_ascii_case(&table_name) {
5853 continue;
5854 }
5855 for c in &mut trig.update_columns {
5856 if c.eq_ignore_ascii_case(&old) {
5857 *c = new.clone();
5858 }
5859 }
5860 }
5861 }
5862 }
5863 Ok(())
5864 }
5865
5866 fn exec_alter_index(
5867 &mut self,
5868 stmt: spg_sql::ast::AlterIndexStatement,
5869 ) -> Result<QueryResult, EngineError> {
5870 let spg_sql::ast::AlterIndexStatement {
5874 name: idx_name,
5875 target,
5876 } = stmt;
5877 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
5881 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
5882 return match renamed {
5883 Ok(()) => Ok(QueryResult::CommandOk {
5884 affected: 0,
5885 modified_catalog: !self.in_transaction(),
5886 }),
5887 Err(StorageError::IndexNotFound { .. }) if if_exists => {
5888 Ok(QueryResult::CommandOk {
5889 affected: 0,
5890 modified_catalog: false,
5891 })
5892 }
5893 Err(e) => Err(EngineError::Storage(e)),
5894 };
5895 }
5896 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
5897 unreachable!("Rename branch returned above");
5898 };
5899 let target = encoding.map(|e| match e {
5900 SqlVecEncoding::F32 => VecEncoding::F32,
5901 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
5902 SqlVecEncoding::F16 => VecEncoding::F16,
5903 });
5904 let table_name = {
5909 let cat = self.active_catalog();
5910 let mut found: Option<String> = None;
5911 for tname in cat.table_names() {
5912 if let Some(t) = cat.get(&tname)
5913 && t.indices().iter().any(|i| i.name == idx_name)
5914 {
5915 found = Some(tname);
5916 break;
5917 }
5918 }
5919 found.ok_or_else(|| {
5920 EngineError::Storage(StorageError::IndexNotFound {
5921 name: idx_name.clone(),
5922 })
5923 })?
5924 };
5925 let table = self
5926 .active_catalog_mut()
5927 .get_mut(&table_name)
5928 .expect("table found above");
5929 table.rebuild_nsw_index(&idx_name, target)?;
5930 self.plan_cache.evict_referencing(&table_name);
5933 Ok(QueryResult::CommandOk {
5934 affected: 0,
5935 modified_catalog: !self.in_transaction(),
5936 })
5937 }
5938
5939 fn exec_create_index(
5940 &mut self,
5941 stmt: CreateIndexStatement,
5942 ) -> Result<QueryResult, EngineError> {
5943 let table = self
5944 .active_catalog_mut()
5945 .get_mut(&stmt.table)
5946 .ok_or_else(|| {
5947 EngineError::Storage(StorageError::TableNotFound {
5948 name: stmt.table.clone(),
5949 })
5950 })?;
5951 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
5953 return Ok(QueryResult::CommandOk {
5954 affected: 0,
5955 modified_catalog: false,
5956 });
5957 }
5958 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
5965 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
5969 Vec::new()
5970 } else {
5971 let schema = table.schema();
5972 stmt.included_columns
5973 .iter()
5974 .map(|c| {
5975 schema.column_position(c).ok_or_else(|| {
5976 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
5977 })
5978 })
5979 .collect::<Result<Vec<_>, _>>()?
5980 };
5981 match stmt.method {
5982 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
5983 IndexMethod::Hnsw => {
5984 if !included_positions.is_empty() {
5985 return Err(EngineError::Unsupported(
5986 "INCLUDE columns are not supported on HNSW indexes".into(),
5987 ));
5988 }
5989 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
5990 }
5991 IndexMethod::Brin => {
5993 if !included_positions.is_empty() {
5994 return Err(EngineError::Unsupported(
5995 "INCLUDE columns are not supported on BRIN indexes".into(),
5996 ));
5997 }
5998 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
5999 }
6000 IndexMethod::Gin => {
6008 if !included_positions.is_empty() {
6009 return Err(EngineError::Unsupported(
6010 "INCLUDE columns are not supported on GIN indexes".into(),
6011 ));
6012 }
6013 let col_pos = table
6014 .schema()
6015 .column_position(&stmt.column)
6016 .ok_or_else(|| {
6017 EngineError::Storage(StorageError::ColumnNotFound {
6018 column: stmt.column.clone(),
6019 })
6020 })?;
6021 let col_ty = table.schema().columns[col_pos].ty;
6022 let is_trgm = stmt
6028 .opclass
6029 .as_deref()
6030 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
6031 if is_trgm
6032 && matches!(
6033 col_ty,
6034 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
6035 )
6036 {
6037 table
6038 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
6039 .map_err(EngineError::Storage)?;
6040 } else if col_ty == spg_storage::DataType::TsVector {
6041 table
6042 .add_gin_index(stmt.name.clone(), &stmt.column)
6043 .map_err(EngineError::Storage)?;
6044 } else {
6045 table.add_index(stmt.name.clone(), &stmt.column)?;
6051 }
6052 }
6053 }
6054 if !included_positions.is_empty()
6055 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
6056 {
6057 idx.included_columns = included_positions;
6058 }
6059 if let Some(pred_expr) = &stmt.partial_predicate {
6067 let canonical = pred_expr.to_string();
6068 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6080 idx.partial_predicate = Some(canonical);
6081 }
6082 }
6083 if let Some(key_expr) = &stmt.expression {
6091 if matches!(
6092 stmt.method,
6093 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
6094 ) {
6095 return Err(EngineError::Unsupported(
6096 "Expression keys are not supported on HNSW or BRIN indexes".into(),
6097 ));
6098 }
6099 let canonical = key_expr.to_string();
6100 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6101 idx.expression = Some(canonical);
6102 }
6103 }
6104 if stmt.is_unique {
6113 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
6114 for col_name in &stmt.extra_columns {
6115 let pos = table
6116 .schema()
6117 .columns
6118 .iter()
6119 .position(|c| c.name.eq_ignore_ascii_case(col_name))
6120 .ok_or_else(|| {
6121 EngineError::Unsupported(alloc::format!(
6122 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
6123 stmt.name,
6124 stmt.table
6125 ))
6126 })?;
6127 extra_positions.push(pos);
6128 }
6129 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6130 idx.is_unique = true;
6131 idx.extra_column_positions = extra_positions;
6132 }
6133 let snapshot_indices = table.indices().to_vec();
6138 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
6139 table.rows().iter().cloned().collect();
6140 let snapshot_schema = table.schema().clone();
6141 let idx_ref = snapshot_indices
6142 .iter()
6143 .find(|i| i.name == stmt.name)
6144 .expect("just-added index");
6145 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
6146 }
6147 self.plan_cache.evict_referencing(&table_name);
6150 Ok(QueryResult::CommandOk {
6151 affected: 0,
6152 modified_catalog: !self.in_transaction(),
6153 })
6154 }
6155
6156 fn reconcile_table_if_not_exists(
6165 &mut self,
6166 stmt: CreateTableStatement,
6167 ) -> Result<QueryResult, EngineError> {
6168 let table_name = stmt.name.clone();
6169 let clock = self.clock;
6170 let existing_col_names: alloc::collections::BTreeSet<String> = self
6171 .active_catalog()
6172 .get(&table_name)
6173 .expect("checked above")
6174 .schema()
6175 .columns
6176 .iter()
6177 .map(|c| c.name.to_ascii_lowercase())
6178 .collect();
6179 let row_count = self
6180 .active_catalog()
6181 .get(&table_name)
6182 .expect("checked above")
6183 .row_count();
6184 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6186 .columns
6187 .iter()
6188 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6189 .cloned()
6190 .collect();
6191 for col_def in new_columns {
6192 let col_name = col_def.name.clone();
6193 let nullable = col_def.nullable;
6194 let has_default = col_def.default.is_some() || col_def.auto_increment;
6195 let col_schema = column_def_to_schema(col_def)?;
6196 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6197 resolve_column_default_free(&col_schema, clock)?
6198 } else if nullable || row_count == 0 {
6199 Value::Null
6200 } else {
6201 return Err(EngineError::Unsupported(alloc::format!(
6202 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6203 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6204 )));
6205 };
6206 let table = self
6207 .active_catalog_mut()
6208 .get_mut(&table_name)
6209 .expect("checked above");
6210 table.add_column(col_schema, fill_value);
6211 }
6212 let table_cols_now = self
6216 .active_catalog()
6217 .get(&table_name)
6218 .expect("checked above")
6219 .schema()
6220 .columns
6221 .clone();
6222 for fk in stmt.foreign_keys {
6223 let all_resolved = fk.columns.iter().all(|c| {
6227 table_cols_now
6228 .iter()
6229 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6230 });
6231 if !all_resolved {
6232 continue;
6233 }
6234 let already_present = {
6235 let table = self
6236 .active_catalog()
6237 .get(&table_name)
6238 .expect("checked above");
6239 table.schema().foreign_keys.iter().any(|f| {
6240 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6241 && f.local_columns.len() == fk.columns.len()
6242 })
6243 };
6244 if already_present {
6245 continue;
6246 }
6247 let storage_fk =
6248 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6249 let table = self
6250 .active_catalog_mut()
6251 .get_mut(&table_name)
6252 .expect("checked above");
6253 table.schema_mut().foreign_keys.push(storage_fk);
6254 }
6255 Ok(QueryResult::CommandOk {
6256 affected: 0,
6257 modified_catalog: !self.in_transaction(),
6258 })
6259 }
6260
6261 fn exec_drop_table(
6263 &mut self,
6264 names: Vec<String>,
6265 if_exists: bool,
6266 ) -> Result<QueryResult, EngineError> {
6267 for name in names {
6268 let dropped = self.active_catalog_mut().drop_table(&name);
6269 if !dropped && !if_exists {
6270 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6271 }
6272 }
6273 Ok(QueryResult::CommandOk {
6274 affected: 0,
6275 modified_catalog: !self.in_transaction(),
6276 })
6277 }
6278
6279 fn exec_drop_index(
6281 &mut self,
6282 name: String,
6283 if_exists: bool,
6284 ) -> Result<QueryResult, EngineError> {
6285 let dropped = self.active_catalog_mut().drop_named_index(&name);
6286 if !dropped && !if_exists {
6287 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6288 }
6289 Ok(QueryResult::CommandOk {
6290 affected: 0,
6291 modified_catalog: !self.in_transaction(),
6292 })
6293 }
6294
6295 fn exec_create_table(
6296 &mut self,
6297 stmt: CreateTableStatement,
6298 ) -> Result<QueryResult, EngineError> {
6299 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6300 return Ok(QueryResult::CommandOk {
6319 affected: 0,
6320 modified_catalog: false,
6321 });
6322 }
6323 let table_name = stmt.name.clone();
6324 let inline_pk_columns: Vec<String> = stmt
6328 .columns
6329 .iter()
6330 .filter(|c| c.is_primary_key)
6331 .map(|c| c.name.clone())
6332 .collect();
6333 let cols = stmt
6339 .columns
6340 .into_iter()
6341 .map(column_def_to_schema)
6342 .collect::<Result<Vec<_>, _>>()?;
6343 let mut cols = cols;
6352 for col in cols.iter_mut() {
6353 let Some(name) = col.user_enum_type.take() else {
6354 continue;
6355 };
6356 let cat = self.active_catalog();
6357 if cat.enum_types().contains_key(&name) {
6358 col.user_enum_type = Some(name);
6359 continue;
6360 }
6361 if let Some(dom) = cat.domain_types().get(&name) {
6362 col.ty = dom.base_type;
6363 col.user_domain_type = Some(name);
6364 if !dom.nullable {
6365 col.nullable = false;
6366 }
6367 continue;
6368 }
6369 return Err(EngineError::Unsupported(alloc::format!(
6370 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6371 col.name,
6372 name
6373 )));
6374 }
6375 for tc in &stmt.table_constraints {
6376 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6377 for col_name in columns {
6378 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6379 col.nullable = false;
6380 }
6381 }
6382 }
6383 }
6384 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6391 Vec::with_capacity(stmt.foreign_keys.len());
6392 for fk in stmt.foreign_keys {
6393 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6400 if !self.foreign_key_checks
6401 && needs_parent
6402 && self.active_catalog().get(&fk.parent_table).is_none()
6403 {
6404 self.pending_foreign_keys.push((table_name.clone(), fk));
6405 continue;
6406 }
6407 fks.push(resolve_foreign_key(
6408 &table_name,
6409 &cols,
6410 fk,
6411 self.active_catalog(),
6412 )?);
6413 }
6414 let mut schema = TableSchema::new(table_name.clone(), cols);
6415 schema.foreign_keys = fks;
6416 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6420 let mut check_exprs: Vec<String> = Vec::new();
6421 for tc in &stmt.table_constraints {
6422 let (is_pk, names, nnd) = match tc {
6423 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6424 (true, columns.clone(), false)
6425 }
6426 spg_sql::ast::TableConstraint::Unique {
6427 columns,
6428 nulls_not_distinct,
6429 ..
6430 } => (false, columns.clone(), *nulls_not_distinct),
6431 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6432 check_exprs.push(alloc::format!("{expr}"));
6435 continue;
6436 }
6437 spg_sql::ast::TableConstraint::Index { .. } => continue,
6443 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6447 };
6448 let mut positions = Vec::with_capacity(names.len());
6449 for n in &names {
6450 let pos = schema
6451 .columns
6452 .iter()
6453 .position(|c| c.name == *n)
6454 .ok_or_else(|| {
6455 EngineError::Unsupported(alloc::format!(
6456 "table constraint references unknown column {n:?}"
6457 ))
6458 })?;
6459 positions.push(pos);
6460 }
6461 uc_storage.push(spg_storage::UniquenessConstraint {
6462 is_primary_key: is_pk,
6463 columns: positions,
6464 nulls_not_distinct: nnd,
6465 });
6466 }
6467 schema.uniqueness_constraints = uc_storage.clone();
6468 schema.checks = check_exprs;
6469 self.active_catalog_mut().create_table(schema)?;
6470 let table = self
6474 .active_catalog_mut()
6475 .get_mut(&table_name)
6476 .expect("just created");
6477 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6478 let idx_name = if inline_pk_columns.len() == 1 {
6479 alloc::format!("{table_name}_pkey")
6480 } else {
6481 alloc::format!("{table_name}_pkey_{i}")
6482 };
6483 if let Err(e) = table.add_index(idx_name, col_name) {
6484 return Err(EngineError::Storage(e));
6485 }
6486 }
6487 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6488 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6493 for (k, col) in columns.iter().enumerate() {
6494 let already = table.indices().iter().any(|idx| {
6495 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6496 && table.schema().columns[idx.column_position].name == *col
6497 });
6498 if already {
6499 continue;
6500 }
6501 let idx_name = match (name.as_ref(), columns.len(), k) {
6502 (Some(n), 1, _) => n.clone(),
6503 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6504 (None, _, _) => {
6505 alloc::format!("{table_name}_{col}_ftidx")
6506 }
6507 };
6508 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6509 return Err(EngineError::Storage(e));
6510 }
6511 }
6512 continue;
6513 }
6514 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6518 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6519 ("pkey", columns, None)
6520 }
6521 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6522 spg_sql::ast::TableConstraint::Index { name, columns } => {
6523 ("idx", columns, name.as_ref())
6524 }
6525 spg_sql::ast::TableConstraint::Check { .. } => continue,
6526 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6528 };
6529 let leading = &names[0];
6530 let already = table.indices().iter().any(|idx| {
6533 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6534 && table.schema().columns[idx.column_position].name == *leading
6535 });
6536 if already {
6537 continue;
6538 }
6539 let idx_name = if let Some(n) = explicit_name {
6540 n.clone()
6541 } else if names.len() == 1 {
6542 alloc::format!("{table_name}_{leading}_{suffix}")
6543 } else {
6544 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6545 };
6546 if let Err(e) = table.add_index(idx_name, leading) {
6547 return Err(EngineError::Storage(e));
6548 }
6549 }
6550 Ok(QueryResult::CommandOk {
6551 affected: 0,
6552 modified_catalog: !self.in_transaction(),
6553 })
6554 }
6555
6556 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6557 for tuple in &mut stmt.rows {
6565 for cell in tuple.iter_mut() {
6566 self.resolve_sequence_calls_in_expr(cell)?;
6567 }
6568 }
6569 if let Some(select) = stmt.select_source.clone() {
6574 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6575 let rows = match select_result {
6576 QueryResult::Rows { rows, .. } => rows,
6577 other => {
6578 return Err(EngineError::Unsupported(alloc::format!(
6579 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6580 )));
6581 }
6582 };
6583 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6584 for row in rows {
6585 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6586 for v in row.values {
6587 tuple.push(value_to_literal_expr_permissive(v)?);
6588 }
6589 materialised.push(tuple);
6590 }
6591 let recurse = InsertStatement {
6592 table: stmt.table,
6593 columns: stmt.columns,
6594 rows: materialised,
6595 select_source: None,
6596 on_conflict: stmt.on_conflict,
6597 returning: stmt.returning,
6598 };
6599 return self.exec_insert(recurse);
6600 }
6601 let clock = self.clock;
6605 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6611 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6612 let trigger_session_cfg: Option<alloc::string::String> = self
6613 .session_params
6614 .get("default_text_search_config")
6615 .cloned();
6616 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6622 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6623 EngineError::Storage(StorageError::TableNotFound {
6624 name: stmt.table.clone(),
6625 })
6626 })?;
6627 preview_table.schema().columns.clone()
6628 };
6629 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6630 pre_borrow_column_meta
6631 .iter()
6632 .enumerate()
6633 .filter_map(|(i, col)| {
6634 if let Some(inline) = &col.inline_enum_variants {
6639 return Some((i, inline.clone()));
6640 }
6641 col.user_enum_type.as_ref().and_then(|ename| {
6642 self.active_catalog()
6643 .enum_types()
6644 .get(ename)
6645 .map(|e| (i, e.labels.clone()))
6646 })
6647 })
6648 .collect();
6649 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6654 pre_borrow_column_meta
6655 .iter()
6656 .enumerate()
6657 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6658 .collect();
6659 let table = self
6660 .active_catalog_mut()
6661 .get_mut(&stmt.table)
6662 .ok_or_else(|| {
6663 EngineError::Storage(StorageError::TableNotFound {
6664 name: stmt.table.clone(),
6665 })
6666 })?;
6667 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6673 let schema_cols_len = column_meta.len();
6674 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6678 None => None, Some(cols) => {
6680 let mut map = alloc::vec![None; schema_cols_len];
6681 for (j, name) in cols.iter().enumerate() {
6682 let idx = column_meta
6683 .iter()
6684 .position(|c| c.name == *name)
6685 .ok_or_else(|| {
6686 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6687 })?;
6688 if map[idx].is_some() {
6689 return Err(EngineError::Storage(StorageError::ArityMismatch {
6690 expected: schema_cols_len,
6691 actual: cols.len(),
6692 }));
6693 }
6694 map[idx] = Some(j);
6695 }
6696 for (i, col) in column_meta.iter().enumerate() {
6700 if map[i].is_none()
6701 && !col.nullable
6702 && col.default.is_none()
6703 && col.runtime_default.is_none()
6704 && !col.auto_increment
6705 {
6706 return Err(EngineError::Storage(StorageError::NullInNotNull {
6707 column: col.name.clone(),
6708 }));
6709 }
6710 }
6711 Some(map)
6712 }
6713 };
6714 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6715 let fks = table.schema().foreign_keys.clone();
6721 let mut affected = 0usize;
6722 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6725 for tuple in stmt.rows {
6726 if tuple.len() != expected_tuple_len {
6727 return Err(EngineError::Storage(StorageError::ArityMismatch {
6728 expected: expected_tuple_len,
6729 actual: tuple.len(),
6730 }));
6731 }
6732 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6736 let raw_tuple: Vec<Value> = tuple
6738 .into_iter()
6739 .map(literal_expr_to_value)
6740 .collect::<Result<_, _>>()?;
6741 let mut out = Vec::with_capacity(schema_cols_len);
6742 for (i, col) in column_meta.iter().enumerate() {
6743 let mut raw = match map[i] {
6744 Some(j) => raw_tuple[j].clone(),
6745 None => resolve_column_default_free(col, clock)?,
6746 };
6747 if col.auto_increment && raw.is_null() {
6748 let next = table.next_auto_value(i).ok_or_else(|| {
6749 EngineError::Unsupported(alloc::format!(
6750 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6751 col.name
6752 ))
6753 })?;
6754 raw = Value::BigInt(next);
6755 }
6756 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6757 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6758 let coerced =
6759 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6760 check_unsigned_range(&coerced, col, i)?;
6761 out.push(coerced);
6762 }
6763 out
6764 } else {
6765 let mut out = Vec::with_capacity(schema_cols_len);
6767 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6768 let mut raw = literal_expr_to_value(expr)?;
6769 if col.auto_increment && raw.is_null() {
6770 let next = table.next_auto_value(i).ok_or_else(|| {
6771 EngineError::Unsupported(alloc::format!(
6772 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6773 col.name
6774 ))
6775 })?;
6776 raw = Value::BigInt(next);
6777 }
6778 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6779 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6780 let coerced =
6781 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6782 check_unsigned_range(&coerced, col, i)?;
6783 out.push(coerced);
6784 }
6785 out
6786 };
6787 all_values.push(values);
6788 }
6789 let uniqueness = table.schema().uniqueness_constraints.clone();
6794 let _ = table;
6795 if !fks.is_empty() {
6796 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6797 }
6798 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6800 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6814 let mut skipped_count = 0usize;
6815 if let Some(clause) = &stmt.on_conflict {
6816 let conflict_cols = resolve_on_conflict_columns(
6817 self.active_catalog(),
6818 &stmt.table,
6819 clause.target_columns.as_slice(),
6820 )?;
6821 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6822 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6823 for values in all_values {
6824 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6825 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6828 let collides_with_table = !has_null_key
6829 && on_conflict_keys_exist(
6830 self.active_catalog(),
6831 &stmt.table,
6832 &conflict_cols,
6833 &key_tuple,
6834 );
6835 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6836 let collides_with_batch =
6837 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6838 let collides = collides_with_table || collides_with_batch;
6839 match (&clause.action, collides) {
6840 (_, false) => {
6841 seen_keys.push(key_tuple_owned);
6842 kept.push(values);
6843 }
6844 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6845 skipped_count += 1;
6846 }
6847 (
6848 spg_sql::ast::OnConflictAction::Update {
6849 assignments,
6850 where_,
6851 },
6852 true,
6853 ) => {
6854 if !collides_with_table {
6855 skipped_count += 1;
6856 continue;
6857 }
6858 let target_pos = lookup_row_position_by_keys(
6859 self.active_catalog(),
6860 &stmt.table,
6861 &conflict_cols,
6862 &key_tuple,
6863 )
6864 .ok_or_else(|| {
6865 EngineError::Unsupported(
6866 "ON CONFLICT DO UPDATE: conflict detected but row \
6867 position could not be resolved (cold-tier row?)"
6868 .into(),
6869 )
6870 })?;
6871 let updated = apply_on_conflict_assignments(
6872 self.active_catalog(),
6873 &stmt.table,
6874 target_pos,
6875 &values,
6876 assignments,
6877 where_.as_ref(),
6878 )?;
6879 if let Some(new_row) = updated {
6880 pending_updates.push((target_pos, new_row));
6881 } else {
6882 skipped_count += 1;
6883 }
6884 }
6885 }
6886 }
6887 all_values = kept;
6888 }
6889 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
6895 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
6896 let table = self
6898 .active_catalog_mut()
6899 .get_mut(&stmt.table)
6900 .ok_or_else(|| {
6901 EngineError::Storage(StorageError::TableNotFound {
6902 name: stmt.table.clone(),
6903 })
6904 })?;
6905 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
6909 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
6913 'rowloop: for values in all_values {
6914 let mut row = Row::new(values);
6915 for fd in &before_insert_triggers {
6920 let (outcome, deferred) = triggers::fire_row_trigger(
6921 fd,
6922 Some(row.clone()),
6923 None,
6924 &stmt.table,
6925 &column_meta,
6926 &[],
6927 trigger_session_cfg.as_deref(),
6928 false,
6929 )
6930 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6931 deferred_embedded.extend(deferred);
6932 match outcome {
6933 triggers::TriggerOutcome::Row(r) => row = r,
6934 triggers::TriggerOutcome::Skip => continue 'rowloop,
6935 }
6936 }
6937 if stmt.returning.is_some() {
6938 returning_rows.push(row.values.clone());
6939 }
6940 let inserted = row.clone();
6943 table.insert(row)?;
6944 affected += 1;
6945 for fd in &after_insert_triggers {
6949 let (_outcome, deferred) = triggers::fire_row_trigger(
6950 fd,
6951 Some(inserted.clone()),
6952 None,
6953 &stmt.table,
6954 &column_meta,
6955 &[],
6956 trigger_session_cfg.as_deref(),
6957 true,
6958 )
6959 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6960 deferred_embedded.extend(deferred);
6961 }
6962 }
6963 for (pos, new_row) in pending_updates {
6967 if stmt.returning.is_some() {
6968 returning_rows.push(new_row.clone());
6969 }
6970 table.update_row(pos, new_row)?;
6971 affected += 1;
6972 }
6973 let _ = skipped_count;
6974 let _ = table;
6980 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
6981 if let Some(items) = &stmt.returning {
6985 return self.build_returning_rows(&stmt.table, items, returning_rows);
6986 }
6987 if !self.in_transaction() && affected > 0 {
6992 self.statistics
6993 .record_modifications(&stmt.table, affected as u64);
6994 }
6995 Ok(QueryResult::CommandOk {
6996 affected,
6997 modified_catalog: !self.in_transaction(),
6998 })
6999 }
7000
7001 fn exec_select_as_of_segment(
7014 &self,
7015 stmt: &SelectStatement,
7016 from: &spg_sql::ast::FromClause,
7017 segment_id: u32,
7018 ) -> Result<QueryResult, EngineError> {
7019 if !from.joins.is_empty()
7022 || stmt.group_by.is_some()
7023 || stmt.having.is_some()
7024 || !stmt.unions.is_empty()
7025 || !stmt.order_by.is_empty()
7026 || stmt.offset.is_some()
7027 || stmt.distinct
7028 || aggregate::uses_aggregate(stmt)
7029 {
7030 return Err(EngineError::Unsupported(
7031 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
7032 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
7033 .into(),
7034 ));
7035 }
7036 let table = self
7037 .active_catalog()
7038 .get(&from.primary.name)
7039 .ok_or_else(|| StorageError::TableNotFound {
7040 name: from.primary.name.clone(),
7041 })?;
7042 let schema = table.schema().clone();
7043 let schema_cols = &schema.columns;
7044 let alias = from
7045 .primary
7046 .alias
7047 .as_deref()
7048 .unwrap_or(from.primary.name.as_str());
7049 let ctx = EvalContext::new(schema_cols, Some(alias));
7050 let seg = self
7051 .active_catalog()
7052 .cold_segment(segment_id)
7053 .ok_or_else(|| {
7054 EngineError::Unsupported(alloc::format!(
7055 "AS OF SEGMENT: cold segment {segment_id} not registered"
7056 ))
7057 })?;
7058 let mut out_rows: Vec<Row> = Vec::new();
7059 let mut limit_remaining: Option<usize> =
7060 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
7061 for (_key, body) in seg.scan() {
7062 let (row, _consumed) =
7063 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
7064 if let Some(where_expr) = &stmt.where_ {
7065 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
7066 if !matches!(cond, Value::Bool(true)) {
7067 continue;
7068 }
7069 }
7070 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7072 out_rows.push(projected);
7073 if let Some(rem) = limit_remaining.as_mut() {
7074 if *rem == 0 {
7075 out_rows.pop();
7076 break;
7077 }
7078 *rem -= 1;
7079 }
7080 }
7081 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7083 Ok(QueryResult::Rows {
7084 columns,
7085 rows: out_rows,
7086 })
7087 }
7088
7089 fn eval_expr_simple(
7094 &self,
7095 expr: &Expr,
7096 row: &Row,
7097 ctx: &EvalContext,
7098 ) -> Result<Value, EngineError> {
7099 let cancel = CancelToken::none();
7100 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7101 }
7102
7103 fn build_returning_rows(
7110 &self,
7111 table_name: &str,
7112 items: &[SelectItem],
7113 mutated_rows: Vec<Vec<Value>>,
7114 ) -> Result<QueryResult, EngineError> {
7115 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7116 EngineError::Storage(StorageError::TableNotFound {
7117 name: table_name.into(),
7118 })
7119 })?;
7120 let schema_cols = table.schema().columns.clone();
7121 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7122 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7123 for values in mutated_rows {
7124 let row = Row::new(values);
7125 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7126 out_rows.push(projected);
7127 }
7128 Ok(QueryResult::Rows {
7129 columns,
7130 rows: out_rows,
7131 })
7132 }
7133
7134 fn project_row_simple(
7138 &self,
7139 row: &Row,
7140 items: &[SelectItem],
7141 schema_cols: &[ColumnSchema],
7142 alias: &str,
7143 ) -> Result<Row, EngineError> {
7144 let ctx = EvalContext::new(schema_cols, Some(alias));
7145 let cancel = CancelToken::none();
7146 let mut out_vals = Vec::new();
7147 for item in items {
7148 match item {
7149 SelectItem::Wildcard => {
7150 out_vals.extend(row.values.iter().cloned());
7151 }
7152 SelectItem::Expr { expr, .. } => {
7153 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7154 out_vals.push(v);
7155 }
7156 }
7157 }
7158 Ok(Row::new(out_vals))
7159 }
7160
7161 fn derive_output_columns(
7166 &self,
7167 items: &[SelectItem],
7168 schema_cols: &[ColumnSchema],
7169 _alias: &str,
7170 ) -> Vec<ColumnSchema> {
7171 let mut out = Vec::new();
7172 for item in items {
7173 match item {
7174 SelectItem::Wildcard => {
7175 out.extend(schema_cols.iter().cloned());
7176 }
7177 SelectItem::Expr { expr, alias } => {
7178 if let Expr::Column(col) = expr
7184 && let Some(sc) = schema_cols.iter().find(|c| c.name == col.name)
7185 {
7186 let name = alias.clone().unwrap_or_else(|| sc.name.clone());
7187 out.push(ColumnSchema::new(name, sc.ty, sc.nullable));
7188 continue;
7189 }
7190 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7191 out.push(ColumnSchema::new(name, DataType::Text, true));
7194 }
7195 }
7196 }
7197 out
7198 }
7199
7200 fn exec_select_cancel(
7201 &self,
7202 stmt: &SelectStatement,
7203 cancel: CancelToken<'_>,
7204 ) -> Result<QueryResult, EngineError> {
7205 cancel.check()?;
7206 if !self.active_catalog().views().is_empty() {
7213 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7214 return self.exec_select_cancel(&rewritten, cancel);
7215 }
7216 }
7217 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7226 return self.exec_select_with_meta_views(stmt, cancel);
7227 }
7228 if let Some(from) = &stmt.from
7237 && let Some(seg_id) = from.primary.as_of_segment
7238 {
7239 return self.exec_select_as_of_segment(stmt, from, seg_id);
7240 }
7241 if let Some(from) = &stmt.from
7245 && from.joins.is_empty()
7246 && stmt.where_.is_none()
7247 && stmt.group_by.is_none()
7248 && stmt.having.is_none()
7249 && stmt.unions.is_empty()
7250 && stmt.order_by.is_empty()
7251 && stmt.limit.is_none()
7252 && stmt.offset.is_none()
7253 && !stmt.distinct
7254 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7255 {
7256 let lower = from.primary.name.to_ascii_lowercase();
7257 match lower.as_str() {
7258 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7259 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7261 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7262 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7263 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7264 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7265 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7266 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7267 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7268 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7269 _ => {}
7270 }
7271 }
7272 if !stmt.ctes.is_empty() {
7280 return self.exec_with_ctes(stmt, cancel);
7281 }
7282 let mut stmt_owned;
7289 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7290 stmt_owned = stmt.clone();
7291 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7292 &stmt_owned
7293 } else {
7294 stmt
7295 };
7296 if stmt_ref.unions.is_empty() {
7297 return self.exec_bare_select_cancel(stmt_ref, cancel);
7298 }
7299 let mut head = stmt_ref.clone();
7304 head.unions = Vec::new();
7305 head.order_by = Vec::new();
7306 head.limit = None;
7307 let QueryResult::Rows { columns, mut rows } =
7308 self.exec_bare_select_cancel(&head, cancel)?
7309 else {
7310 unreachable!("bare SELECT cannot return CommandOk")
7311 };
7312 for (kind, peer) in &stmt_ref.unions {
7313 let QueryResult::Rows {
7314 columns: peer_cols,
7315 rows: peer_rows,
7316 } = self.exec_bare_select_cancel(peer, cancel)?
7317 else {
7318 unreachable!("bare SELECT cannot return CommandOk")
7319 };
7320 if peer_cols.len() != columns.len() {
7321 return Err(EngineError::Unsupported(alloc::format!(
7322 "UNION arity mismatch: head has {} columns, peer has {}",
7323 columns.len(),
7324 peer_cols.len()
7325 )));
7326 }
7327 rows.extend(peer_rows);
7328 if matches!(kind, UnionKind::Distinct) {
7329 rows = dedup_rows(rows);
7330 }
7331 }
7332 if !stmt.order_by.is_empty() {
7335 let synth_ctx = EvalContext::new(&columns, None);
7336 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7337 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7338 for r in rows {
7339 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7340 tagged.push((keys, r));
7341 }
7342 sort_by_keys(&mut tagged, &descs);
7343 rows = tagged.into_iter().map(|(_, r)| r).collect();
7344 }
7345 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7346 Ok(QueryResult::Rows { columns, rows })
7347 }
7348
7349 #[allow(clippy::too_many_lines)]
7350 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7358 &self,
7359 stmt: &SelectStatement,
7360 primary: &TableRef,
7361 cancel: CancelToken<'_>,
7362 ) -> Result<QueryResult, EngineError> {
7363 let expr = primary
7364 .unnest_expr
7365 .as_deref()
7366 .expect("caller guards unnest_expr.is_some()");
7367 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7370 let ctx = EvalContext::new(&empty_schema, None);
7371 let dummy_row = Row::new(alloc::vec::Vec::new());
7372 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7375 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7376 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7377 Value::TextArray(items) => {
7378 let rows = items
7379 .into_iter()
7380 .map(|item| {
7381 Row::new(alloc::vec![match item {
7382 Some(s) => Value::Text(s),
7383 None => Value::Null,
7384 }])
7385 })
7386 .collect();
7387 (DataType::Text, rows)
7388 }
7389 Value::IntArray(items) => {
7390 let rows = items
7391 .into_iter()
7392 .map(|item| {
7393 Row::new(alloc::vec![match item {
7394 Some(n) => Value::Int(n),
7395 None => Value::Null,
7396 }])
7397 })
7398 .collect();
7399 (DataType::Int, rows)
7400 }
7401 Value::BigIntArray(items) => {
7402 let rows = items
7403 .into_iter()
7404 .map(|item| {
7405 Row::new(alloc::vec![match item {
7406 Some(n) => Value::BigInt(n),
7407 None => Value::Null,
7408 }])
7409 })
7410 .collect();
7411 (DataType::BigInt, rows)
7412 }
7413 other => {
7414 return Err(EngineError::Unsupported(alloc::format!(
7415 "unnest() expects an array argument, got {:?}",
7416 other.data_type()
7417 )));
7418 }
7419 };
7420 let alias = primary
7421 .alias
7422 .clone()
7423 .unwrap_or_else(|| "unnest".to_string());
7424 let col_name = primary
7430 .unnest_column_aliases
7431 .first()
7432 .cloned()
7433 .unwrap_or_else(|| alias.clone());
7434 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7435 let schema_cols = alloc::vec![col_schema.clone()];
7436 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7437 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7439 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7440 for row in rows {
7441 cancel.check()?;
7442 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7443 if matches!(v, Value::Bool(true)) {
7444 out.push(row);
7445 }
7446 }
7447 out
7448 } else {
7449 rows
7450 };
7451 if aggregate::uses_aggregate(stmt) {
7457 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7458 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7459 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7460 return Ok(QueryResult::Rows {
7461 columns: agg.columns,
7462 rows: agg.rows,
7463 });
7464 }
7465 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7467 let mut projected_rows: alloc::vec::Vec<Row> =
7468 alloc::vec::Vec::with_capacity(filtered.len());
7469 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7478 if let Some(srf_idx) = srf_position {
7479 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7480 .expect("checked by is_top_level_unnest above");
7481 for row in &filtered {
7482 let arr_val =
7483 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7484 let elements = array_value_to_elements(&arr_val)?;
7485 for elem in elements {
7489 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7490 for (i, p) in projection.iter().enumerate() {
7491 if i == srf_idx {
7492 vals.push(elem.clone());
7493 } else {
7494 vals.push(
7495 eval::eval_expr(&p.expr, row, &scan_ctx)
7496 .map_err(EngineError::Eval)?,
7497 );
7498 }
7499 }
7500 projected_rows.push(Row::new(vals));
7501 }
7502 }
7503 } else {
7504 for row in &filtered {
7505 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7506 for p in &projection {
7507 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7508 }
7509 projected_rows.push(Row::new(vals));
7510 }
7511 }
7512 let columns: alloc::vec::Vec<ColumnSchema> = projection
7515 .iter()
7516 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7517 .collect();
7518 if !stmt.order_by.is_empty() {
7521 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7522 .iter()
7523 .enumerate()
7524 .map(|(i, r)| -> Result<_, EngineError> {
7525 let keys: Result<Vec<Value>, EngineError> = stmt
7526 .order_by
7527 .iter()
7528 .map(|ob| {
7529 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7530 })
7531 .collect();
7532 Ok((i, keys?))
7533 })
7534 .collect::<Result<_, _>>()?;
7535 indexed.sort_by(|a, b| {
7536 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7537 let mut cmp = value_cmp(ka, kb);
7538 if stmt.order_by[idx].desc {
7539 cmp = cmp.reverse();
7540 }
7541 if cmp != core::cmp::Ordering::Equal {
7542 return cmp;
7543 }
7544 }
7545 core::cmp::Ordering::Equal
7546 });
7547 projected_rows = indexed
7548 .into_iter()
7549 .map(|(i, _)| projected_rows[i].clone())
7550 .collect();
7551 }
7552 if let Some(offset) = stmt.offset_literal() {
7554 let off = (offset as usize).min(projected_rows.len());
7555 projected_rows.drain(..off);
7556 }
7557 if let Some(limit) = stmt.limit_literal() {
7558 projected_rows.truncate(limit as usize);
7559 }
7560 Ok(QueryResult::Rows {
7561 columns,
7562 rows: projected_rows,
7563 })
7564 }
7565
7566 fn exec_select_generate_series(
7577 &self,
7578 stmt: &SelectStatement,
7579 primary: &TableRef,
7580 cancel: CancelToken<'_>,
7581 ) -> Result<QueryResult, EngineError> {
7582 let args = primary
7583 .generate_series_args
7584 .as_ref()
7585 .expect("caller guards generate_series_args.is_some()");
7586 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7587 let ctx = EvalContext::new(&empty_schema, None);
7588 let dummy_row = Row::new(alloc::vec::Vec::new());
7589 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7590 for a in args {
7591 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7592 }
7593 let (elem_dtype, rows) = match arg_values.as_slice() {
7597 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7598 let interval_step = match step {
7599 Value::Interval { .. } => step.clone(),
7600 other => {
7601 return Err(EngineError::Unsupported(alloc::format!(
7602 "generate_series(timestamp, timestamp, …): \
7603 step must be INTERVAL, got {:?}",
7604 other.data_type()
7605 )));
7606 }
7607 };
7608 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7609 (DataType::Timestamp, rows)
7610 }
7611 [start, stop, step]
7612 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7613 {
7614 let s = value_to_i64(start);
7615 let e = value_to_i64(stop);
7616 let st = value_to_i64(step);
7617 let rows = generate_series_integers(s, e, st, &cancel)?;
7618 (DataType::BigInt, rows)
7619 }
7620 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7621 let s = value_to_i64(start);
7622 let e = value_to_i64(stop);
7623 let rows = generate_series_integers(s, e, 1, &cancel)?;
7624 (DataType::BigInt, rows)
7625 }
7626 _ => {
7627 return Err(EngineError::Unsupported(alloc::format!(
7628 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7629 argument shapes; got {:?}",
7630 arg_values
7631 .iter()
7632 .map(|v| v.data_type())
7633 .collect::<alloc::vec::Vec<_>>()
7634 )));
7635 }
7636 };
7637 let alias = primary
7638 .alias
7639 .clone()
7640 .unwrap_or_else(|| "generate_series".to_string());
7641 let col_name = alias.clone();
7642 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7643 let schema_cols = alloc::vec![col_schema.clone()];
7644 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7645 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7647 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7648 for row in rows {
7649 cancel.check()?;
7650 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7651 if matches!(v, Value::Bool(true)) {
7652 out.push(row);
7653 }
7654 }
7655 out
7656 } else {
7657 rows
7658 };
7659 if aggregate::uses_aggregate(stmt) {
7669 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7670 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7671 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7672 return Ok(QueryResult::Rows {
7673 columns: agg.columns,
7674 rows: agg.rows,
7675 });
7676 }
7677 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7679 let mut projected_rows: alloc::vec::Vec<Row> =
7680 alloc::vec::Vec::with_capacity(filtered.len());
7681 for row in &filtered {
7682 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7683 for p in &projection {
7684 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7685 }
7686 projected_rows.push(Row::new(vals));
7687 }
7688 let columns: alloc::vec::Vec<ColumnSchema> = projection
7689 .iter()
7690 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7691 .collect();
7692 if !stmt.order_by.is_empty() {
7694 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7695 .iter()
7696 .enumerate()
7697 .map(|(i, r)| -> Result<_, EngineError> {
7698 let keys: Result<Vec<Value>, EngineError> = stmt
7699 .order_by
7700 .iter()
7701 .map(|ob| {
7702 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7703 })
7704 .collect();
7705 Ok((i, keys?))
7706 })
7707 .collect::<Result<_, _>>()?;
7708 indexed.sort_by(|a, b| {
7709 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7710 let mut cmp = value_cmp(ka, kb);
7711 if stmt.order_by[idx].desc {
7712 cmp = cmp.reverse();
7713 }
7714 if cmp != core::cmp::Ordering::Equal {
7715 return cmp;
7716 }
7717 }
7718 core::cmp::Ordering::Equal
7719 });
7720 projected_rows = indexed
7721 .into_iter()
7722 .map(|(i, _)| projected_rows[i].clone())
7723 .collect();
7724 }
7725 if let Some(offset) = stmt.offset_literal() {
7726 let off = (offset as usize).min(projected_rows.len());
7727 projected_rows.drain(..off);
7728 }
7729 if let Some(limit) = stmt.limit_literal() {
7730 projected_rows.truncate(limit as usize);
7731 }
7732 Ok(QueryResult::Rows {
7733 columns,
7734 rows: projected_rows,
7735 })
7736 }
7737
7738 fn exec_bare_select_cancel(
7739 &self,
7740 stmt: &SelectStatement,
7741 cancel: CancelToken<'_>,
7742 ) -> Result<QueryResult, EngineError> {
7743 check_with_ties_requires_order_by(stmt)?;
7748 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7756 return self.exec_select_with_meta_views(stmt, cancel);
7757 }
7758 if select_has_window(stmt) {
7763 return self.exec_select_with_window(stmt, cancel);
7764 }
7765 let Some(from) = &stmt.from else {
7770 let empty_schema: Vec<ColumnSchema> = Vec::new();
7771 let ctx = self.ev_ctx(&empty_schema, None);
7772 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7773 let dummy_row = Row::new(Vec::new());
7774 let mut values = Vec::with_capacity(projection.len());
7775 for p in &projection {
7776 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7777 }
7778 let columns: Vec<ColumnSchema> = projection
7779 .into_iter()
7780 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7781 .collect();
7782 return Ok(QueryResult::Rows {
7783 columns,
7784 rows: alloc::vec![Row::new(values)],
7785 });
7786 };
7787 if !from.joins.is_empty() {
7791 return self.exec_joined_select(stmt, from);
7792 }
7793 if from.primary.unnest_expr.is_some() {
7800 return self.exec_select_unnest(stmt, &from.primary, cancel);
7801 }
7802 if from.primary.generate_series_args.is_some() {
7808 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7809 }
7810 let primary = &from.primary;
7811 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7812 StorageError::TableNotFound {
7813 name: primary.name.clone(),
7814 }
7815 })?;
7816 let schema_cols = &table.schema().columns;
7817 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7820 let ctx = self.ev_ctx(schema_cols, Some(alias));
7821
7822 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7827 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7828 }
7829
7830 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7838 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7841 .or_else(|| {
7842 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7848 })
7849 .or_else(|| {
7850 try_trgm_seek(w, schema_cols, table, alias)
7856 })
7857 });
7858
7859 if aggregate::uses_aggregate(stmt) {
7862 let mut filtered: Vec<&Row> = Vec::new();
7863 let mut memo = memoize::MemoizeCache::new();
7867 if let Some(rows) = &indexed_rows {
7868 for cow in rows {
7869 let row = cow.as_ref();
7870 if let Some(where_expr) = &stmt.where_ {
7871 let cond = self.eval_expr_with_correlated(
7872 where_expr,
7873 row,
7874 &ctx,
7875 cancel,
7876 Some(&mut memo),
7877 )?;
7878 if !matches!(cond, Value::Bool(true)) {
7879 continue;
7880 }
7881 }
7882 filtered.push(row);
7883 }
7884 } else {
7885 for i in 0..table.row_count() {
7886 let row = &table.rows()[i];
7887 if let Some(where_expr) = &stmt.where_ {
7888 let cond = self.eval_expr_with_correlated(
7889 where_expr,
7890 row,
7891 &ctx,
7892 cancel,
7893 Some(&mut memo),
7894 )?;
7895 if !matches!(cond, Value::Bool(true)) {
7896 continue;
7897 }
7898 }
7899 filtered.push(row);
7900 }
7901 }
7902 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
7903 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7904 return Ok(QueryResult::Rows {
7905 columns: agg.columns,
7906 rows: agg.rows,
7907 });
7908 }
7909
7910 let projection = build_projection(&stmt.items, schema_cols, alias)?;
7911 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7919
7920 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
7923 let mut memo = memoize::MemoizeCache::new();
7925 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
7928 if loop_idx.is_multiple_of(256) {
7929 cancel.check()?;
7930 }
7931 if let Some(where_expr) = &stmt.where_ {
7932 let cond =
7933 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
7934 if !matches!(cond, Value::Bool(true)) {
7935 return Ok(());
7936 }
7937 }
7938 let order_keys = if stmt.order_by.is_empty() {
7939 Vec::new()
7940 } else {
7941 build_order_keys(&stmt.order_by, row, &ctx)?
7942 };
7943 if let Some(srf_idx) = srf_position {
7944 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7945 .expect("checked by is_top_level_unnest above");
7946 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
7947 let elements = array_value_to_elements(&arr_val)?;
7948 for elem in elements {
7949 let mut values = Vec::with_capacity(projection.len());
7950 for (i, p) in projection.iter().enumerate() {
7951 if i == srf_idx {
7952 values.push(elem.clone());
7953 } else {
7954 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7955 }
7956 }
7957 tagged.push((order_keys.clone(), Row::new(values)));
7958 }
7959 } else {
7960 let mut values = Vec::with_capacity(projection.len());
7961 for p in &projection {
7962 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7963 }
7964 tagged.push((order_keys, Row::new(values)));
7965 }
7966 Ok(())
7967 };
7968 if let Some(rows) = &indexed_rows {
7969 for (loop_idx, cow) in rows.iter().enumerate() {
7970 process_row(cow.as_ref(), loop_idx)?;
7971 }
7972 } else {
7973 for i in 0..table.row_count() {
7974 process_row(&table.rows()[i], i)?;
7975 }
7976 }
7977
7978 if !stmt.order_by.is_empty() {
7979 let keep = if stmt.distinct || stmt.limit_with_ties {
7987 None
7988 } else {
7989 stmt.limit_literal()
7990 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
7991 };
7992 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7993 partial_sort_tagged(&mut tagged, keep, &descs);
7994 }
7995
7996 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
8006 apply_offset_and_limit_tagged(
8007 &mut tagged,
8008 stmt.offset_literal(),
8009 stmt.limit_literal(),
8010 true,
8011 );
8012 tagged.into_iter().map(|(_, r)| r).collect()
8013 } else {
8014 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8015 if stmt.distinct {
8016 output_rows = dedup_rows(output_rows);
8017 }
8018 apply_offset_and_limit(
8019 &mut output_rows,
8020 stmt.offset_literal(),
8021 stmt.limit_literal(),
8022 );
8023 output_rows
8024 };
8025
8026 let columns: Vec<ColumnSchema> = projection
8027 .into_iter()
8028 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8029 .collect();
8030
8031 Ok(QueryResult::Rows {
8032 columns,
8033 rows: output_rows,
8034 })
8035 }
8036
8037 #[allow(clippy::too_many_lines)]
8044 fn materialise_table_ref(
8052 &self,
8053 tref: &TableRef,
8054 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
8055 if let Some(expr) = tref.unnest_expr.as_deref() {
8056 let empty_schema: Vec<ColumnSchema> = Vec::new();
8057 let ctx = EvalContext::new(&empty_schema, None);
8058 let dummy_row = Row::new(Vec::new());
8059 let (elem_dtype, rows) =
8060 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
8061 Value::Null => (DataType::Text, Vec::new()),
8062 Value::TextArray(items) => (
8063 DataType::Text,
8064 items
8065 .into_iter()
8066 .map(|item| {
8067 Row::new(alloc::vec![match item {
8068 Some(s) => Value::Text(s),
8069 None => Value::Null,
8070 }])
8071 })
8072 .collect(),
8073 ),
8074 Value::IntArray(items) => (
8075 DataType::Int,
8076 items
8077 .into_iter()
8078 .map(|item| {
8079 Row::new(alloc::vec![match item {
8080 Some(n) => Value::Int(n),
8081 None => Value::Null,
8082 }])
8083 })
8084 .collect(),
8085 ),
8086 Value::BigIntArray(items) => (
8087 DataType::BigInt,
8088 items
8089 .into_iter()
8090 .map(|item| {
8091 Row::new(alloc::vec![match item {
8092 Some(n) => Value::BigInt(n),
8093 None => Value::Null,
8094 }])
8095 })
8096 .collect(),
8097 ),
8098 other => {
8099 return Err(EngineError::Unsupported(alloc::format!(
8100 "unnest() expects an array argument, got {:?}",
8101 other.data_type()
8102 )));
8103 }
8104 };
8105 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8106 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8107 return Ok((
8108 rows,
8109 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8110 ));
8111 }
8112 let table =
8113 self.active_catalog()
8114 .get(&tref.name)
8115 .ok_or_else(|| StorageError::TableNotFound {
8116 name: tref.name.clone(),
8117 })?;
8118 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8119 let cols = table.schema().columns.clone();
8120 Ok((rows, cols))
8121 }
8122
8123 fn build_joined_filtered_rows(
8135 &self,
8136 from: &FromClause,
8137 where_: Option<&Expr>,
8138 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8139 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8140 let primary_alias = from
8141 .primary
8142 .alias
8143 .as_deref()
8144 .unwrap_or(from.primary.name.as_str())
8145 .to_string();
8146 #[allow(clippy::type_complexity)]
8153 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8154 for j in &from.joins {
8155 let a = j
8156 .table
8157 .alias
8158 .as_deref()
8159 .unwrap_or(j.table.name.as_str())
8160 .to_string();
8161 if let Some(inner_box) = &j.table.lateral_subquery {
8162 let schema = self.lateral_probe_schema(inner_box)?;
8167 joined.push(JoinedPeer {
8168 eager_rows: None,
8169 cols: schema,
8170 alias: a,
8171 kind: j.kind,
8172 on: j.on.as_ref(),
8173 lateral: Some(inner_box.as_ref()),
8174 });
8175 } else {
8176 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8177 joined.push(JoinedPeer {
8178 eager_rows: Some(rows),
8179 cols,
8180 alias: a,
8181 kind: j.kind,
8182 on: j.on.as_ref(),
8183 lateral: None,
8184 });
8185 }
8186 }
8187 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8188 for col in &primary_cols {
8189 combined_schema.push(ColumnSchema::new(
8190 alloc::format!("{primary_alias}.{}", col.name),
8191 col.ty,
8192 col.nullable,
8193 ));
8194 }
8195 for peer in &joined {
8196 for col in &peer.cols {
8197 combined_schema.push(ColumnSchema::new(
8198 alloc::format!("{}.{}", peer.alias, col.name),
8199 col.ty,
8200 col.nullable,
8201 ));
8202 }
8203 }
8204 let ctx = EvalContext::new(&combined_schema, None);
8205 let mut working: Vec<Row> = primary_rows;
8206 let mut consumed_cols = primary_cols.len();
8209 for peer in &joined {
8210 let right_arity = peer.cols.len();
8211 let mut next: Vec<Row> = Vec::new();
8212 for left in &working {
8213 let mut left_matched = false;
8214 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8215 Some(inner) => {
8216 let outer_schema = &combined_schema[..consumed_cols];
8220 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8221 alloc::borrow::Cow::Owned(rows)
8222 }
8223 None => {
8224 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8225 alloc::borrow::Cow::Borrowed(r.as_slice())
8226 }
8227 };
8228 for right in per_left_rrows.as_ref() {
8229 let mut combined_vals = left.values.clone();
8230 combined_vals.extend(right.values.iter().cloned());
8231 let combined = Row::new(combined_vals);
8232 let keep = if let Some(on_expr) = peer.on {
8233 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
8234 matches!(cond, Value::Bool(true))
8235 } else {
8236 true
8237 };
8238 if keep {
8239 next.push(combined);
8240 left_matched = true;
8241 }
8242 }
8243 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8244 let mut combined_vals = left.values.clone();
8245 for _ in 0..right_arity {
8246 combined_vals.push(Value::Null);
8247 }
8248 next.push(Row::new(combined_vals));
8249 }
8250 }
8251 working = next;
8252 consumed_cols += right_arity;
8253 debug_assert!(consumed_cols <= combined_schema.len());
8254 }
8255 let mut filtered: Vec<Row> = Vec::new();
8256 for row in working {
8257 if let Some(where_expr) = where_ {
8258 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
8259 if !matches!(cond, Value::Bool(true)) {
8260 continue;
8261 }
8262 }
8263 filtered.push(row);
8264 }
8265 Ok((combined_schema, filtered))
8266 }
8267
8268 fn lateral_probe_schema(
8274 &self,
8275 inner: &SelectStatement,
8276 ) -> Result<Vec<ColumnSchema>, EngineError> {
8277 match self.execute_readonly_select_for_lateral_probe(inner) {
8287 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8288 _ => {
8294 let mut out: Vec<ColumnSchema> = Vec::new();
8295 for (i, item) in inner.items.iter().enumerate() {
8296 let name = match item {
8297 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8298 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8299 SelectItem::Wildcard => alloc::format!("col{i}"),
8300 };
8301 out.push(ColumnSchema::new(name, DataType::Text, true));
8302 }
8303 Ok(out)
8304 }
8305 }
8306 }
8307
8308 fn execute_readonly_select_for_lateral_probe(
8314 &self,
8315 inner: &SelectStatement,
8316 ) -> Result<QueryResult, EngineError> {
8317 self.exec_bare_select_cancel(inner, CancelToken::none())
8318 }
8319
8320 fn materialise_lateral_for_outer(
8326 &self,
8327 inner: &SelectStatement,
8328 outer_schema: &[ColumnSchema],
8329 outer_row: &Row,
8330 ) -> Result<Vec<Row>, EngineError> {
8331 let mut substituted = inner.clone();
8332 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8333 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8334 match result {
8335 QueryResult::Rows { rows, .. } => Ok(rows),
8336 _ => Err(EngineError::Unsupported(
8337 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8338 )),
8339 }
8340 }
8341
8342 fn exec_joined_select(
8343 &self,
8344 stmt: &SelectStatement,
8345 from: &FromClause,
8346 ) -> Result<QueryResult, EngineError> {
8347 let (combined_schema, filtered) =
8355 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8356 let ctx = EvalContext::new(&combined_schema, None);
8357 if aggregate::uses_aggregate(stmt) {
8360 let refs: Vec<&Row> = filtered.iter().collect();
8361 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8362 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8363 return Ok(QueryResult::Rows {
8364 columns: agg.columns,
8365 rows: agg.rows,
8366 });
8367 }
8368
8369 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8370 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8371 for row in &filtered {
8372 let mut values = Vec::with_capacity(projection.len());
8373 for p in &projection {
8374 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8375 }
8376 let order_keys = if stmt.order_by.is_empty() {
8377 Vec::new()
8378 } else {
8379 build_order_keys(&stmt.order_by, row, &ctx)?
8380 };
8381 tagged.push((order_keys, Row::new(values)));
8382 }
8383 if !stmt.order_by.is_empty() {
8384 let keep = if stmt.distinct {
8385 None
8386 } else {
8387 stmt.limit_literal()
8388 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8389 };
8390 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8391 partial_sort_tagged(&mut tagged, keep, &descs);
8392 }
8393 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8394 if stmt.distinct {
8395 output_rows = dedup_rows(output_rows);
8396 }
8397 apply_offset_and_limit(
8398 &mut output_rows,
8399 stmt.offset_literal(),
8400 stmt.limit_literal(),
8401 );
8402 let columns: Vec<ColumnSchema> = projection
8403 .into_iter()
8404 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8405 .collect();
8406 Ok(QueryResult::Rows {
8407 columns,
8408 rows: output_rows,
8409 })
8410 }
8411}
8412
8413#[derive(Debug, Clone)]
8416struct ProjectedItem {
8417 expr: Expr,
8418 output_name: String,
8419 ty: DataType,
8420 nullable: bool,
8421}
8422
8423fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8429 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8430 for r in rows {
8431 if !out.iter().any(|seen| seen == &r) {
8432 out.push(r);
8433 }
8434 }
8435 out
8436}
8437
8438fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8442 match v {
8443 Value::Null => Ok(f64::INFINITY),
8444 Value::SmallInt(n) => Ok(f64::from(*n)),
8445 Value::Int(n) => Ok(f64::from(*n)),
8446 Value::Date(d) => Ok(f64::from(*d)),
8447 #[allow(clippy::cast_precision_loss)]
8448 Value::Timestamp(t) => Ok(*t as f64),
8449 #[allow(clippy::cast_precision_loss)]
8452 Value::Time(us) => Ok(*us as f64),
8453 Value::Year(y) => Ok(f64::from(*y)),
8457 #[allow(clippy::cast_precision_loss)]
8462 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8463 #[allow(clippy::cast_precision_loss)]
8465 Value::Money(c) => Ok(*c as f64),
8466 Value::Range { .. } => Err(EngineError::Unsupported(
8469 "ORDER BY of a range value is not supported in v7.17.0".into(),
8470 )),
8471 Value::Hstore(_) => Err(EngineError::Unsupported(
8473 "ORDER BY of a hstore value is not supported".into(),
8474 )),
8475 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8477 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8478 ),
8479 #[allow(clippy::cast_precision_loss)]
8480 Value::Numeric { scaled, scale } => {
8481 let mut divisor = 1.0_f64;
8487 for _ in 0..*scale {
8488 divisor *= 10.0;
8489 }
8490 Ok((*scaled as f64) / divisor)
8491 }
8492 #[allow(clippy::cast_precision_loss)]
8493 Value::BigInt(n) => Ok(*n as f64),
8494 Value::Float(x) => Ok(*x),
8495 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8496 Value::Text(s) => {
8497 let mut key: u64 = 0;
8501 for &b in s.as_bytes().iter().take(8) {
8502 key = (key << 8) | u64::from(b);
8503 }
8504 #[allow(clippy::cast_precision_loss)]
8505 Ok(key as f64)
8506 }
8507 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8508 Err(EngineError::Unsupported(
8509 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8510 ))
8511 }
8512 Value::Interval { .. } => Err(EngineError::Unsupported(
8513 "ORDER BY of an INTERVAL is not supported in v2.11 \
8514 (months vs micros has no single canonical ordering)"
8515 .into(),
8516 )),
8517 Value::Json(_) => Err(EngineError::Unsupported(
8518 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8519 )),
8520 _ => Err(EngineError::Unsupported(
8524 "ORDER BY of this value type is not supported".into(),
8525 )),
8526 }
8527}
8528
8529fn try_nsw_knn(
8543 stmt: &SelectStatement,
8544 table: &Table,
8545 schema_cols: &[ColumnSchema],
8546 table_alias: &str,
8547) -> Option<Vec<usize>> {
8548 if stmt.distinct {
8549 return None;
8550 }
8551 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8552 if limit == 0 {
8553 return None;
8554 }
8555 if stmt.order_by.len() != 1 {
8559 return None;
8560 }
8561 let order = &stmt.order_by[0];
8562 if order.desc {
8566 return None;
8567 }
8568 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8569 return None;
8570 };
8571 let metric = match op {
8572 BinOp::L2Distance => spg_storage::NswMetric::L2,
8573 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8574 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8575 _ => return None,
8576 };
8577 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8579 (lhs.as_ref(), rhs.as_ref())
8580 else {
8581 return None;
8582 };
8583 if let Some(q) = &col.qualifier
8584 && q != table_alias
8585 {
8586 return None;
8587 }
8588 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8589 let query = literal_to_vector(literal)?;
8590 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8591 if let Some(where_expr) = &stmt.where_ {
8592 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8596 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8597 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8598 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8599 for i in candidates {
8600 let row = &table.rows()[i];
8601 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8602 if matches!(cond, Value::Bool(true)) {
8603 kept.push(i);
8604 if kept.len() >= limit {
8605 break;
8606 }
8607 }
8608 }
8609 Some(kept)
8610 } else {
8611 Some(spg_storage::nsw_query(
8612 table, &idx.name, &query, limit, metric,
8613 ))
8614 }
8615}
8616
8617const NSW_OVER_FETCH_FLOOR: usize = 32;
8621
8622fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8625 match e {
8626 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8627 Expr::Cast { expr, .. } => literal_to_vector(expr),
8628 _ => None,
8629 }
8630}
8631
8632fn materialise_in_order(
8636 stmt: &SelectStatement,
8637 table: &Table,
8638 schema_cols: &[ColumnSchema],
8639 table_alias: &str,
8640 ordered_rows: &[usize],
8641) -> Result<QueryResult, EngineError> {
8642 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8643 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8644 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8645 for &i in ordered_rows {
8646 let row = &table.rows()[i];
8647 let mut values = Vec::with_capacity(projection.len());
8648 for p in &projection {
8649 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8650 }
8651 output_rows.push(Row::new(values));
8652 }
8653 apply_offset_and_limit(
8654 &mut output_rows,
8655 stmt.offset_literal(),
8656 stmt.limit_literal(),
8657 );
8658 let columns: Vec<ColumnSchema> = projection
8659 .into_iter()
8660 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8661 .collect();
8662 Ok(QueryResult::Rows {
8663 columns,
8664 rows: output_rows,
8665 })
8666}
8667
8668fn try_index_seek_positions(
8681 where_expr: &Expr,
8682 schema_cols: &[ColumnSchema],
8683 table: &Table,
8684 table_alias: &str,
8685) -> Option<Vec<usize>> {
8686 if let Expr::Binary {
8687 lhs,
8688 op: BinOp::And,
8689 rhs,
8690 } = where_expr
8691 {
8692 if let Some(p) = try_index_seek_positions(lhs, schema_cols, table, table_alias) {
8693 return Some(p);
8694 }
8695 return try_index_seek_positions(rhs, schema_cols, table, table_alias);
8696 }
8697 let Expr::Binary {
8698 lhs,
8699 op: BinOp::Eq,
8700 rhs,
8701 } = where_expr
8702 else {
8703 return None;
8704 };
8705 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8706 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8707 let idx = table.index_on(col_pos)?;
8708 let key = IndexKey::from_value(&value)?;
8709 let locators = idx.lookup_eq(&key);
8710 let mut out = Vec::with_capacity(locators.len());
8711 for loc in locators {
8712 match *loc {
8713 spg_storage::RowLocator::Hot(i) => out.push(i),
8714 spg_storage::RowLocator::Cold { .. } => return None,
8715 }
8716 }
8717 Some(out)
8718}
8719
8720fn try_index_seek<'a>(
8721 where_expr: &Expr,
8722 schema_cols: &[ColumnSchema],
8723 catalog: &'a Catalog,
8724 table: &'a Table,
8725 table_alias: &str,
8726) -> Option<Vec<Cow<'a, Row>>> {
8727 if let Expr::Binary {
8734 lhs,
8735 op: BinOp::And,
8736 rhs,
8737 } = where_expr
8738 {
8739 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8742 return Some(rows);
8743 }
8744 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8745 }
8746 let Expr::Binary {
8747 lhs,
8748 op: BinOp::Eq,
8749 rhs,
8750 } = where_expr
8751 else {
8752 return None;
8753 };
8754 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8755 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8756 let idx = table.index_on(col_pos)?;
8757 let key = IndexKey::from_value(&value)?;
8758 let locators = idx.lookup_eq(&key);
8759 let table_name = table.schema().name.as_str();
8760 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8768 for loc in locators {
8769 match *loc {
8770 spg_storage::RowLocator::Hot(i) => {
8771 if let Some(row) = table.rows().get(i) {
8772 out.push(Cow::Borrowed(row));
8773 }
8774 }
8775 spg_storage::RowLocator::Cold { segment_id, .. } => {
8776 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8777 out.push(Cow::Owned(row));
8778 }
8779 }
8780 }
8781 }
8782 Some(out)
8783}
8784
8785fn try_gin_seek<'a>(
8804 where_expr: &Expr,
8805 schema_cols: &[ColumnSchema],
8806 catalog: &'a Catalog,
8807 table: &'a Table,
8808 table_alias: &str,
8809 ctx: &eval::EvalContext<'_>,
8810) -> Option<Vec<Cow<'a, Row>>> {
8811 if let Expr::Binary {
8812 lhs,
8813 op: BinOp::And,
8814 rhs,
8815 } = where_expr
8816 {
8817 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8818 return Some(rows);
8819 }
8820 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8821 }
8822 if let Expr::Binary {
8831 lhs,
8832 op: BinOp::Or,
8833 rhs,
8834 } = where_expr
8835 {
8836 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8837 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8838 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8839 out.extend(left);
8840 out.extend(right);
8841 return Some(out);
8842 }
8843 let Expr::Binary {
8844 lhs,
8845 op: BinOp::TsMatch,
8846 rhs,
8847 } = where_expr
8848 else {
8849 return None;
8850 };
8851 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8856 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8857 let idx = table
8864 .indices()
8865 .iter()
8866 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8867 let candidates = gin_query_candidates(idx, &query)?;
8868 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8870 for loc in candidates {
8871 match loc {
8872 spg_storage::RowLocator::Hot(i) => {
8873 if let Some(row) = table.rows().get(i) {
8874 out.push(Cow::Borrowed(row));
8875 }
8876 }
8877 spg_storage::RowLocator::Cold { .. } => {}
8884 }
8885 }
8886 Some(out)
8887}
8888
8889fn try_trgm_seek<'a>(
8905 where_expr: &Expr,
8906 schema_cols: &[ColumnSchema],
8907 table: &'a Table,
8908 table_alias: &str,
8909) -> Option<Vec<Cow<'a, Row>>> {
8910 if let Expr::Binary {
8911 lhs,
8912 op: BinOp::And,
8913 rhs,
8914 } = where_expr
8915 {
8916 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
8917 return Some(rows);
8918 }
8919 return try_trgm_seek(rhs, schema_cols, table, table_alias);
8920 }
8921 let Expr::Like { expr, pattern, .. } = where_expr else {
8927 return None;
8928 };
8929 let Expr::Column(c) = expr.as_ref() else {
8931 return None;
8932 };
8933 if let Some(q) = &c.qualifier
8934 && q != table_alias
8935 {
8936 return None;
8937 }
8938 let col_pos = schema_cols
8939 .iter()
8940 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
8941 let idx = table
8943 .indices()
8944 .iter()
8945 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
8946 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
8950 return None;
8951 };
8952 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
8953 let mut iter = trigrams.iter();
8956 let first = iter.next()?;
8957 let mut acc: Vec<spg_storage::RowLocator> = {
8958 let mut v = idx.gin_trgm_lookup(first).to_vec();
8959 v.sort_by_key(locator_sort_key);
8960 v.dedup_by_key(|l| locator_sort_key(l));
8961 v
8962 };
8963 for tri in iter {
8964 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
8965 next.sort_by_key(locator_sort_key);
8966 next.dedup_by_key(|l| locator_sort_key(l));
8967 let mut merged: Vec<spg_storage::RowLocator> =
8969 Vec::with_capacity(acc.len().min(next.len()));
8970 let (mut i, mut j) = (0usize, 0usize);
8971 while i < acc.len() && j < next.len() {
8972 let lk = locator_sort_key(&acc[i]);
8973 let rk = locator_sort_key(&next[j]);
8974 match lk.cmp(&rk) {
8975 core::cmp::Ordering::Less => i += 1,
8976 core::cmp::Ordering::Greater => j += 1,
8977 core::cmp::Ordering::Equal => {
8978 merged.push(acc[i]);
8979 i += 1;
8980 j += 1;
8981 }
8982 }
8983 }
8984 acc = merged;
8985 if acc.is_empty() {
8986 break;
8987 }
8988 }
8989 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
8990 for loc in acc {
8991 if let spg_storage::RowLocator::Hot(i) = loc
8992 && let Some(row) = table.rows().get(i)
8993 {
8994 out.push(Cow::Borrowed(row));
8995 }
8996 }
8998 Some(out)
8999}
9000
9001fn resolve_gin_col_query(
9007 col_side: &Expr,
9008 query_side: &Expr,
9009 schema_cols: &[ColumnSchema],
9010 table_alias: &str,
9011 ctx: &eval::EvalContext<'_>,
9012) -> Option<(usize, spg_storage::TsQueryAst)> {
9013 let column = match col_side {
9018 Expr::Column(c) => c,
9019 Expr::FunctionCall { name, args }
9020 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
9021 {
9022 if let Expr::Column(c) = args.last().unwrap() {
9026 c
9027 } else {
9028 return None;
9029 }
9030 }
9031 _ => return None,
9032 };
9033 let c = column;
9034 if let Some(q) = &c.qualifier
9035 && q != table_alias
9036 {
9037 return None;
9038 }
9039 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9040 let empty_row = Row::new(Vec::new());
9044 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
9045 let Value::TsQuery(q) = v else { return None };
9046 Some((pos, q))
9047}
9048
9049fn gin_query_candidates(
9060 idx: &spg_storage::Index,
9061 query: &spg_storage::TsQueryAst,
9062) -> Option<Vec<spg_storage::RowLocator>> {
9063 use spg_storage::TsQueryAst;
9064 match query {
9065 TsQueryAst::Term { word, .. } => {
9066 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
9067 v.sort_by_key(locator_sort_key);
9068 v.dedup_by_key(|l| locator_sort_key(l));
9069 Some(v)
9070 }
9071 TsQueryAst::And(l, r) => {
9072 let mut left = gin_query_candidates(idx, l)?;
9073 let mut right = gin_query_candidates(idx, r)?;
9074 left.sort_by_key(locator_sort_key);
9075 right.sort_by_key(locator_sort_key);
9076 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
9078 let (mut i, mut j) = (0usize, 0usize);
9079 while i < left.len() && j < right.len() {
9080 let lk = locator_sort_key(&left[i]);
9081 let rk = locator_sort_key(&right[j]);
9082 match lk.cmp(&rk) {
9083 core::cmp::Ordering::Less => i += 1,
9084 core::cmp::Ordering::Greater => j += 1,
9085 core::cmp::Ordering::Equal => {
9086 out.push(left[i]);
9087 i += 1;
9088 j += 1;
9089 }
9090 }
9091 }
9092 Some(out)
9093 }
9094 TsQueryAst::Or(l, r) => {
9095 let mut out = gin_query_candidates(idx, l)?;
9096 out.extend(gin_query_candidates(idx, r)?);
9097 out.sort_by_key(locator_sort_key);
9098 out.dedup_by_key(|l| locator_sort_key(l));
9099 Some(out)
9100 }
9101 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
9106 }
9107}
9108
9109fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
9114 match *l {
9115 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
9116 spg_storage::RowLocator::Cold {
9117 segment_id,
9118 page_offset,
9119 } => (1, u64::from(segment_id), u64::from(page_offset)),
9120 }
9121}
9122
9123fn try_pk_predicate(
9135 where_expr: &Expr,
9136 schema_cols: &[ColumnSchema],
9137 table_alias: &str,
9138) -> Option<(usize, IndexKey)> {
9139 let Expr::Binary {
9140 lhs,
9141 op: BinOp::Eq,
9142 rhs,
9143 } = where_expr
9144 else {
9145 return None;
9146 };
9147 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9148 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9149 let key = IndexKey::from_value(&value)?;
9150 Some((col_pos, key))
9151}
9152
9153fn resolve_col_literal_pair(
9154 col_side: &Expr,
9155 lit_side: &Expr,
9156 schema_cols: &[ColumnSchema],
9157 table_alias: &str,
9158) -> Option<(usize, Value)> {
9159 let Expr::Column(c) = col_side else {
9160 return None;
9161 };
9162 if let Some(q) = &c.qualifier
9163 && q != table_alias
9164 {
9165 return None;
9166 }
9167 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9168 let Expr::Literal(l) = lit_side else {
9169 return None;
9170 };
9171 let v = match l {
9172 Literal::Integer(n) => {
9173 if let Ok(small) = i32::try_from(*n) {
9174 Value::Int(small)
9175 } else {
9176 Value::BigInt(*n)
9177 }
9178 }
9179 Literal::Float(x) => Value::Float(*x),
9180 Literal::String(s) => Value::Text(s.clone()),
9181 Literal::Bool(b) => Value::Bool(*b),
9182 Literal::Null => Value::Null,
9183 Literal::Vector(_)
9186 | Literal::Interval { .. }
9187 | Literal::TextArray(_)
9188 | Literal::IntArray(_)
9189 | Literal::BigIntArray(_) => return None,
9190 };
9191 Some((pos, v))
9192}
9193
9194fn resolve_projection_column<'a>(
9199 c: &ColumnName,
9200 schema_cols: &'a [ColumnSchema],
9201 table_alias: &str,
9202) -> Result<&'a ColumnSchema, EngineError> {
9203 if let Some(q) = &c.qualifier {
9204 let composite = alloc::format!("{q}.{name}", name = c.name);
9205 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9206 return Ok(s);
9207 }
9208 if q == table_alias
9211 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9212 {
9213 return Ok(s);
9214 }
9215 let prefix = alloc::format!("{q}.");
9219 let qualifier_known =
9220 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9221 if !qualifier_known {
9222 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9223 qualifier: q.clone(),
9224 }));
9225 }
9226 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9227 name: c.name.clone(),
9228 }));
9229 }
9230 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9231 return Ok(s);
9232 }
9233 let suffix = alloc::format!(".{name}", name = c.name);
9234 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9235 let first = matches.next();
9236 let extra = matches.next();
9237 match (first, extra) {
9238 (Some(s), None) => Ok(s),
9239 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9240 detail: alloc::format!("ambiguous column reference: {}", c.name),
9241 })),
9242 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9243 name: c.name.clone(),
9244 })),
9245 }
9246}
9247
9248fn build_projection(
9249 items: &[SelectItem],
9250 schema_cols: &[ColumnSchema],
9251 table_alias: &str,
9252) -> Result<Vec<ProjectedItem>, EngineError> {
9253 let mut out = Vec::new();
9254 for item in items {
9255 match item {
9256 SelectItem::Wildcard => {
9257 for col in schema_cols {
9258 out.push(ProjectedItem {
9259 expr: Expr::Column(ColumnName {
9260 qualifier: None,
9261 name: col.name.clone(),
9262 }),
9263 output_name: col.name.clone(),
9264 ty: col.ty,
9265 nullable: col.nullable,
9266 });
9267 }
9268 }
9269 SelectItem::Expr { expr, alias } => {
9270 if let Expr::Column(c) = expr {
9277 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9278 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9279 out.push(ProjectedItem {
9280 expr: expr.clone(),
9281 output_name,
9282 ty: sch.ty,
9283 nullable: sch.nullable,
9284 });
9285 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9286 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9287 out.push(ProjectedItem {
9288 expr: expr.clone(),
9289 output_name,
9290 ty: shape.ty,
9291 nullable: shape.nullable,
9292 });
9293 } else {
9294 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9295 out.push(ProjectedItem {
9296 expr: expr.clone(),
9297 output_name,
9298 ty: DataType::Text,
9299 nullable: true,
9300 });
9301 }
9302 }
9303 }
9304 }
9305 Ok(out)
9306}
9307
9308fn numeric_from_integer(
9312 n: i128,
9313 precision: u8,
9314 scale: u8,
9315 col_name: &str,
9316) -> Result<Value, EngineError> {
9317 let factor = pow10_i128(scale);
9318 let scaled = n.checked_mul(factor).ok_or_else(|| {
9319 EngineError::Unsupported(alloc::format!(
9320 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9321 ))
9322 })?;
9323 check_precision(scaled, precision, col_name)?;
9324 Ok(Value::Numeric { scaled, scale })
9325}
9326
9327#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9330fn numeric_from_float(
9331 x: f64,
9332 precision: u8,
9333 scale: u8,
9334 col_name: &str,
9335) -> Result<Value, EngineError> {
9336 if !x.is_finite() {
9337 return Err(EngineError::Unsupported(alloc::format!(
9338 "cannot store non-finite float in NUMERIC column `{col_name}`"
9339 )));
9340 }
9341 let mut factor = 1.0_f64;
9342 for _ in 0..scale {
9343 factor *= 10.0;
9344 }
9345 let shifted = x * factor;
9350 let biased = if shifted >= 0.0 {
9351 shifted + 0.5
9352 } else {
9353 shifted - 0.5
9354 };
9355 if !(-1e38..=1e38).contains(&biased) {
9358 return Err(EngineError::Unsupported(alloc::format!(
9359 "value {x} overflows NUMERIC range for column `{col_name}`"
9360 )));
9361 }
9362 let scaled = biased as i128;
9363 check_precision(scaled, precision, col_name)?;
9364 Ok(Value::Numeric { scaled, scale })
9365}
9366
9367fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9374 let s = s.trim();
9375 if s.is_empty() {
9376 return None;
9377 }
9378 let (negative, rest) = match s.as_bytes()[0] {
9379 b'-' => (true, &s[1..]),
9380 b'+' => (false, &s[1..]),
9381 _ => (false, s),
9382 };
9383 if rest.is_empty() {
9384 return None;
9385 }
9386 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9390 return None;
9391 }
9392 let (int_part, frac_part) = match rest.find('.') {
9393 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9394 None => (rest, ""),
9395 };
9396 if int_part.is_empty() && frac_part.is_empty() {
9397 return None;
9398 }
9399 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9400 return None;
9401 }
9402 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9403 return None;
9404 }
9405 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9406 if scale_u32 > u32::from(u8::MAX) {
9407 return None;
9408 }
9409 let scale = scale_u32 as u8;
9410 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9411 if negative {
9412 digits.push('-');
9413 }
9414 digits.push_str(int_part);
9415 digits.push_str(frac_part);
9416 let digits = if digits == "-" {
9418 return None;
9419 } else if digits.is_empty() {
9420 "0"
9421 } else {
9422 digits.as_str()
9423 };
9424 let mantissa: i128 = digits.parse().ok()?;
9425 Some((mantissa, scale))
9426}
9427
9428fn numeric_rescale(
9431 scaled: i128,
9432 src_scale: u8,
9433 precision: u8,
9434 dst_scale: u8,
9435 col_name: &str,
9436) -> Result<Value, EngineError> {
9437 let new_scaled = if dst_scale >= src_scale {
9438 let bump = pow10_i128(dst_scale - src_scale);
9439 scaled.checked_mul(bump).ok_or_else(|| {
9440 EngineError::Unsupported(alloc::format!(
9441 "overflow rescaling NUMERIC for column `{col_name}`"
9442 ))
9443 })?
9444 } else {
9445 let drop = pow10_i128(src_scale - dst_scale);
9446 let half = drop / 2;
9447 if scaled >= 0 {
9448 (scaled + half) / drop
9449 } else {
9450 (scaled - half) / drop
9451 }
9452 };
9453 check_precision(new_scaled, precision, col_name)?;
9454 Ok(Value::Numeric {
9455 scaled: new_scaled,
9456 scale: dst_scale,
9457 })
9458}
9459
9460const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9463 if scale == 0 {
9464 return scaled;
9465 }
9466 let factor = pow10_i128_const(scale);
9467 scaled / factor
9468}
9469
9470fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9474 if precision == 0 {
9475 return Ok(());
9476 }
9477 let limit = pow10_i128(precision);
9478 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9479 return Err(EngineError::Unsupported(alloc::format!(
9480 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9481 )));
9482 }
9483 Ok(())
9484}
9485
9486const fn pow10_i128_const(p: u8) -> i128 {
9487 let mut acc: i128 = 1;
9488 let mut i = 0;
9489 while i < p {
9490 acc *= 10;
9491 i += 1;
9492 }
9493 acc
9494}
9495
9496fn pow10_i128(p: u8) -> i128 {
9497 pow10_i128_const(p)
9498}
9499
9500impl Engine {
9515 #[allow(
9526 clippy::too_many_lines,
9527 clippy::type_complexity,
9528 clippy::needless_range_loop
9529 )] fn exec_select_with_window(
9531 &self,
9532 stmt: &SelectStatement,
9533 cancel: CancelToken<'_>,
9534 ) -> Result<QueryResult, EngineError> {
9535 let from = stmt.from.as_ref().ok_or_else(|| {
9536 EngineError::Unsupported("window functions require a FROM clause".into())
9537 })?;
9538 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9547 let filtered: Vec<Row>;
9548 if from.joins.is_empty() {
9549 let primary = &from.primary;
9550 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9551 StorageError::TableNotFound {
9552 name: primary.name.clone(),
9553 }
9554 })?;
9555 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9556 schema_cols_owned = table.schema().columns.clone();
9557 alias_opt = Some(alias);
9558 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9563 let mut owned: Vec<Row> = Vec::new();
9564 for (i, row) in table.rows().iter().enumerate() {
9565 if i.is_multiple_of(256) {
9566 cancel.check()?;
9567 }
9568 if let Some(w) = &stmt.where_ {
9569 let cond = eval::eval_expr(w, row, &ctx)?;
9570 if !matches!(cond, Value::Bool(true)) {
9571 continue;
9572 }
9573 }
9574 owned.push(row.clone());
9575 }
9576 filtered = owned;
9577 } else {
9578 let (combined_schema, rows) =
9579 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9580 schema_cols_owned = combined_schema;
9581 alias_opt = None;
9582 filtered = rows;
9583 }
9584 let schema_cols = &schema_cols_owned;
9585 let ctx = self.ev_ctx(schema_cols, alias_opt);
9586 let alias = alias_opt.unwrap_or("");
9587 let n_rows = filtered.len();
9588 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9592
9593 let mut window_nodes: Vec<Expr> = Vec::new();
9595 for item in &stmt.items {
9596 if let SelectItem::Expr { expr, .. } = item {
9597 collect_window_nodes(expr, &mut window_nodes);
9598 }
9599 }
9600
9601 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9604 for wnode in &window_nodes {
9605 let Expr::WindowFunction {
9606 name,
9607 args,
9608 partition_by,
9609 order_by,
9610 frame,
9611 null_treatment,
9612 } = wnode
9613 else {
9614 unreachable!("collect_window_nodes pushes only WindowFunction");
9615 };
9616 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9618 Vec::with_capacity(n_rows);
9619 for (i, row) in filtered.iter().enumerate() {
9620 let pkey: Vec<Value> = partition_by
9621 .iter()
9622 .map(|p| eval::eval_expr(p, row, &ctx))
9623 .collect::<Result<_, _>>()?;
9624 let okey: Vec<(Value, bool)> = order_by
9625 .iter()
9626 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9627 .collect::<Result<_, _>>()?;
9628 indexed.push((pkey, okey, i));
9629 }
9630 indexed.sort_by(|a, b| {
9633 let p_cmp = partition_key_cmp(&a.0, &b.0);
9634 if p_cmp != core::cmp::Ordering::Equal {
9635 return p_cmp;
9636 }
9637 order_key_cmp(&a.1, &b.1)
9638 });
9639 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9641 let mut p_start = 0;
9642 while p_start < indexed.len() {
9643 let mut p_end = p_start + 1;
9644 while p_end < indexed.len()
9645 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9646 == core::cmp::Ordering::Equal
9647 {
9648 p_end += 1;
9649 }
9650 compute_window_partition(
9652 name,
9653 args,
9654 !order_by.is_empty(),
9655 frame.as_ref(),
9656 *null_treatment,
9657 &indexed[p_start..p_end],
9658 &filtered_refs,
9659 &ctx,
9660 &mut out_vals,
9661 )?;
9662 p_start = p_end;
9663 }
9664 win_vals.push(out_vals);
9665 }
9666
9667 let mut ext_cols = schema_cols.clone();
9669 for i in 0..window_nodes.len() {
9670 ext_cols.push(ColumnSchema::new(
9671 alloc::format!("__win_{i}"),
9672 DataType::Text, true,
9674 ));
9675 }
9676 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9678 for i in 0..n_rows {
9679 let mut values = filtered[i].values.clone();
9680 for w in 0..window_nodes.len() {
9681 values.push(win_vals[w][i].clone());
9682 }
9683 ext_rows.push(Row::new(values));
9684 }
9685 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9687 for item in &stmt.items {
9688 let new_item = match item {
9689 SelectItem::Wildcard => SelectItem::Wildcard,
9690 SelectItem::Expr { expr, alias } => {
9691 let mut e = expr.clone();
9692 rewrite_window_to_columns(&mut e, &window_nodes);
9693 SelectItem::Expr {
9694 expr: e,
9695 alias: alias.clone(),
9696 }
9697 }
9698 };
9699 rewritten_items.push(new_item);
9700 }
9701
9702 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9708 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9709 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9710 for (i, row) in ext_rows.iter().enumerate() {
9711 if i.is_multiple_of(256) {
9712 cancel.check()?;
9713 }
9714 let mut values = Vec::with_capacity(projection.len());
9715 for p in &projection {
9716 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9717 }
9718 let order_keys = if stmt.order_by.is_empty() {
9719 Vec::new()
9720 } else {
9721 let mut keys = Vec::with_capacity(stmt.order_by.len());
9722 for o in &stmt.order_by {
9723 let mut e = o.expr.clone();
9724 rewrite_window_to_columns(&mut e, &window_nodes);
9725 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9726 keys.push(value_to_order_key(&key)?);
9727 }
9728 keys
9729 };
9730 tagged.push((order_keys, Row::new(values)));
9731 }
9732 if !stmt.order_by.is_empty() {
9734 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9735 sort_by_keys(&mut tagged, &descs);
9736 }
9737 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9738 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9739 let final_cols: Vec<ColumnSchema> = projection
9740 .into_iter()
9741 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9742 .collect();
9743 Ok(QueryResult::Rows {
9744 columns: final_cols,
9745 rows: out_rows,
9746 })
9747 }
9748
9749 fn exec_select_with_meta_views(
9766 &self,
9767 stmt: &SelectStatement,
9768 cancel: CancelToken<'_>,
9769 ) -> Result<QueryResult, EngineError> {
9770 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9771 collect_meta_view_names(stmt, &mut needed);
9772 let mut catalog = self.active_catalog().clone();
9773 for view in &needed {
9774 if catalog.get(view).is_some() {
9775 continue;
9776 }
9777 match view.as_str() {
9778 "__spg_info_columns" => {
9779 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9780 materialise_meta_view(&mut catalog, view, schema, rows)?;
9781 }
9782 "__spg_info_tables" => {
9783 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9784 materialise_meta_view(&mut catalog, view, schema, rows)?;
9785 }
9786 "__spg_pg_class" => {
9787 let (schema, rows) = synth_pg_class(self.active_catalog());
9788 materialise_meta_view(&mut catalog, view, schema, rows)?;
9789 }
9790 "__spg_pg_attribute" => {
9791 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9792 materialise_meta_view(&mut catalog, view, schema, rows)?;
9793 }
9794 "__spg_pg_type" => {
9797 let (schema, rows) = synth_pg_type(self.active_catalog());
9798 materialise_meta_view(&mut catalog, view, schema, rows)?;
9799 }
9800 "__spg_pg_proc" => {
9803 let (schema, rows) = synth_pg_proc(self.active_catalog());
9804 materialise_meta_view(&mut catalog, view, schema, rows)?;
9805 }
9806 "__spg_pg_namespace" => {
9809 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9810 materialise_meta_view(&mut catalog, view, schema, rows)?;
9811 }
9812 "__spg_pg_indexes" => {
9815 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9816 materialise_meta_view(&mut catalog, view, schema, rows)?;
9817 }
9818 "__spg_pg_index" => {
9821 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9822 materialise_meta_view(&mut catalog, view, schema, rows)?;
9823 }
9824 "__spg_pg_constraint" => {
9827 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9828 materialise_meta_view(&mut catalog, view, schema, rows)?;
9829 }
9830 "__spg_pg_database" => {
9835 let (schema, rows) = synth_pg_database(self.active_catalog());
9836 materialise_meta_view(&mut catalog, view, schema, rows)?;
9837 }
9838 "__spg_pg_roles" | "__spg_pg_user" => {
9839 let (schema, rows) = synth_pg_roles(self);
9840 materialise_meta_view(&mut catalog, view, schema, rows)?;
9841 }
9842 "__spg_pg_views" => {
9846 let (schema, rows) = synth_pg_views(self.active_catalog());
9847 materialise_meta_view(&mut catalog, view, schema, rows)?;
9848 }
9849 "__spg_pg_matviews" => {
9853 let (schema, _) = synth_pg_views(self.active_catalog());
9854 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9855 }
9856 "__spg_pg_extension" => {
9859 let (schema, rows) = synth_pg_extension();
9860 materialise_meta_view(&mut catalog, view, schema, rows)?;
9861 }
9862 "__spg_pg_settings" => {
9864 let (schema, rows) = synth_pg_settings(self);
9865 materialise_meta_view(&mut catalog, view, schema, rows)?;
9866 }
9867 "__spg_info_key_column_usage" => {
9869 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9870 materialise_meta_view(&mut catalog, view, schema, rows)?;
9871 }
9872 "__spg_info_referential_constraints" => {
9874 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9875 materialise_meta_view(&mut catalog, view, schema, rows)?;
9876 }
9877 "__spg_info_statistics" => {
9879 let (schema, rows) = synth_info_statistics(self.active_catalog());
9880 materialise_meta_view(&mut catalog, view, schema, rows)?;
9881 }
9882 "__spg_info_routines" => {
9884 let (schema, rows) = synth_info_routines();
9885 materialise_meta_view(&mut catalog, view, schema, rows)?;
9886 }
9887 "__spg_mysql_user" => {
9889 let (schema, rows) = synth_mysql_user(self);
9890 materialise_meta_view(&mut catalog, view, schema, rows)?;
9891 }
9892 "__spg_mysql_db" => {
9893 let (schema, rows) = synth_mysql_db();
9894 materialise_meta_view(&mut catalog, view, schema, rows)?;
9895 }
9896 _ => {
9897 return Err(EngineError::Unsupported(alloc::format!(
9898 "meta view {view:?} is not yet materialisable; \
9899 v7.16.2 covers information_schema.columns / .tables \
9900 and pg_catalog.pg_class / pg_attribute; \
9901 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9902 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
9903 pg_user / pg_views / pg_matviews / pg_settings"
9904 )));
9905 }
9906 }
9907 }
9908 let mut temp = Engine::restore(catalog);
9909 if let Some(c) = self.clock {
9910 temp = temp.with_clock(c);
9911 }
9912 if let Some(f) = self.salt_fn {
9913 temp = temp.with_salt_fn(f);
9914 }
9915 temp.meta_views_materialised = true;
9916 temp.exec_select_cancel(stmt, cancel)
9917 }
9918
9919 fn exec_with_ctes(
9920 &self,
9921 stmt: &SelectStatement,
9922 cancel: CancelToken<'_>,
9923 ) -> Result<QueryResult, EngineError> {
9924 cancel.check()?;
9925 let mut catalog = self.active_catalog().clone();
9926 for cte in &stmt.ctes {
9927 if catalog.get(&cte.name).is_some() {
9928 return Err(EngineError::Unsupported(alloc::format!(
9929 "CTE name {:?} shadows an existing table; rename the CTE",
9930 cte.name
9931 )));
9932 }
9933 let (columns, rows) = if cte.recursive {
9934 self.materialise_recursive_cte(cte, &catalog, cancel)?
9935 } else {
9936 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
9937 let QueryResult::Rows { columns, rows } = body_result else {
9938 return Err(EngineError::Unsupported(alloc::format!(
9939 "CTE {:?} body did not return rows",
9940 cte.name
9941 )));
9942 };
9943 (columns, rows)
9944 };
9945 let inferred = infer_column_types(&columns, &rows);
9950 let mut columns = inferred;
9951 if !cte.column_overrides.is_empty() {
9953 if cte.column_overrides.len() != columns.len() {
9954 return Err(EngineError::Unsupported(alloc::format!(
9955 "CTE {:?} column list has {} names but body returns {} columns",
9956 cte.name,
9957 cte.column_overrides.len(),
9958 columns.len()
9959 )));
9960 }
9961 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9962 col.name.clone_from(name);
9963 }
9964 }
9965 let schema = TableSchema::new(cte.name.clone(), columns);
9966 catalog.create_table(schema).map_err(EngineError::Storage)?;
9967 let table = catalog
9968 .get_mut(&cte.name)
9969 .expect("just-created CTE table must exist");
9970 for row in rows {
9971 table.insert(row).map_err(EngineError::Storage)?;
9972 }
9973 }
9974 let mut body = stmt.clone();
9977 body.ctes = Vec::new();
9978 let mut temp = Engine::restore(catalog);
9979 if let Some(c) = self.clock {
9980 temp = temp.with_clock(c);
9981 }
9982 if let Some(f) = self.salt_fn {
9983 temp = temp.with_salt_fn(f);
9984 }
9985 temp.exec_select_cancel(&body, cancel)
9986 }
9987
9988 #[allow(clippy::too_many_lines)]
9998 fn materialise_recursive_cte(
9999 &self,
10000 cte: &spg_sql::ast::Cte,
10001 base_catalog: &Catalog,
10002 cancel: CancelToken<'_>,
10003 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
10004 const MAX_TOTAL_ROWS: usize = 1_000_000;
10005 const MAX_ITERATIONS: usize = 100_000;
10006 cancel.check()?;
10007 if cte.body.unions.is_empty() {
10008 return Err(EngineError::Unsupported(alloc::format!(
10009 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
10010 cte.name
10011 )));
10012 }
10013 let mut anchor = cte.body.clone();
10015 let union_terms = core::mem::take(&mut anchor.unions);
10016 anchor.ctes = Vec::new();
10017 if select_refers_to(&anchor, &cte.name) {
10019 return Err(EngineError::Unsupported(alloc::format!(
10020 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
10021 cte.name
10022 )));
10023 }
10024 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
10025 let QueryResult::Rows {
10026 columns: anchor_cols,
10027 rows: anchor_rows,
10028 } = anchor_result
10029 else {
10030 return Err(EngineError::Unsupported(alloc::format!(
10031 "WITH RECURSIVE {:?}: anchor did not return rows",
10032 cte.name
10033 )));
10034 };
10035 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
10039 if !cte.column_overrides.is_empty() {
10040 if cte.column_overrides.len() != columns.len() {
10041 return Err(EngineError::Unsupported(alloc::format!(
10042 "CTE {:?} column list has {} names but anchor returns {} columns",
10043 cte.name,
10044 cte.column_overrides.len(),
10045 columns.len()
10046 )));
10047 }
10048 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
10049 col.name.clone_from(name);
10050 }
10051 }
10052 let mut all_rows: Vec<Row> = anchor_rows.clone();
10053 let mut working_set: Vec<Row> = anchor_rows;
10054 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
10055 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
10058 if !all_union_all {
10059 for r in &all_rows {
10060 seen.insert(encode_row_key(r));
10061 }
10062 }
10063 for iter in 0..MAX_ITERATIONS {
10064 cancel.check()?;
10065 if working_set.is_empty() {
10066 break;
10067 }
10068 let mut iter_catalog = base_catalog.clone();
10070 let schema = TableSchema::new(cte.name.clone(), columns.clone());
10071 iter_catalog
10072 .create_table(schema)
10073 .map_err(EngineError::Storage)?;
10074 {
10075 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
10076 for row in &working_set {
10077 table.insert(row.clone()).map_err(EngineError::Storage)?;
10078 }
10079 }
10080 let mut iter_engine = Engine::restore(iter_catalog);
10081 if let Some(c) = self.clock {
10082 iter_engine = iter_engine.with_clock(c);
10083 }
10084 if let Some(f) = self.salt_fn {
10085 iter_engine = iter_engine.with_salt_fn(f);
10086 }
10087 let mut next_set: Vec<Row> = Vec::new();
10089 for (_, term) in &union_terms {
10090 let mut term = term.clone();
10091 term.ctes = Vec::new();
10092 let r = iter_engine.exec_select_cancel(&term, cancel)?;
10093 let QueryResult::Rows {
10094 columns: rc,
10095 rows: rs,
10096 } = r
10097 else {
10098 return Err(EngineError::Unsupported(alloc::format!(
10099 "WITH RECURSIVE {:?}: recursive term did not return rows",
10100 cte.name
10101 )));
10102 };
10103 if rc.len() != columns.len() {
10104 return Err(EngineError::Unsupported(alloc::format!(
10105 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
10106 cte.name,
10107 rc.len(),
10108 columns.len()
10109 )));
10110 }
10111 for row in rs {
10112 if !all_union_all {
10113 let key = encode_row_key(&row);
10114 if !seen.insert(key) {
10115 continue;
10116 }
10117 }
10118 next_set.push(row);
10119 }
10120 }
10121 if next_set.is_empty() {
10122 break;
10123 }
10124 all_rows.extend(next_set.iter().cloned());
10125 working_set = next_set;
10126 if all_rows.len() > MAX_TOTAL_ROWS {
10127 return Err(EngineError::Unsupported(alloc::format!(
10128 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
10129 cte.name
10130 )));
10131 }
10132 if iter + 1 == MAX_ITERATIONS {
10133 return Err(EngineError::Unsupported(alloc::format!(
10134 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
10135 cte.name
10136 )));
10137 }
10138 }
10139 Ok((columns, all_rows))
10140 }
10141
10142 fn resolve_select_subqueries(
10143 &self,
10144 stmt: &mut SelectStatement,
10145 cancel: CancelToken<'_>,
10146 ) -> Result<(), EngineError> {
10147 for item in &mut stmt.items {
10148 if let SelectItem::Expr { expr, .. } = item {
10149 self.resolve_expr_subqueries(expr, cancel)?;
10150 }
10151 }
10152 if let Some(w) = &mut stmt.where_ {
10153 self.resolve_expr_subqueries(w, cancel)?;
10154 }
10155 if let Some(gs) = &mut stmt.group_by {
10156 for g in gs {
10157 self.resolve_expr_subqueries(g, cancel)?;
10158 }
10159 }
10160 if let Some(h) = &mut stmt.having {
10161 self.resolve_expr_subqueries(h, cancel)?;
10162 }
10163 for o in &mut stmt.order_by {
10164 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10165 }
10166 for (_, peer) in &mut stmt.unions {
10167 self.resolve_select_subqueries(peer, cancel)?;
10168 }
10169 Ok(())
10170 }
10171
10172 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10174 &self,
10175 e: &mut Expr,
10176 cancel: CancelToken<'_>,
10177 ) -> Result<(), EngineError> {
10178 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10180 *e = replacement;
10181 return Ok(());
10182 }
10183 match e {
10184 Expr::Binary { lhs, rhs, .. } => {
10185 self.resolve_expr_subqueries(lhs, cancel)?;
10186 self.resolve_expr_subqueries(rhs, cancel)?;
10187 }
10188 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10189 self.resolve_expr_subqueries(expr, cancel)?;
10190 }
10191 Expr::FunctionCall { args, .. } => {
10192 for a in args {
10193 self.resolve_expr_subqueries(a, cancel)?;
10194 }
10195 }
10196 Expr::Like { expr, pattern, .. } => {
10197 self.resolve_expr_subqueries(expr, cancel)?;
10198 self.resolve_expr_subqueries(pattern, cancel)?;
10199 }
10200 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10201 Expr::WindowFunction {
10204 args,
10205 partition_by,
10206 order_by,
10207 ..
10208 } => {
10209 for a in args {
10210 self.resolve_expr_subqueries(a, cancel)?;
10211 }
10212 for p in partition_by {
10213 self.resolve_expr_subqueries(p, cancel)?;
10214 }
10215 for (e, _) in order_by {
10216 self.resolve_expr_subqueries(e, cancel)?;
10217 }
10218 }
10219 Expr::ScalarSubquery(_)
10223 | Expr::Exists { .. }
10224 | Expr::InSubquery { .. }
10225 | Expr::Literal(_)
10226 | Expr::Placeholder(_)
10227 | Expr::Column(_) => {}
10228 Expr::Array(items) => {
10230 for elem in items {
10231 self.resolve_expr_subqueries(elem, cancel)?;
10232 }
10233 }
10234 Expr::ArraySubscript { target, index } => {
10235 self.resolve_expr_subqueries(target, cancel)?;
10236 self.resolve_expr_subqueries(index, cancel)?;
10237 }
10238 Expr::AnyAll { expr, array, .. } => {
10239 self.resolve_expr_subqueries(expr, cancel)?;
10240 self.resolve_expr_subqueries(array, cancel)?;
10241 }
10242 Expr::Case {
10243 operand,
10244 branches,
10245 else_branch,
10246 } => {
10247 if let Some(o) = operand {
10248 self.resolve_expr_subqueries(o, cancel)?;
10249 }
10250 for (w, t) in branches {
10251 self.resolve_expr_subqueries(w, cancel)?;
10252 self.resolve_expr_subqueries(t, cancel)?;
10253 }
10254 if let Some(e) = else_branch {
10255 self.resolve_expr_subqueries(e, cancel)?;
10256 }
10257 }
10258 }
10259 Ok(())
10260 }
10261
10262 fn eval_expr_with_correlated(
10270 &self,
10271 expr: &Expr,
10272 row: &Row,
10273 ctx: &EvalContext<'_>,
10274 cancel: CancelToken<'_>,
10275 memo: Option<&mut memoize::MemoizeCache>,
10276 ) -> Result<Value, EngineError> {
10277 if !expr_has_subquery(expr) {
10278 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10279 }
10280 let mut e = expr.clone();
10281 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10282 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10283 }
10284
10285 fn resolve_correlated_in_expr(
10286 &self,
10287 e: &mut Expr,
10288 row: &Row,
10289 ctx: &EvalContext<'_>,
10290 cancel: CancelToken<'_>,
10291 mut memo: Option<&mut memoize::MemoizeCache>,
10292 ) -> Result<(), EngineError> {
10293 match e {
10294 Expr::ScalarSubquery(inner) => {
10295 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10300 subquery_repr: alloc::format!("{}", **inner),
10301 outer_values: row.values.clone(),
10302 });
10303 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10304 && let Some(cached) = cache.get(k)
10305 {
10306 *e = value_to_literal_expr(cached)?;
10307 return Ok(());
10308 }
10309 let mut s = (**inner).clone();
10310 substitute_outer_columns(&mut s, row, ctx);
10311 let r = self.exec_select_cancel(&s, cancel)?;
10312 let QueryResult::Rows { rows, .. } = r else {
10313 return Err(EngineError::Unsupported(
10314 "scalar subquery: inner did not return rows".into(),
10315 ));
10316 };
10317 let value = match rows.as_slice() {
10318 [] => Value::Null,
10319 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10320 _ => {
10321 return Err(EngineError::Unsupported(alloc::format!(
10322 "scalar subquery returned {} rows; expected 0 or 1",
10323 rows.len()
10324 )));
10325 }
10326 };
10327 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10328 cache.insert(k, value.clone());
10329 }
10330 *e = value_to_literal_expr(value)?;
10331 }
10332 Expr::Exists { subquery, negated } => {
10333 let mut s = (**subquery).clone();
10334 substitute_outer_columns(&mut s, row, ctx);
10335 let r = self.exec_select_cancel(&s, cancel)?;
10336 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10337 let bit = if *negated { !exists } else { exists };
10338 *e = Expr::Literal(Literal::Bool(bit));
10339 }
10340 Expr::InSubquery {
10341 expr: lhs,
10342 subquery,
10343 negated,
10344 } => {
10345 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10346 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10347 let mut s = (**subquery).clone();
10348 substitute_outer_columns(&mut s, row, ctx);
10349 let r = self.exec_select_cancel(&s, cancel)?;
10350 let QueryResult::Rows { columns, rows, .. } = r else {
10351 return Err(EngineError::Unsupported(
10352 "IN-subquery: inner did not return rows".into(),
10353 ));
10354 };
10355 if columns.len() != 1 {
10356 return Err(EngineError::Unsupported(alloc::format!(
10357 "IN-subquery must project exactly one column; got {}",
10358 columns.len()
10359 )));
10360 }
10361 let mut found = false;
10362 let mut any_null = false;
10363 for r0 in rows {
10364 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10365 if v.is_null() {
10366 any_null = true;
10367 continue;
10368 }
10369 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10370 found = true;
10371 break;
10372 }
10373 }
10374 let bit = if found {
10375 !*negated
10376 } else if any_null {
10377 return Err(EngineError::Unsupported(
10378 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10379 ));
10380 } else {
10381 *negated
10382 };
10383 *e = Expr::Literal(Literal::Bool(bit));
10384 }
10385 Expr::Binary { lhs, rhs, .. } => {
10386 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10387 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10388 }
10389 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10390 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10391 }
10392 Expr::Like { expr, pattern, .. } => {
10393 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10394 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10395 }
10396 Expr::FunctionCall { args, .. } => {
10397 for a in args {
10398 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10399 }
10400 }
10401 Expr::Extract { source, .. } => {
10402 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10403 }
10404 Expr::WindowFunction { .. }
10405 | Expr::Literal(_)
10406 | Expr::Placeholder(_)
10407 | Expr::Column(_) => {}
10408 Expr::Array(items) => {
10410 for elem in items {
10411 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10412 }
10413 }
10414 Expr::ArraySubscript { target, index } => {
10415 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10416 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10417 }
10418 Expr::AnyAll { expr, array, .. } => {
10419 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10420 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10421 }
10422 Expr::Case {
10423 operand,
10424 branches,
10425 else_branch,
10426 } => {
10427 if let Some(o) = operand {
10428 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10429 }
10430 for (w, t) in branches {
10431 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10432 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10433 }
10434 if let Some(e) = else_branch {
10435 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10436 }
10437 }
10438 }
10439 Ok(())
10440 }
10441
10442 fn subquery_replacement(
10443 &self,
10444 e: &Expr,
10445 cancel: CancelToken<'_>,
10446 ) -> Result<Option<Expr>, EngineError> {
10447 match e {
10448 Expr::ScalarSubquery(inner) => {
10449 let mut s = (**inner).clone();
10450 self.resolve_select_subqueries(&mut s, cancel)?;
10453 let r = match self.exec_bare_select_cancel(&s, cancel) {
10454 Ok(r) => r,
10455 Err(e) if is_correlation_error(&e) => return Ok(None),
10456 Err(e) => return Err(e),
10457 };
10458 let QueryResult::Rows { rows, .. } = r else {
10459 return Err(EngineError::Unsupported(
10460 "scalar subquery: inner statement did not return rows".into(),
10461 ));
10462 };
10463 let value = match rows.as_slice() {
10464 [] => Value::Null,
10465 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10466 _ => {
10467 return Err(EngineError::Unsupported(alloc::format!(
10468 "scalar subquery returned {} rows; expected 0 or 1",
10469 rows.len()
10470 )));
10471 }
10472 };
10473 Ok(Some(value_to_literal_expr(value)?))
10474 }
10475 Expr::Exists { subquery, negated } => {
10476 let mut s = (**subquery).clone();
10477 self.resolve_select_subqueries(&mut s, cancel)?;
10478 let r = match self.exec_bare_select_cancel(&s, cancel) {
10479 Ok(r) => r,
10480 Err(e) if is_correlation_error(&e) => return Ok(None),
10481 Err(e) => return Err(e),
10482 };
10483 let exists = match r {
10484 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10485 QueryResult::CommandOk { .. } => false,
10486 };
10487 let bit = if *negated { !exists } else { exists };
10488 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10489 }
10490 Expr::InSubquery {
10491 expr,
10492 subquery,
10493 negated,
10494 } => {
10495 let mut s = (**subquery).clone();
10496 self.resolve_select_subqueries(&mut s, cancel)?;
10497 let r = match self.exec_bare_select_cancel(&s, cancel) {
10498 Ok(r) => r,
10499 Err(e) if is_correlation_error(&e) => return Ok(None),
10500 Err(e) => return Err(e),
10501 };
10502 let QueryResult::Rows { columns, rows, .. } = r else {
10503 return Err(EngineError::Unsupported(
10504 "IN-subquery: inner statement did not return rows".into(),
10505 ));
10506 };
10507 if columns.len() != 1 {
10508 return Err(EngineError::Unsupported(alloc::format!(
10509 "IN-subquery must project exactly one column; got {}",
10510 columns.len()
10511 )));
10512 }
10513 let mut acc: Option<Expr> = None;
10516 for row in rows {
10517 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10518 let lit = value_to_literal_expr(v)?;
10519 let cmp = Expr::Binary {
10520 lhs: expr.clone(),
10521 op: BinOp::Eq,
10522 rhs: Box::new(lit),
10523 };
10524 acc = Some(match acc {
10525 None => cmp,
10526 Some(prev) => Expr::Binary {
10527 lhs: Box::new(prev),
10528 op: BinOp::Or,
10529 rhs: Box::new(cmp),
10530 },
10531 });
10532 }
10533 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10534 let final_expr = if *negated {
10535 Expr::Unary {
10536 op: UnOp::Not,
10537 expr: Box::new(combined),
10538 }
10539 } else {
10540 combined
10541 };
10542 Ok(Some(final_expr))
10543 }
10544 _ => Ok(None),
10545 }
10546 }
10547}
10548
10549fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10561 if let Some(from) = &stmt.from
10562 && from_refers_to(from, target)
10563 {
10564 return true;
10565 }
10566 for (_, peer) in &stmt.unions {
10567 if select_refers_to(peer, target) {
10568 return true;
10569 }
10570 }
10571 for item in &stmt.items {
10572 if let SelectItem::Expr { expr, .. } = item
10573 && expr_refers_to(expr, target)
10574 {
10575 return true;
10576 }
10577 }
10578 if let Some(w) = &stmt.where_
10579 && expr_refers_to(w, target)
10580 {
10581 return true;
10582 }
10583 false
10584}
10585
10586fn from_refers_to(from: &FromClause, target: &str) -> bool {
10587 if from.primary.name.eq_ignore_ascii_case(target) {
10588 return true;
10589 }
10590 from.joins
10591 .iter()
10592 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10593}
10594
10595fn expr_refers_to(e: &Expr, target: &str) -> bool {
10596 match e {
10597 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10598 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10599 select_refers_to(subquery, target)
10600 }
10601 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10602 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10603 expr_refers_to(expr, target)
10604 }
10605 Expr::Like { expr, pattern, .. } => {
10606 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10607 }
10608 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10609 Expr::Extract { source, .. } => expr_refers_to(source, target),
10610 Expr::WindowFunction {
10611 args,
10612 partition_by,
10613 order_by,
10614 ..
10615 } => {
10616 args.iter().any(|a| expr_refers_to(a, target))
10617 || partition_by.iter().any(|p| expr_refers_to(p, target))
10618 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10619 }
10620 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10621 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10622 Expr::ArraySubscript { target: t, index } => {
10623 expr_refers_to(t, target) || expr_refers_to(index, target)
10624 }
10625 Expr::AnyAll { expr, array, .. } => {
10626 expr_refers_to(expr, target) || expr_refers_to(array, target)
10627 }
10628 Expr::Case {
10629 operand,
10630 branches,
10631 else_branch,
10632 } => {
10633 operand
10634 .as_deref()
10635 .is_some_and(|o| expr_refers_to(o, target))
10636 || branches
10637 .iter()
10638 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10639 || else_branch
10640 .as_deref()
10641 .is_some_and(|e| expr_refers_to(e, target))
10642 }
10643 }
10644}
10645
10646fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10657 let s = match ty {
10658 DataType::Int => "integer",
10659 DataType::BigInt => "bigint",
10660 DataType::SmallInt => "smallint",
10661 DataType::Float => "double precision",
10662 DataType::Bool => "boolean",
10663 DataType::Text => "text",
10664 DataType::Varchar(_) => "character varying",
10665 DataType::Date => "date",
10666 DataType::Timestamp => "timestamp without time zone",
10667 DataType::Timestamptz => "timestamp with time zone",
10668 DataType::Json => "jsonb",
10669 DataType::Bytes => "bytea",
10670 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10671 DataType::TsVector => "tsvector",
10672 DataType::TsQuery => "tsquery",
10673 DataType::Vector { .. } => "USER-DEFINED",
10674 _ => "USER-DEFINED",
10677 };
10678 alloc::string::String::from(s)
10679}
10680
10681fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10688 let schema = alloc::vec![
10689 ColumnSchema::new("table_catalog", DataType::Text, false),
10690 ColumnSchema::new("table_schema", DataType::Text, false),
10691 ColumnSchema::new("table_name", DataType::Text, false),
10692 ColumnSchema::new("column_name", DataType::Text, false),
10693 ColumnSchema::new("ordinal_position", DataType::Int, false),
10694 ColumnSchema::new("is_nullable", DataType::Text, false),
10695 ColumnSchema::new("data_type", DataType::Text, false),
10696 ];
10697 let mut rows: Vec<Row> = Vec::new();
10698 for tname in cat.table_names() {
10699 let Some(t) = cat.get(&tname) else { continue };
10700 for (i, col) in t.schema().columns.iter().enumerate() {
10701 #[allow(clippy::cast_possible_wrap)]
10702 let ordinal = (i + 1) as i32;
10703 rows.push(Row::new(alloc::vec![
10704 Value::Text("spg".into()),
10705 Value::Text("public".into()),
10706 Value::Text(tname.clone()),
10707 Value::Text(col.name.clone()),
10708 Value::Int(ordinal),
10709 Value::Text(if col.nullable {
10710 "YES".into()
10711 } else {
10712 "NO".into()
10713 }),
10714 Value::Text(pg_data_type_text(col.ty)),
10715 ]));
10716 }
10717 }
10718 (schema, rows)
10719}
10720
10721fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10723 let schema = alloc::vec![
10724 ColumnSchema::new("table_catalog", DataType::Text, false),
10725 ColumnSchema::new("table_schema", DataType::Text, false),
10726 ColumnSchema::new("table_name", DataType::Text, false),
10727 ColumnSchema::new("table_type", DataType::Text, false),
10728 ];
10729 let mut rows: Vec<Row> = Vec::new();
10730 for tname in cat.table_names() {
10731 rows.push(Row::new(alloc::vec![
10732 Value::Text("spg".into()),
10733 Value::Text("public".into()),
10734 Value::Text(tname.clone()),
10735 Value::Text("BASE TABLE".into()),
10736 ]));
10737 }
10738 (schema, rows)
10739}
10740
10741fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10745 let schema = alloc::vec![
10746 ColumnSchema::new("relname", DataType::Text, false),
10747 ColumnSchema::new("relkind", DataType::Text, false),
10748 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10749 ];
10750 let mut rows: Vec<Row> = Vec::new();
10751 for tname in cat.table_names() {
10752 rows.push(Row::new(alloc::vec![
10753 Value::Text(tname.clone()),
10754 Value::Text("r".into()),
10755 Value::BigInt(2200), ]));
10757 }
10758 (schema, rows)
10759}
10760
10761fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10765 let schema = alloc::vec![
10766 ColumnSchema::new("attrelid", DataType::Text, false),
10767 ColumnSchema::new("attname", DataType::Text, false),
10768 ColumnSchema::new("attnum", DataType::Int, false),
10769 ColumnSchema::new("atttypid", DataType::Text, false),
10770 ColumnSchema::new("attnotnull", DataType::Bool, false),
10771 ];
10772 let mut rows: Vec<Row> = Vec::new();
10773 for tname in cat.table_names() {
10774 let Some(t) = cat.get(&tname) else { continue };
10775 for (i, col) in t.schema().columns.iter().enumerate() {
10776 #[allow(clippy::cast_possible_wrap)]
10777 let ordinal = (i + 1) as i32;
10778 rows.push(Row::new(alloc::vec![
10779 Value::Text(tname.clone()),
10780 Value::Text(col.name.clone()),
10781 Value::Int(ordinal),
10782 Value::Text(pg_data_type_text(col.ty)),
10783 Value::Bool(!col.nullable),
10784 ]));
10785 }
10786 }
10787 (schema, rows)
10788}
10789
10790fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10807 let schema = alloc::vec![
10808 ColumnSchema::new("oid", DataType::BigInt, false),
10809 ColumnSchema::new("typname", DataType::Text, false),
10810 ColumnSchema::new("typlen", DataType::SmallInt, false),
10811 ColumnSchema::new("typtype", DataType::Text, false),
10812 ColumnSchema::new("typcategory", DataType::Text, false),
10813 ColumnSchema::new("typelem", DataType::BigInt, false),
10814 ColumnSchema::new("typarray", DataType::BigInt, false),
10815 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10816 ];
10817 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10820 (16, "bool", 1, "b", "B", 0, 1000),
10822 (17, "bytea", -1, "b", "U", 0, 1001),
10823 (18, "char", 1, "b", "S", 0, 1002),
10824 (19, "name", 64, "b", "S", 0, 1003),
10825 (20, "int8", 8, "b", "N", 0, 1016),
10826 (21, "int2", 2, "b", "N", 0, 1005),
10827 (23, "int4", 4, "b", "N", 0, 1007),
10828 (24, "regproc", 4, "b", "N", 0, 1008),
10829 (25, "text", -1, "b", "S", 0, 1009),
10830 (26, "oid", 4, "b", "N", 0, 1028),
10831 (114, "json", -1, "b", "U", 0, 199),
10832 (142, "xml", -1, "b", "U", 0, 143),
10833 (700, "float4", 4, "b", "N", 0, 1021),
10834 (701, "float8", 8, "b", "N", 0, 1022),
10835 (650, "cidr", -1, "b", "I", 0, 651),
10836 (869, "inet", -1, "b", "I", 0, 1041),
10837 (829, "macaddr", 6, "b", "U", 0, 1040),
10838 (1042, "bpchar", -1, "b", "S", 0, 1014),
10839 (1043, "varchar", -1, "b", "S", 0, 1015),
10840 (1082, "date", 4, "b", "D", 0, 1182),
10841 (1083, "time", 8, "b", "D", 0, 1183),
10842 (1114, "timestamp", 8, "b", "D", 0, 1115),
10843 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10844 (1186, "interval", 16, "b", "T", 0, 1187),
10845 (1266, "timetz", 12, "b", "D", 0, 1270),
10846 (1700, "numeric", -1, "b", "N", 0, 1231),
10847 (790, "money", 8, "b", "N", 0, 791),
10848 (2950, "uuid", 16, "b", "U", 0, 2951),
10849 (3802, "jsonb", -1, "b", "U", 0, 3807),
10850 (3614, "tsvector", -1, "b", "U", 0, 3643),
10851 (3615, "tsquery", -1, "b", "U", 0, 3645),
10852 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10854 (3910, "tsrange", -1, "r", "R", 0, 3911),
10855 (3904, "int4range", -1, "r", "R", 0, 3905),
10856 (3926, "int8range", -1, "r", "R", 0, 3927),
10857 (3906, "numrange", -1, "r", "R", 0, 3907),
10858 (3912, "daterange", -1, "r", "R", 0, 3913),
10859 ];
10860 let arrays: &[(i64, &str, i64)] = &[
10863 (1000, "_bool", 16),
10864 (1001, "_bytea", 17),
10865 (1002, "_char", 18),
10866 (1003, "_name", 19),
10867 (1016, "_int8", 20),
10868 (1005, "_int2", 21),
10869 (1007, "_int4", 23),
10870 (1008, "_regproc", 24),
10871 (1009, "_text", 25),
10872 (1028, "_oid", 26),
10873 (199, "_json", 114),
10874 (143, "_xml", 142),
10875 (1021, "_float4", 700),
10876 (1022, "_float8", 701),
10877 (651, "_cidr", 650),
10878 (1041, "_inet", 869),
10879 (1040, "_macaddr", 829),
10880 (1014, "_bpchar", 1042),
10881 (1015, "_varchar", 1043),
10882 (1182, "_date", 1082),
10883 (1183, "_time", 1083),
10884 (1115, "_timestamp", 1114),
10885 (1185, "_timestamptz", 1184),
10886 (1187, "_interval", 1186),
10887 (1270, "_timetz", 1266),
10888 (1231, "_numeric", 1700),
10889 (791, "_money", 790),
10890 (2951, "_uuid", 2950),
10891 (3807, "_jsonb", 3802),
10892 (3643, "_tsvector", 3614),
10893 (3645, "_tsquery", 3615),
10894 ];
10895 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10896 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10897 rows.push(Row::new(alloc::vec![
10898 Value::BigInt(oid),
10899 Value::Text(name.into()),
10900 Value::SmallInt(len),
10901 Value::Text(ty.into()),
10902 Value::Text(cat.into()),
10903 Value::BigInt(elem),
10904 Value::BigInt(arr),
10905 Value::BigInt(2200),
10906 ]));
10907 }
10908 for &(oid, name, elem) in arrays {
10909 rows.push(Row::new(alloc::vec![
10910 Value::BigInt(oid),
10911 Value::Text(name.into()),
10912 Value::SmallInt(-1),
10913 Value::Text("b".into()),
10914 Value::Text("A".into()),
10915 Value::BigInt(elem),
10916 Value::BigInt(0),
10917 Value::BigInt(2200),
10918 ]));
10919 }
10920 (schema, rows)
10921}
10922
10923fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10937 let schema = alloc::vec![
10938 ColumnSchema::new("oid", DataType::BigInt, false),
10939 ColumnSchema::new("proname", DataType::Text, false),
10940 ColumnSchema::new("pronamespace", DataType::BigInt, false),
10941 ColumnSchema::new("prokind", DataType::Text, false),
10942 ColumnSchema::new("pronargs", DataType::Int, false),
10943 ColumnSchema::new("prorettype", DataType::BigInt, false),
10944 ];
10945 let funcs: &[(i64, &str, &str, i32, i64)] = &[
10948 (1318, "length", "f", 1, 23),
10950 (871, "upper", "f", 1, 25),
10951 (870, "lower", "f", 1, 25),
10952 (936, "substring", "f", 3, 25),
10953 (937, "substring", "f", 2, 25),
10954 (3055, "btrim", "f", 1, 25),
10955 (885, "btrim", "f", 2, 25),
10956 (3056, "ltrim", "f", 1, 25),
10957 (875, "ltrim", "f", 2, 25),
10958 (3057, "rtrim", "f", 1, 25),
10959 (876, "rtrim", "f", 2, 25),
10960 (1397, "abs", "f", 1, 23),
10961 (1396, "abs", "f", 1, 20),
10962 (1606, "round", "f", 1, 1700),
10963 (1707, "round", "f", 2, 1700),
10964 (2308, "ceil", "f", 1, 701),
10965 (2309, "ceiling", "f", 1, 701),
10966 (2310, "floor", "f", 1, 701),
10967 (1376, "sqrt", "f", 1, 701),
10968 (1369, "ln", "f", 1, 701),
10969 (1373, "exp", "f", 1, 701),
10970 (1368, "power", "f", 2, 701),
10971 (2228, "random", "f", 0, 701),
10972 (1299, "now", "f", 0, 1184),
10974 (1274, "current_timestamp", "f", 0, 1184),
10975 (1140, "current_date", "f", 0, 1082),
10976 (2050, "current_time", "f", 0, 1083),
10977 (1158, "date_trunc", "f", 2, 1184),
10978 (1171, "date_part", "f", 2, 701),
10979 (1172, "age", "f", 1, 1186),
10980 (936, "to_char", "f", 2, 25),
10981 (861, "current_database", "f", 0, 19),
10983 (745, "current_user", "f", 0, 19),
10984 (745, "session_user", "f", 0, 19),
10985 (1402, "current_schema", "f", 0, 19),
10986 (3058, "concat", "f", -1, 25),
10988 (3059, "concat_ws", "f", -1, 25),
10989 (3539, "format", "f", -1, 25),
10990 (2877, "pg_typeof", "f", 1, 2206),
10992 (3198, "json_build_object", "f", -1, 114),
10994 (3199, "jsonb_build_object", "f", -1, 3802),
10995 (3271, "json_build_array", "f", -1, 114),
10996 (3272, "jsonb_build_array", "f", -1, 3802),
10997 (3253, "gen_random_uuid", "f", 0, 2950),
10999 (3252, "uuid_generate_v4", "f", 0, 2950),
11000 (2147, "count", "a", 0, 20),
11002 (2803, "count", "a", -1, 20),
11003 (2116, "max", "a", 1, 23),
11004 (2132, "min", "a", 1, 23),
11005 (2108, "sum", "a", 1, 20),
11006 (2100, "avg", "a", 1, 1700),
11007 (2517, "string_agg", "a", 2, 25),
11008 (2747, "array_agg", "a", 1, 1009),
11009 (2517, "bool_and", "a", 1, 16),
11010 (2518, "bool_or", "a", 1, 16),
11011 (2519, "every", "a", 1, 16),
11012 (3100, "row_number", "w", 0, 20),
11014 (3101, "rank", "w", 0, 20),
11015 (3102, "dense_rank", "w", 0, 20),
11016 (3103, "percent_rank", "w", 0, 701),
11017 (3104, "cume_dist", "w", 0, 701),
11018 (3105, "lag", "w", -1, 2283),
11019 (3106, "lead", "w", -1, 2283),
11020 (3107, "first_value", "w", 1, 2283),
11021 (3108, "last_value", "w", 1, 2283),
11022 (3109, "nth_value", "w", 2, 2283),
11023 ];
11024 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
11025 for &(oid, name, kind, nargs, rettype) in funcs {
11026 rows.push(Row::new(alloc::vec![
11027 Value::BigInt(oid),
11028 Value::Text(name.into()),
11029 Value::BigInt(11),
11030 Value::Text(kind.into()),
11031 Value::Int(nargs),
11032 Value::BigInt(rettype),
11033 ]));
11034 }
11035 (schema, rows)
11036}
11037
11038fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11044 let schema = alloc::vec![
11045 ColumnSchema::new("user", DataType::Text, false),
11046 ColumnSchema::new("host", DataType::Text, false),
11047 ColumnSchema::new("select_priv", DataType::Text, false),
11048 ];
11049 let mut rows: Vec<Row> = Vec::new();
11050 rows.push(Row::new(alloc::vec![
11051 Value::Text("root".into()),
11052 Value::Text("localhost".into()),
11053 Value::Text("Y".into()),
11054 ]));
11055 for (name, _) in engine.users.iter() {
11056 if name != "root" {
11057 rows.push(Row::new(alloc::vec![
11058 Value::Text(name.to_string()),
11059 Value::Text("%".into()),
11060 Value::Text("Y".into()),
11061 ]));
11062 }
11063 }
11064 (schema, rows)
11065}
11066
11067fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
11072 let schema = alloc::vec![
11073 ColumnSchema::new("host", DataType::Text, false),
11074 ColumnSchema::new("db", DataType::Text, false),
11075 ColumnSchema::new("user", DataType::Text, false),
11076 ColumnSchema::new("select_priv", DataType::Text, false),
11077 ];
11078 let rows = alloc::vec![Row::new(alloc::vec![
11079 Value::Text("localhost".into()),
11080 Value::Text("postgres".into()),
11081 Value::Text("root".into()),
11082 Value::Text("Y".into()),
11083 ])];
11084 (schema, rows)
11085}
11086
11087fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11100 let schema = alloc::vec![
11101 ColumnSchema::new("constraint_name", DataType::Text, false),
11102 ColumnSchema::new("table_name", DataType::Text, false),
11103 ColumnSchema::new("column_name", DataType::Text, false),
11104 ColumnSchema::new("ordinal_position", DataType::Int, false),
11105 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11106 ColumnSchema::new("referenced_column_name", DataType::Text, false),
11107 ];
11108 let mut rows: Vec<Row> = Vec::new();
11109 for tname in cat.table_names() {
11110 let Some(t) = cat.get(&tname) else { continue };
11111 let cols = &t.schema().columns;
11112 let col_name_at = |pos: usize| -> String {
11113 cols.get(pos)
11114 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11115 };
11116 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11118 let conname = fk
11119 .name
11120 .clone()
11121 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11122 for (i, (&local, &parent)) in fk
11123 .local_columns
11124 .iter()
11125 .zip(fk.parent_columns.iter())
11126 .enumerate()
11127 {
11128 let parent_name = cat
11129 .get(&fk.parent_table)
11130 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
11131 .unwrap_or_else(|| alloc::format!("col{parent}"));
11132 #[allow(clippy::cast_possible_wrap)]
11133 let ordinal = (i + 1) as i32;
11134 rows.push(Row::new(alloc::vec![
11135 Value::Text(conname.clone()),
11136 Value::Text(tname.clone()),
11137 Value::Text(col_name_at(local)),
11138 Value::Int(ordinal),
11139 Value::Text(fk.parent_table.clone()),
11140 Value::Text(parent_name),
11141 ]));
11142 }
11143 }
11144 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11146 let conname = if uc.is_primary_key {
11147 alloc::format!("{}_pkey", tname)
11148 } else {
11149 alloc::format!("{}_uniq{ci}", tname)
11150 };
11151 for (i, &local) in uc.columns.iter().enumerate() {
11152 #[allow(clippy::cast_possible_wrap)]
11153 let ordinal = (i + 1) as i32;
11154 rows.push(Row::new(alloc::vec![
11155 Value::Text(conname.clone()),
11156 Value::Text(tname.clone()),
11157 Value::Text(col_name_at(local)),
11158 Value::Int(ordinal),
11159 Value::Text(String::new()),
11160 Value::Text(String::new()),
11161 ]));
11162 }
11163 }
11164 }
11165 (schema, rows)
11166}
11167
11168fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11171 let schema = alloc::vec![
11172 ColumnSchema::new("constraint_name", DataType::Text, false),
11173 ColumnSchema::new("table_name", DataType::Text, false),
11174 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11175 ColumnSchema::new("update_rule", DataType::Text, false),
11176 ColumnSchema::new("delete_rule", DataType::Text, false),
11177 ];
11178 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11179 match a {
11180 spg_storage::FkAction::Cascade => "CASCADE",
11181 spg_storage::FkAction::SetNull => "SET NULL",
11182 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11183 spg_storage::FkAction::Restrict => "RESTRICT",
11184 spg_storage::FkAction::NoAction => "NO ACTION",
11185 }
11186 }
11187 let mut rows: Vec<Row> = Vec::new();
11188 for tname in cat.table_names() {
11189 let Some(t) = cat.get(&tname) else { continue };
11190 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11191 let conname = fk
11192 .name
11193 .clone()
11194 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11195 rows.push(Row::new(alloc::vec![
11196 Value::Text(conname),
11197 Value::Text(tname.clone()),
11198 Value::Text(fk.parent_table.clone()),
11199 Value::Text(rule_name(fk.on_update).into()),
11200 Value::Text(rule_name(fk.on_delete).into()),
11201 ]));
11202 }
11203 }
11204 (schema, rows)
11205}
11206
11207fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11211 let schema = alloc::vec![
11212 ColumnSchema::new("table_name", DataType::Text, false),
11213 ColumnSchema::new("index_name", DataType::Text, false),
11214 ColumnSchema::new("column_name", DataType::Text, false),
11215 ColumnSchema::new("seq_in_index", DataType::Int, false),
11216 ColumnSchema::new("non_unique", DataType::Int, false),
11217 ColumnSchema::new("index_type", DataType::Text, false),
11218 ];
11219 let mut rows: Vec<Row> = Vec::new();
11220 for tname in cat.table_names() {
11221 let Some(t) = cat.get(&tname) else { continue };
11222 for idx in t.indices() {
11223 let col = t
11224 .schema()
11225 .columns
11226 .get(idx.column_position)
11227 .map_or("?".into(), |c| c.name.clone());
11228 rows.push(Row::new(alloc::vec![
11229 Value::Text(tname.clone()),
11230 Value::Text(idx.name.clone()),
11231 Value::Text(col),
11232 Value::Int(1),
11233 Value::Int(i32::from(!idx.is_unique)),
11234 Value::Text("BTREE".into()),
11235 ]));
11236 }
11237 }
11238 (schema, rows)
11239}
11240
11241fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11245 let schema = alloc::vec![
11246 ColumnSchema::new("routine_name", DataType::Text, false),
11247 ColumnSchema::new("routine_type", DataType::Text, false),
11248 ColumnSchema::new("data_type", DataType::Text, false),
11249 ];
11250 (schema, Vec::new())
11251}
11252
11253fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11268 let schema = alloc::vec![
11269 ColumnSchema::new("conname", DataType::Text, false),
11270 ColumnSchema::new("contype", DataType::Text, false),
11271 ColumnSchema::new("conrelid", DataType::Text, false),
11272 ColumnSchema::new("confrelid", DataType::Text, false),
11273 ColumnSchema::new("conkey", DataType::Text, false),
11274 ColumnSchema::new("confkey", DataType::Text, false),
11275 ];
11276 let mut rows: Vec<Row> = Vec::new();
11277 for tname in cat.table_names() {
11278 let Some(t) = cat.get(&tname) else { continue };
11279 let cols = &t.schema().columns;
11280 let col_name_at = |pos: usize| -> String {
11281 cols.get(pos)
11282 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11283 };
11284 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11286 let kind = if uc.is_primary_key { "p" } else { "u" };
11287 let conname = if uc.is_primary_key {
11288 alloc::format!("{}_pkey", tname)
11289 } else {
11290 alloc::format!("{}_uniq{ci}", tname)
11291 };
11292 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11293 rows.push(Row::new(alloc::vec![
11294 Value::Text(conname),
11295 Value::Text(kind.into()),
11296 Value::Text(tname.clone()),
11297 Value::Text(String::new()),
11298 Value::Text(conkey.join(",")),
11299 Value::Text(String::new()),
11300 ]));
11301 }
11302 for idx in t.indices() {
11307 if !idx.is_unique {
11308 continue;
11309 }
11310 let is_primary = idx.name.ends_with("_pkey");
11311 let conname = idx.name.clone();
11312 let kind = if is_primary { "p" } else { "u" };
11313 let col_name = col_name_at(idx.column_position);
11314 let already = t
11317 .schema()
11318 .uniqueness_constraints
11319 .iter()
11320 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11321 if already {
11322 continue;
11323 }
11324 rows.push(Row::new(alloc::vec![
11325 Value::Text(conname),
11326 Value::Text(kind.into()),
11327 Value::Text(tname.clone()),
11328 Value::Text(String::new()),
11329 Value::Text(col_name),
11330 Value::Text(String::new()),
11331 ]));
11332 }
11333 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11335 let conname = fk
11336 .name
11337 .clone()
11338 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11339 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11340 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11343 fk.parent_columns
11344 .iter()
11345 .map(|&p| {
11346 parent
11347 .schema()
11348 .columns
11349 .get(p)
11350 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11351 })
11352 .collect()
11353 } else {
11354 fk.parent_columns
11355 .iter()
11356 .map(|p| alloc::format!("col{p}"))
11357 .collect()
11358 };
11359 rows.push(Row::new(alloc::vec![
11360 Value::Text(conname),
11361 Value::Text("f".into()),
11362 Value::Text(tname.clone()),
11363 Value::Text(fk.parent_table.clone()),
11364 Value::Text(conkey.join(",")),
11365 Value::Text(confkey.join(",")),
11366 ]));
11367 }
11368 }
11369 (schema, rows)
11370}
11371
11372fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11377 let schema = alloc::vec![
11378 ColumnSchema::new("oid", DataType::BigInt, false),
11379 ColumnSchema::new("datname", DataType::Text, false),
11380 ColumnSchema::new("datdba", DataType::BigInt, false),
11381 ColumnSchema::new("encoding", DataType::Int, false),
11382 ColumnSchema::new("datcollate", DataType::Text, false),
11383 ];
11384 let rows = alloc::vec![Row::new(alloc::vec![
11385 Value::BigInt(16384),
11386 Value::Text("postgres".into()),
11387 Value::BigInt(10),
11388 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11390 ])];
11391 (schema, rows)
11392}
11393
11394fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11399 let schema = alloc::vec![
11400 ColumnSchema::new("oid", DataType::BigInt, false),
11401 ColumnSchema::new("rolname", DataType::Text, false),
11402 ColumnSchema::new("rolsuper", DataType::Bool, false),
11403 ColumnSchema::new("rolinherit", DataType::Bool, false),
11404 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11405 ];
11406 let mut rows: Vec<Row> = Vec::new();
11407 let oid: i64 = 10;
11408 for (i, (name, _)) in engine.users.iter().enumerate() {
11409 rows.push(Row::new(alloc::vec![
11410 Value::BigInt(oid + (i as i64) + 1),
11411 Value::Text(name.to_string()),
11412 Value::Bool(false),
11413 Value::Bool(true),
11414 Value::Bool(true),
11415 ]));
11416 }
11417 if !rows
11420 .iter()
11421 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11422 {
11423 rows.insert(
11424 0,
11425 Row::new(alloc::vec![
11426 Value::BigInt(10),
11427 Value::Text("postgres".into()),
11428 Value::Bool(true),
11429 Value::Bool(true),
11430 Value::Bool(true),
11431 ]),
11432 );
11433 }
11434 (schema, rows)
11435}
11436
11437fn synth_pg_extension() -> (Vec<ColumnSchema>, Vec<Row>) {
11446 let schema = alloc::vec![
11447 ColumnSchema::new("oid", DataType::BigInt, false),
11448 ColumnSchema::new("extname", DataType::Text, false),
11449 ColumnSchema::new("extversion", DataType::Text, false),
11450 ColumnSchema::new("extnamespace", DataType::Text, false),
11451 ];
11452 let exts: &[(&str, &str)] = &[("plpgsql", "1.0"), ("vector", "0.8.0"), ("pg_trgm", "1.6")];
11453 let rows = exts
11454 .iter()
11455 .enumerate()
11456 .map(|(i, (name, ver))| {
11457 Row::new(alloc::vec![
11458 Value::BigInt(16384 + i as i64),
11459 Value::Text((*name).into()),
11460 Value::Text((*ver).into()),
11461 Value::Text("pg_catalog".into()),
11462 ])
11463 })
11464 .collect();
11465 (schema, rows)
11466}
11467
11468fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11469 let schema = alloc::vec![
11470 ColumnSchema::new("schemaname", DataType::Text, false),
11471 ColumnSchema::new("viewname", DataType::Text, false),
11472 ColumnSchema::new("definition", DataType::Text, false),
11473 ];
11474 let mut rows: Vec<Row> = Vec::new();
11475 for (name, def) in cat.views() {
11476 rows.push(Row::new(alloc::vec![
11477 Value::Text("public".into()),
11478 Value::Text(name.clone()),
11479 Value::Text(def.body.clone()),
11480 ]));
11481 }
11482 (schema, rows)
11483}
11484
11485fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11491 let schema = alloc::vec![
11492 ColumnSchema::new("name", DataType::Text, false),
11493 ColumnSchema::new("setting", DataType::Text, false),
11494 ColumnSchema::new("category", DataType::Text, false),
11495 ];
11496 let mut rows: Vec<Row> = Vec::new();
11497 let defaults: &[(&str, &str, &str)] = &[
11499 ("server_version", "16.0 (spg)", "Preset Options"),
11500 ("server_encoding", "UTF8", "Client Connection Defaults"),
11501 ("client_encoding", "UTF8", "Client Connection Defaults"),
11502 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11503 ("TimeZone", "UTC", "Client Connection Defaults"),
11504 ("standard_conforming_strings", "on", "Compatibility"),
11505 ("integer_datetimes", "on", "Compatibility"),
11506 ("max_connections", "100", "Connections and Authentication"),
11507 ];
11508 for &(name, val, cat) in defaults {
11509 rows.push(Row::new(alloc::vec![
11510 Value::Text(name.into()),
11511 Value::Text(val.into()),
11512 Value::Text(cat.into()),
11513 ]));
11514 }
11515 for (k, v) in &engine.session_params {
11517 if !defaults
11518 .iter()
11519 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11520 {
11521 rows.push(Row::new(alloc::vec![
11522 Value::Text(k.clone()),
11523 Value::Text(v.clone()),
11524 Value::Text("Session".into()),
11525 ]));
11526 }
11527 }
11528 (schema, rows)
11529}
11530
11531fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11542 let schema = alloc::vec![
11543 ColumnSchema::new("schemaname", DataType::Text, false),
11544 ColumnSchema::new("tablename", DataType::Text, false),
11545 ColumnSchema::new("indexname", DataType::Text, false),
11546 ColumnSchema::new("indexdef", DataType::Text, false),
11547 ];
11548 let mut rows: Vec<Row> = Vec::new();
11549 for tname in cat.table_names() {
11550 let Some(t) = cat.get(&tname) else { continue };
11551 for idx in t.indices() {
11552 let col_name = t
11553 .schema()
11554 .columns
11555 .get(idx.column_position)
11556 .map_or("?".into(), |c| c.name.clone());
11557 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11558 let indexdef = alloc::format!(
11559 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11560 idx.name,
11561 tname,
11562 col_name
11563 );
11564 rows.push(Row::new(alloc::vec![
11565 Value::Text("public".into()),
11566 Value::Text(tname.clone()),
11567 Value::Text(idx.name.clone()),
11568 Value::Text(indexdef),
11569 ]));
11570 }
11571 }
11572 (schema, rows)
11573}
11574
11575fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11587 let schema = alloc::vec![
11588 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11589 ColumnSchema::new("indrelid", DataType::BigInt, false),
11590 ColumnSchema::new("indnatts", DataType::Int, false),
11591 ColumnSchema::new("indisunique", DataType::Bool, false),
11592 ColumnSchema::new("indisprimary", DataType::Bool, false),
11593 ];
11594 let mut rows: Vec<Row> = Vec::new();
11595 let mut idx_oid: i64 = 100_000;
11596 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11597 let Some(t) = cat.get(tname) else { continue };
11598 for idx in t.indices() {
11599 idx_oid += 1;
11600 #[allow(clippy::cast_possible_wrap)]
11601 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11602 let is_primary = idx.name.ends_with("_pkey");
11605 rows.push(Row::new(alloc::vec![
11606 Value::BigInt(idx_oid),
11607 Value::BigInt((table_idx + 1) as i64),
11608 Value::Int(nattrs),
11609 Value::Bool(idx.is_unique),
11610 Value::Bool(is_primary),
11611 ]));
11612 }
11613 }
11614 (schema, rows)
11615}
11616
11617fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11622 let schema = alloc::vec![
11623 ColumnSchema::new("oid", DataType::BigInt, false),
11624 ColumnSchema::new("nspname", DataType::Text, false),
11625 ColumnSchema::new("nspowner", DataType::BigInt, false),
11626 ];
11627 let rows = alloc::vec![
11628 Row::new(alloc::vec![
11629 Value::BigInt(11),
11630 Value::Text("pg_catalog".into()),
11631 Value::BigInt(10),
11632 ]),
11633 Row::new(alloc::vec![
11634 Value::BigInt(2200),
11635 Value::Text("public".into()),
11636 Value::BigInt(10),
11637 ]),
11638 Row::new(alloc::vec![
11639 Value::BigInt(13000),
11640 Value::Text("information_schema".into()),
11641 Value::BigInt(10),
11642 ]),
11643 ];
11644 (schema, rows)
11645}
11646
11647fn materialise_meta_view(
11650 catalog: &mut Catalog,
11651 name: &str,
11652 columns: Vec<ColumnSchema>,
11653 rows: Vec<Row>,
11654) -> Result<(), EngineError> {
11655 let schema = TableSchema::new(name.to_string(), columns);
11656 catalog.create_table(schema).map_err(EngineError::Storage)?;
11657 let table = catalog
11658 .get_mut(name)
11659 .expect("just-created meta view must exist");
11660 for row in rows {
11661 table.insert(row).map_err(EngineError::Storage)?;
11662 }
11663 Ok(())
11664}
11665
11666fn collect_view_refs(
11679 tref: &spg_sql::ast::TableRef,
11680 cat: &spg_storage::Catalog,
11681 into: &mut Vec<String>,
11682) {
11683 if cat.views().contains_key(&tref.name)
11684 && cat.get(&tref.name).is_none()
11685 && !into.iter().any(|n| n == &tref.name)
11686 {
11687 into.push(tref.name.clone());
11688 }
11689}
11690
11691fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11692 fn is_meta(name: &str) -> bool {
11693 name.starts_with("__spg_info_")
11694 || name.starts_with("__spg_pg_")
11695 || name.starts_with("__spg_mysql_")
11696 }
11697 if let Some(from) = &stmt.from {
11698 if is_meta(&from.primary.name) {
11699 return true;
11700 }
11701 for j in &from.joins {
11702 if is_meta(&j.table.name) {
11703 return true;
11704 }
11705 }
11706 }
11707 for cte in &stmt.ctes {
11708 if select_references_meta_view(&cte.body) {
11709 return true;
11710 }
11711 }
11712 false
11713}
11714
11715fn collect_meta_view_names(
11720 stmt: &SelectStatement,
11721 into: &mut alloc::collections::BTreeSet<String>,
11722) {
11723 fn is_meta(name: &str) -> bool {
11724 name.starts_with("__spg_info_")
11725 || name.starts_with("__spg_pg_")
11726 || name.starts_with("__spg_mysql_")
11727 }
11728 if let Some(from) = &stmt.from {
11729 if is_meta(&from.primary.name) {
11730 into.insert(from.primary.name.clone());
11731 }
11732 for j in &from.joins {
11733 if is_meta(&j.table.name) {
11734 into.insert(j.table.name.clone());
11735 }
11736 }
11737 }
11738 for cte in &stmt.ctes {
11739 collect_meta_view_names(&cte.body, into);
11740 }
11741}
11742
11743fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11744 let mut out = columns.to_vec();
11745 for (col_idx, col) in out.iter_mut().enumerate() {
11746 if col.ty != DataType::Text {
11747 continue;
11748 }
11749 let mut inferred: Option<DataType> = None;
11750 let mut all_null = true;
11751 for row in rows {
11752 let Some(v) = row.values.get(col_idx) else {
11753 continue;
11754 };
11755 let ty = match v {
11756 Value::Null => continue,
11757 Value::SmallInt(_) => DataType::SmallInt,
11758 Value::Int(_) => DataType::Int,
11759 Value::BigInt(_) => DataType::BigInt,
11760 Value::Float(_) => DataType::Float,
11761 Value::Bool(_) => DataType::Bool,
11762 Value::Vector(_) => DataType::Vector {
11763 dim: 0,
11764 encoding: VecEncoding::F32,
11765 },
11766 _ => DataType::Text,
11767 };
11768 all_null = false;
11769 inferred = Some(match inferred {
11770 None => ty,
11771 Some(prev) if prev == ty => prev,
11772 Some(_) => DataType::Text,
11773 });
11774 }
11775 if let Some(t) = inferred {
11776 col.ty = t;
11777 col.nullable = true;
11778 } else if all_null {
11779 col.nullable = true;
11780 }
11781 }
11782 out
11783}
11784
11785#[allow(clippy::too_many_lines, clippy::format_push_string)]
11790fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11807 use alloc::collections::BTreeSet;
11808 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11809 let mut out: Vec<String> = Vec::new();
11810 let cat = engine.active_catalog();
11811 let Some(from) = &stmt.from else {
11815 return out;
11816 };
11817 let mut tables: Vec<String> = Vec::new();
11818 tables.push(from.primary.name.clone());
11819 for j in &from.joins {
11820 tables.push(j.table.name.clone());
11821 }
11822 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11825 if let Some(w) = &stmt.where_ {
11826 collect_column_refs(w, &mut col_refs);
11827 }
11828 for j in &from.joins {
11829 if let Some(on) = &j.on {
11830 collect_column_refs(on, &mut col_refs);
11831 }
11832 }
11833 for cn in &col_refs {
11834 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11837 tables.iter().find(|t| t == &q).cloned()
11838 } else {
11839 tables.iter().find_map(|t| {
11840 cat.get(t).and_then(|tbl| {
11841 if tbl.schema().column_position(&cn.name).is_some() {
11842 Some(t.clone())
11843 } else {
11844 None
11845 }
11846 })
11847 })
11848 };
11849 let Some(owner) = owner else {
11850 continue;
11851 };
11852 let Some(tbl) = cat.get(&owner) else {
11853 continue;
11854 };
11855 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11856 continue;
11857 };
11858 let already_indexed = tbl.indices().iter().any(|i| {
11861 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11862 && i.column_position == col_pos
11863 && i.expression.is_none()
11864 && i.partial_predicate.is_none()
11865 });
11866 if already_indexed {
11867 continue;
11868 }
11869 if seen.insert((owner.clone(), cn.name.clone())) {
11870 out.push(alloc::format!(
11871 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11872 owner,
11873 cn.name,
11874 owner,
11875 cn.name
11876 ));
11877 }
11878 }
11879 out
11880}
11881
11882fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11885 match expr {
11886 Expr::Column(cn) => out.push(cn.clone()),
11887 Expr::FunctionCall { args, .. } => {
11888 for a in args {
11889 collect_column_refs(a, out);
11890 }
11891 }
11892 Expr::Binary { lhs, rhs, .. } => {
11893 collect_column_refs(lhs, out);
11894 collect_column_refs(rhs, out);
11895 }
11896 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11897 _ => {}
11898 }
11899}
11900
11901fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11902 let catalog = engine.active_catalog();
11903 let cold_ids = catalog.cold_segment_ids_global();
11904 let any_cold = !cold_ids.is_empty();
11905 let cold_ids_repr = if any_cold {
11906 let mut s = alloc::string::String::from("[");
11907 for (i, id) in cold_ids.iter().enumerate() {
11908 if i > 0 {
11909 s.push(',');
11910 }
11911 s.push_str(&alloc::format!("{id}"));
11912 }
11913 s.push(']');
11914 s
11915 } else {
11916 alloc::string::String::new()
11917 };
11918 for (idx, line) in lines.iter_mut().enumerate() {
11919 let trimmed = line.trim_start();
11920 let is_top_level = idx == 0;
11921 if is_top_level {
11922 line.push_str(&alloc::format!(" (rows={total_rows})"));
11923 continue;
11924 }
11925 if let Some(rest) = trimmed.strip_prefix("From: ") {
11926 let (name, scan_kind) = match rest.split_once(" [") {
11927 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
11928 None => (rest.trim(), ""),
11929 };
11930 let bare = name.split_whitespace().next().unwrap_or(name);
11931 let hot = catalog.get(bare).map(|t| t.rows().len());
11932 let annot = match (hot, scan_kind) {
11937 (Some(h), "full scan") => {
11938 let mut s = alloc::format!(" (hot_rows={h}");
11939 if any_cold {
11940 s.push_str(&alloc::format!(
11941 ", cold_tier=present, cold_segments={cold_ids_repr}"
11942 ));
11943 }
11944 s.push(')');
11945 s
11946 }
11947 (Some(h), "index seek") => {
11948 let mut s = alloc::format!(" (hot_rows≤{h}");
11949 if any_cold {
11950 s.push_str(&alloc::format!(
11951 ", cold_tier=present, cold_segments={cold_ids_repr}"
11952 ));
11953 }
11954 s.push(')');
11955 s
11956 }
11957 _ => " (rows=—)".to_string(),
11958 };
11959 line.push_str(&annot);
11960 continue;
11961 }
11962 line.push_str(" (rows=—)");
11964 }
11965}
11966
11967fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
11968 let pad = " ".repeat(depth);
11969 let top = if !stmt.ctes.is_empty() {
11971 if stmt.ctes.iter().any(|c| c.recursive) {
11972 "CTEScan (WITH RECURSIVE)"
11973 } else {
11974 "CTEScan (WITH)"
11975 }
11976 } else if !stmt.unions.is_empty() {
11977 "UnionScan"
11978 } else if select_has_window(stmt) {
11979 "WindowAgg"
11980 } else if aggregate::uses_aggregate(stmt) {
11981 "Aggregate"
11982 } else if stmt.distinct {
11983 "Distinct"
11984 } else if stmt.from.is_some() {
11985 "TableScan"
11986 } else {
11987 "Result"
11988 };
11989 out.push(alloc::format!("{pad}{top}"));
11990 let child = " ".repeat(depth + 1);
11991 for cte in &stmt.ctes {
11993 let head = if cte.recursive {
11994 alloc::format!("{child}CTE (recursive): {}", cte.name)
11995 } else {
11996 alloc::format!("{child}CTE: {}", cte.name)
11997 };
11998 out.push(head);
11999 explain_select(&cte.body, engine, depth + 2, out);
12000 }
12001 if let Some(from) = &stmt.from {
12003 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
12004 if let Some(alias) = &from.primary.alias {
12005 tag.push_str(&alloc::format!(" AS {alias}"));
12006 }
12007 if let Some(w) = &stmt.where_
12010 && let Some(table) = engine.active_catalog().get(&from.primary.name)
12011 {
12012 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
12013 let cols = &table.schema().columns;
12014 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
12015 tag.push_str(" [index seek]");
12016 } else {
12017 tag.push_str(" [full scan]");
12018 }
12019 } else {
12020 tag.push_str(" [full scan]");
12021 }
12022 out.push(tag);
12023 for j in &from.joins {
12024 let kind = match j.kind {
12025 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
12026 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
12027 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
12028 };
12029 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
12030 if let Some(alias) = &j.table.alias {
12031 s.push_str(&alloc::format!(" AS {alias}"));
12032 }
12033 if j.on.is_some() {
12034 s.push_str(" (ON …)");
12035 }
12036 out.push(s);
12037 }
12038 }
12039 if let Some(w) = &stmt.where_ {
12041 let mut s = alloc::format!("{child}Filter: {w}");
12042 if expr_has_subquery(w) {
12043 s.push_str(" [subquery]");
12044 }
12045 out.push(s);
12046 }
12047 if let Some(gs) = &stmt.group_by {
12048 let mut parts = Vec::new();
12049 for g in gs {
12050 parts.push(alloc::format!("{g}"));
12051 }
12052 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
12053 }
12054 if let Some(h) = &stmt.having {
12055 out.push(alloc::format!("{child}Having: {h}"));
12056 }
12057 for o in &stmt.order_by {
12058 let dir = if o.desc { "DESC" } else { "ASC" };
12059 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
12060 }
12061 if let Some(lim) = stmt.limit {
12062 out.push(alloc::format!("{child}Limit: {lim}"));
12063 }
12064 if let Some(off) = stmt.offset {
12065 out.push(alloc::format!("{child}Offset: {off}"));
12066 }
12067 if stmt
12069 .items
12070 .iter()
12071 .any(|it| matches!(it, SelectItem::Wildcard))
12072 {
12073 out.push(alloc::format!("{child}Project: *"));
12074 } else {
12075 out.push(alloc::format!(
12076 "{child}Project: {} item(s)",
12077 stmt.items.len()
12078 ));
12079 }
12080 for (kind, peer) in &stmt.unions {
12082 let label = match kind {
12083 UnionKind::All => "UNION ALL",
12084 UnionKind::Distinct => "UNION",
12085 };
12086 out.push(alloc::format!("{child}{label}"));
12087 explain_select(peer, engine, depth + 2, out);
12088 }
12089}
12090
12091fn is_correlation_error(e: &EngineError) -> bool {
12096 matches!(
12097 e,
12098 EngineError::Eval(
12099 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
12100 )
12101 )
12102}
12103
12104struct JoinedPeer<'a> {
12115 eager_rows: Option<Vec<Row>>,
12116 cols: Vec<ColumnSchema>,
12117 alias: String,
12118 kind: JoinKind,
12119 on: Option<&'a Expr>,
12120 lateral: Option<&'a SelectStatement>,
12121}
12122
12123fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
12130 match expr {
12131 Expr::Column(c) => c.name.clone(),
12133 Expr::FunctionCall { name, .. } => name.clone(),
12136 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
12138 _ => alloc::format!("column{}", idx + 1),
12140 }
12141}
12142
12143fn substitute_outer_columns_multi(
12150 stmt: &mut SelectStatement,
12151 outer_row: &Row,
12152 outer_schema: &[ColumnSchema],
12153) {
12154 substitute_outer_in_select(stmt, outer_row, outer_schema);
12155}
12156
12157fn substitute_outer_in_select(
12158 stmt: &mut SelectStatement,
12159 outer_row: &Row,
12160 outer_schema: &[ColumnSchema],
12161) {
12162 for item in &mut stmt.items {
12163 if let SelectItem::Expr { expr, .. } = item {
12164 substitute_outer_in_expr(expr, outer_row, outer_schema);
12165 }
12166 }
12167 if let Some(w) = &mut stmt.where_ {
12168 substitute_outer_in_expr(w, outer_row, outer_schema);
12169 }
12170 if let Some(gs) = &mut stmt.group_by {
12171 for g in gs {
12172 substitute_outer_in_expr(g, outer_row, outer_schema);
12173 }
12174 }
12175 if let Some(h) = &mut stmt.having {
12176 substitute_outer_in_expr(h, outer_row, outer_schema);
12177 }
12178 for o in &mut stmt.order_by {
12179 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12180 }
12181 for (_, peer) in &mut stmt.unions {
12182 substitute_outer_in_select(peer, outer_row, outer_schema);
12183 }
12184}
12185
12186fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12187 if let Expr::Column(c) = e
12188 && let Some(qual) = &c.qualifier
12189 {
12190 let composite = alloc::format!("{qual}.{}", c.name);
12191 if let Some(idx) = outer_schema
12192 .iter()
12193 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12194 {
12195 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12196 if let Ok(lit) = value_to_literal_expr(v) {
12197 *e = lit;
12198 return;
12199 }
12200 }
12201 }
12202 match e {
12203 Expr::Binary { lhs, rhs, .. } => {
12204 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12205 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12206 }
12207 Expr::Unary { expr: inner, .. } => {
12208 substitute_outer_in_expr(inner, outer_row, outer_schema);
12209 }
12210 Expr::FunctionCall { args, .. } => {
12211 for a in args {
12212 substitute_outer_in_expr(a, outer_row, outer_schema);
12213 }
12214 }
12215 Expr::Cast { expr: inner, .. } => {
12216 substitute_outer_in_expr(inner, outer_row, outer_schema);
12217 }
12218 Expr::Case {
12219 operand,
12220 branches,
12221 else_branch,
12222 } => {
12223 if let Some(op) = operand {
12224 substitute_outer_in_expr(op, outer_row, outer_schema);
12225 }
12226 for (cond, val) in branches {
12227 substitute_outer_in_expr(cond, outer_row, outer_schema);
12228 substitute_outer_in_expr(val, outer_row, outer_schema);
12229 }
12230 if let Some(e) = else_branch {
12231 substitute_outer_in_expr(e, outer_row, outer_schema);
12232 }
12233 }
12234 _ => {}
12235 }
12236}
12237
12238fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12239 let Some(outer_alias) = ctx.table_alias else {
12240 return;
12241 };
12242 substitute_in_select(stmt, row, ctx, outer_alias);
12243}
12244
12245fn substitute_in_select(
12246 stmt: &mut SelectStatement,
12247 row: &Row,
12248 ctx: &EvalContext<'_>,
12249 outer_alias: &str,
12250) {
12251 for item in &mut stmt.items {
12252 if let SelectItem::Expr { expr, .. } = item {
12253 substitute_in_expr(expr, row, ctx, outer_alias);
12254 }
12255 }
12256 if let Some(w) = &mut stmt.where_ {
12257 substitute_in_expr(w, row, ctx, outer_alias);
12258 }
12259 if let Some(gs) = &mut stmt.group_by {
12260 for g in gs {
12261 substitute_in_expr(g, row, ctx, outer_alias);
12262 }
12263 }
12264 if let Some(h) = &mut stmt.having {
12265 substitute_in_expr(h, row, ctx, outer_alias);
12266 }
12267 for o in &mut stmt.order_by {
12268 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12269 }
12270 for (_, peer) in &mut stmt.unions {
12271 substitute_in_select(peer, row, ctx, outer_alias);
12272 }
12273}
12274
12275fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12276 if let Expr::Column(c) = e
12277 && let Some(qual) = &c.qualifier
12278 && qual.eq_ignore_ascii_case(outer_alias)
12279 {
12280 if let Some(idx) = ctx
12282 .columns
12283 .iter()
12284 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12285 {
12286 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12287 if let Ok(lit) = value_to_literal_expr(v) {
12288 *e = lit;
12289 return;
12290 }
12291 }
12292 }
12293 match e {
12294 Expr::Binary { lhs, rhs, .. } => {
12295 substitute_in_expr(lhs, row, ctx, outer_alias);
12296 substitute_in_expr(rhs, row, ctx, outer_alias);
12297 }
12298 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12299 substitute_in_expr(expr, row, ctx, outer_alias);
12300 }
12301 Expr::Like { expr, pattern, .. } => {
12302 substitute_in_expr(expr, row, ctx, outer_alias);
12303 substitute_in_expr(pattern, row, ctx, outer_alias);
12304 }
12305 Expr::FunctionCall { args, .. } => {
12306 for a in args {
12307 substitute_in_expr(a, row, ctx, outer_alias);
12308 }
12309 }
12310 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12311 Expr::WindowFunction {
12312 args,
12313 partition_by,
12314 order_by,
12315 ..
12316 } => {
12317 for a in args {
12318 substitute_in_expr(a, row, ctx, outer_alias);
12319 }
12320 for p in partition_by {
12321 substitute_in_expr(p, row, ctx, outer_alias);
12322 }
12323 for (o, _) in order_by {
12324 substitute_in_expr(o, row, ctx, outer_alias);
12325 }
12326 }
12327 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12328 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12329 substitute_in_select(subquery, row, ctx, outer_alias);
12330 }
12331 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12332 Expr::Array(items) => {
12333 for elem in items {
12334 substitute_in_expr(elem, row, ctx, outer_alias);
12335 }
12336 }
12337 Expr::ArraySubscript { target, index } => {
12338 substitute_in_expr(target, row, ctx, outer_alias);
12339 substitute_in_expr(index, row, ctx, outer_alias);
12340 }
12341 Expr::AnyAll { expr, array, .. } => {
12342 substitute_in_expr(expr, row, ctx, outer_alias);
12343 substitute_in_expr(array, row, ctx, outer_alias);
12344 }
12345 Expr::Case {
12346 operand,
12347 branches,
12348 else_branch,
12349 } => {
12350 if let Some(o) = operand {
12351 substitute_in_expr(o, row, ctx, outer_alias);
12352 }
12353 for (w, t) in branches {
12354 substitute_in_expr(w, row, ctx, outer_alias);
12355 substitute_in_expr(t, row, ctx, outer_alias);
12356 }
12357 if let Some(e) = else_branch {
12358 substitute_in_expr(e, row, ctx, outer_alias);
12359 }
12360 }
12361 }
12362}
12363
12364fn encode_row_key(row: &Row) -> Vec<u8> {
12368 let mut out = Vec::new();
12369 for v in &row.values {
12370 let s = alloc::format!("{v:?}|");
12371 out.extend_from_slice(s.as_bytes());
12372 }
12373 out
12374}
12375
12376fn select_has_window(stmt: &SelectStatement) -> bool {
12377 for item in &stmt.items {
12378 if let SelectItem::Expr { expr, .. } = item
12379 && expr_has_window(expr)
12380 {
12381 return true;
12382 }
12383 }
12384 false
12385}
12386
12387fn expr_has_window(e: &Expr) -> bool {
12388 match e {
12389 Expr::WindowFunction { .. } => true,
12390 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12391 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12392 expr_has_window(expr)
12393 }
12394 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12395 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12396 Expr::Extract { source, .. } => expr_has_window(source),
12397 Expr::ScalarSubquery(_)
12398 | Expr::Exists { .. }
12399 | Expr::InSubquery { .. }
12400 | Expr::Literal(_)
12401 | Expr::Placeholder(_)
12402 | Expr::Column(_) => false,
12403 Expr::Array(items) => items.iter().any(expr_has_window),
12404 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12405 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12406 Expr::Case {
12407 operand,
12408 branches,
12409 else_branch,
12410 } => {
12411 operand.as_deref().is_some_and(expr_has_window)
12412 || branches
12413 .iter()
12414 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12415 || else_branch.as_deref().is_some_and(expr_has_window)
12416 }
12417 }
12418}
12419
12420fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12421 if let Expr::WindowFunction { .. } = e {
12422 if !out.iter().any(|x| x == e) {
12427 out.push(e.clone());
12428 }
12429 return;
12430 }
12431 match e {
12432 Expr::WindowFunction { .. } => unreachable!(),
12434 Expr::Binary { lhs, rhs, .. } => {
12435 collect_window_nodes(lhs, out);
12436 collect_window_nodes(rhs, out);
12437 }
12438 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12439 collect_window_nodes(expr, out);
12440 }
12441 Expr::FunctionCall { args, .. } => {
12442 for a in args {
12443 collect_window_nodes(a, out);
12444 }
12445 }
12446 Expr::Like { expr, pattern, .. } => {
12447 collect_window_nodes(expr, out);
12448 collect_window_nodes(pattern, out);
12449 }
12450 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12451 _ => {}
12452 }
12453}
12454
12455fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12456 if let Expr::WindowFunction { .. } = e
12457 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12458 {
12459 *e = Expr::Column(spg_sql::ast::ColumnName {
12460 qualifier: None,
12461 name: alloc::format!("__win_{idx}"),
12462 });
12463 return;
12464 }
12465 match e {
12466 Expr::Binary { lhs, rhs, .. } => {
12467 rewrite_window_to_columns(lhs, window_nodes);
12468 rewrite_window_to_columns(rhs, window_nodes);
12469 }
12470 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12471 rewrite_window_to_columns(expr, window_nodes);
12472 }
12473 Expr::FunctionCall { args, .. } => {
12474 for a in args {
12475 rewrite_window_to_columns(a, window_nodes);
12476 }
12477 }
12478 Expr::Like { expr, pattern, .. } => {
12479 rewrite_window_to_columns(expr, window_nodes);
12480 rewrite_window_to_columns(pattern, window_nodes);
12481 }
12482 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12483 _ => {}
12484 }
12485}
12486
12487fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12491 for (x, y) in a.iter().zip(b.iter()) {
12492 let c = value_cmp(x, y);
12493 if c != core::cmp::Ordering::Equal {
12494 return c;
12495 }
12496 }
12497 a.len().cmp(&b.len())
12498}
12499
12500fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12501 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12502 let c = value_cmp(va, vb);
12503 let c = if *desc { c.reverse() } else { c };
12504 if c != core::cmp::Ordering::Equal {
12505 return c;
12506 }
12507 }
12508 a.len().cmp(&b.len())
12509}
12510
12511const fn value_is_integer(v: &Value) -> bool {
12517 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12518}
12519
12520const fn value_to_i64(v: &Value) -> i64 {
12524 match v {
12525 Value::SmallInt(n) => *n as i64,
12526 Value::Int(n) => *n as i64,
12527 Value::BigInt(n) => *n,
12528 _ => panic!("value_to_i64 called on non-integer Value"),
12529 }
12530}
12531
12532fn generate_series_integers(
12538 start: i64,
12539 stop: i64,
12540 step: i64,
12541 cancel: &CancelToken<'_>,
12542) -> Result<alloc::vec::Vec<Row>, EngineError> {
12543 if step == 0 {
12544 return Err(EngineError::Unsupported(
12545 "generate_series(): step argument cannot be zero".into(),
12546 ));
12547 }
12548 let mut out = alloc::vec::Vec::new();
12549 let mut cur = start;
12550 const MAX_ROWS: usize = 10_000_000;
12554 loop {
12555 cancel.check()?;
12556 if step > 0 && cur > stop {
12557 break;
12558 }
12559 if step < 0 && cur < stop {
12560 break;
12561 }
12562 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12563 if out.len() > MAX_ROWS {
12564 return Err(EngineError::Unsupported(alloc::format!(
12565 "generate_series(): exceeded {MAX_ROWS} rows; \
12566 narrow start/stop or use a larger step"
12567 )));
12568 }
12569 cur = match cur.checked_add(step) {
12570 Some(n) => n,
12571 None => break,
12572 };
12573 }
12574 Ok(out)
12575}
12576
12577fn generate_series_timestamps(
12582 start: i64,
12583 stop: i64,
12584 step: Value,
12585 cancel: &CancelToken<'_>,
12586) -> Result<alloc::vec::Vec<Row>, EngineError> {
12587 let (months, micros) = match &step {
12588 Value::Interval { months, micros } => (*months, *micros),
12589 _ => unreachable!("caller guards step.is_interval"),
12590 };
12591 if months == 0 && micros == 0 {
12592 return Err(EngineError::Unsupported(
12593 "generate_series(): INTERVAL step cannot be zero".into(),
12594 ));
12595 }
12596 let ascending = months > 0 || micros > 0;
12597 let mut out = alloc::vec::Vec::new();
12598 let mut cur = Value::Timestamp(start);
12599 const MAX_ROWS: usize = 10_000_000;
12600 loop {
12601 cancel.check()?;
12602 let cur_t = match cur {
12603 Value::Timestamp(t) => t,
12604 _ => unreachable!("loop invariant: cur is Timestamp"),
12605 };
12606 if ascending && cur_t > stop {
12607 break;
12608 }
12609 if !ascending && cur_t < stop {
12610 break;
12611 }
12612 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12613 if out.len() > MAX_ROWS {
12614 return Err(EngineError::Unsupported(alloc::format!(
12615 "generate_series(): exceeded {MAX_ROWS} rows; \
12616 narrow start/stop or use a larger step"
12617 )));
12618 }
12619 let next = eval::apply_binary_interval(
12620 spg_sql::ast::BinOp::Add,
12621 &cur,
12622 &Value::Interval { months, micros },
12623 )
12624 .map_err(EngineError::Eval)?;
12625 cur = match next {
12626 Some(v) => v,
12627 None => break,
12628 };
12629 }
12630 Ok(out)
12631}
12632
12633#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12635 use core::cmp::Ordering;
12636 match (a, b) {
12637 (Value::Null, Value::Null) => Ordering::Equal,
12638 (Value::Null, _) => Ordering::Less,
12639 (_, Value::Null) => Ordering::Greater,
12640 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12641 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12642 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12643 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12644 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12645 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12646 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12647 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12648 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12651 }
12652}
12653
12654#[allow(
12660 clippy::too_many_arguments,
12661 clippy::cast_possible_truncation,
12662 clippy::cast_possible_wrap,
12663 clippy::cast_precision_loss,
12664 clippy::cast_sign_loss,
12665 clippy::doc_markdown,
12666 clippy::too_many_lines,
12667 clippy::type_complexity,
12668 clippy::match_same_arms
12669)]
12670fn compute_window_partition(
12671 name: &str,
12672 args: &[Expr],
12673 ordered: bool,
12674 frame: Option<&WindowFrame>,
12675 null_treatment: spg_sql::ast::NullTreatment,
12676 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12677 filtered_rows: &[&Row],
12678 ctx: &EvalContext<'_>,
12679 out_vals: &mut [Value],
12680) -> Result<(), EngineError> {
12681 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12682 let lower = name.to_ascii_lowercase();
12683 match lower.as_str() {
12684 "row_number" => {
12685 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12686 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12687 }
12688 Ok(())
12689 }
12690 "rank" => {
12691 let mut prev_key: Option<&[(Value, bool)]> = None;
12692 let mut current_rank: i64 = 1;
12693 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12694 if let Some(p) = prev_key
12695 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12696 {
12697 current_rank = (i + 1) as i64;
12698 }
12699 if prev_key.is_none() {
12700 current_rank = 1;
12701 }
12702 out_vals[*idx] = Value::BigInt(current_rank);
12703 prev_key = Some(okey.as_slice());
12704 }
12705 Ok(())
12706 }
12707 "dense_rank" => {
12708 let mut prev_key: Option<&[(Value, bool)]> = None;
12709 let mut current_rank: i64 = 0;
12710 for (_, okey, idx) in slice {
12711 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12712 current_rank += 1;
12713 }
12714 out_vals[*idx] = Value::BigInt(current_rank);
12715 prev_key = Some(okey.as_slice());
12716 }
12717 Ok(())
12718 }
12719 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12720 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12723 slice.iter().map(|_| Value::Null).collect()
12724 } else {
12725 slice
12726 .iter()
12727 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12728 .collect::<Result<_, _>>()
12729 .map_err(EngineError::Eval)?
12730 };
12731 let eff = effective_frame(frame, ordered)?;
12735 #[allow(clippy::needless_range_loop)]
12736 for i in 0..slice.len() {
12737 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12738 let mut sum: f64 = 0.0;
12739 let mut count: i64 = 0;
12740 let mut min_v: Option<f64> = None;
12741 let mut max_v: Option<f64> = None;
12742 let mut row_count: i64 = 0;
12743 if lo <= hi {
12744 for j in lo..=hi {
12745 let v = &arg_values[j];
12746 match lower.as_str() {
12747 "count_star" => row_count += 1,
12748 "count" => {
12749 if !v.is_null() {
12750 count += 1;
12751 }
12752 }
12753 _ => {
12754 if let Some(x) = value_to_f64(v) {
12755 sum += x;
12756 count += 1;
12757 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12758 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12759 }
12760 }
12761 }
12762 }
12763 }
12764 let value = match lower.as_str() {
12765 "count_star" => Value::BigInt(row_count),
12766 "count" => Value::BigInt(count),
12767 "sum" => Value::Float(sum),
12768 "avg" => {
12769 if count == 0 {
12770 Value::Null
12771 } else {
12772 Value::Float(sum / count as f64)
12773 }
12774 }
12775 "min" => min_v.map_or(Value::Null, Value::Float),
12776 "max" => max_v.map_or(Value::Null, Value::Float),
12777 _ => unreachable!(),
12778 };
12779 let (_, _, idx) = &slice[i];
12780 out_vals[*idx] = value;
12781 }
12782 Ok(())
12783 }
12784 "lag" | "lead" => {
12785 if args.is_empty() {
12788 return Err(EngineError::Unsupported(alloc::format!(
12789 "{lower}() requires at least one argument"
12790 )));
12791 }
12792 let offset: i64 = if args.len() >= 2 {
12793 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12794 .map_err(EngineError::Eval)?;
12795 match v {
12796 Value::SmallInt(n) => i64::from(n),
12797 Value::Int(n) => i64::from(n),
12798 Value::BigInt(n) => n,
12799 _ => {
12800 return Err(EngineError::Unsupported(alloc::format!(
12801 "{lower}() offset must be integer"
12802 )));
12803 }
12804 }
12805 } else {
12806 1
12807 };
12808 let default: Value = if args.len() >= 3 {
12809 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12810 .map_err(EngineError::Eval)?
12811 } else {
12812 Value::Null
12813 };
12814 let values: Vec<Value> = slice
12815 .iter()
12816 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12817 .collect::<Result<_, _>>()
12818 .map_err(EngineError::Eval)?;
12819 let n = slice.len();
12820 for (i, (_, _, idx)) in slice.iter().enumerate() {
12821 let signed_offset = if lower == "lag" { -offset } else { offset };
12822 let v = if ignore_nulls {
12823 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12827 let needed: i64 = signed_offset.abs();
12828 if needed == 0 {
12829 values[i].clone()
12830 } else {
12831 let mut j: i64 = i as i64;
12832 let mut hits: i64 = 0;
12833 let mut found: Option<Value> = None;
12834 loop {
12835 j += step;
12836 if j < 0 || j >= n as i64 {
12837 break;
12838 }
12839 #[allow(clippy::cast_sign_loss)]
12840 let v = &values[j as usize];
12841 if !v.is_null() {
12842 hits += 1;
12843 if hits == needed {
12844 found = Some(v.clone());
12845 break;
12846 }
12847 }
12848 }
12849 found.unwrap_or_else(|| default.clone())
12850 }
12851 } else {
12852 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12853 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12854 default.clone()
12855 } else {
12856 #[allow(clippy::cast_sign_loss)]
12857 {
12858 values[target_signed as usize].clone()
12859 }
12860 }
12861 };
12862 out_vals[*idx] = v;
12863 }
12864 Ok(())
12865 }
12866 "first_value" | "last_value" | "nth_value" => {
12867 if args.is_empty() {
12868 return Err(EngineError::Unsupported(alloc::format!(
12869 "{lower}() requires at least one argument"
12870 )));
12871 }
12872 let values: Vec<Value> = slice
12873 .iter()
12874 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12875 .collect::<Result<_, _>>()
12876 .map_err(EngineError::Eval)?;
12877 let nth: usize = if lower == "nth_value" {
12878 if args.len() < 2 {
12879 return Err(EngineError::Unsupported(
12880 "nth_value() requires (expr, n)".into(),
12881 ));
12882 }
12883 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12884 .map_err(EngineError::Eval)?;
12885 let raw = match v {
12886 Value::SmallInt(n) => i64::from(n),
12887 Value::Int(n) => i64::from(n),
12888 Value::BigInt(n) => n,
12889 _ => {
12890 return Err(EngineError::Unsupported(
12891 "nth_value() n must be integer".into(),
12892 ));
12893 }
12894 };
12895 if raw < 1 {
12896 return Err(EngineError::Unsupported(
12897 "nth_value() n must be >= 1".into(),
12898 ));
12899 }
12900 #[allow(clippy::cast_sign_loss)]
12901 {
12902 raw as usize
12903 }
12904 } else {
12905 0
12906 };
12907 let eff = effective_frame(frame, ordered)?;
12908 for i in 0..slice.len() {
12909 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12910 let (_, _, idx) = &slice[i];
12911 let v = if lo > hi {
12912 Value::Null
12913 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
12914 if lower == "first_value" {
12917 (lo..=hi)
12918 .find_map(|j| {
12919 let v = &values[j];
12920 (!v.is_null()).then(|| v.clone())
12921 })
12922 .unwrap_or(Value::Null)
12923 } else {
12924 (lo..=hi)
12925 .rev()
12926 .find_map(|j| {
12927 let v = &values[j];
12928 (!v.is_null()).then(|| v.clone())
12929 })
12930 .unwrap_or(Value::Null)
12931 }
12932 } else {
12933 match lower.as_str() {
12934 "first_value" => values[lo].clone(),
12935 "last_value" => values[hi].clone(),
12936 "nth_value" => {
12937 let pos = lo + nth - 1;
12938 if pos > hi {
12939 Value::Null
12940 } else {
12941 values[pos].clone()
12942 }
12943 }
12944 _ => unreachable!(),
12945 }
12946 };
12947 out_vals[*idx] = v;
12948 }
12949 Ok(())
12950 }
12951 "ntile" => {
12952 if args.is_empty() {
12953 return Err(EngineError::Unsupported(
12954 "ntile(n) requires an integer argument".into(),
12955 ));
12956 }
12957 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
12958 .map_err(EngineError::Eval)?;
12959 let bucket_count: i64 = match v {
12960 Value::SmallInt(n) => i64::from(n),
12961 Value::Int(n) => i64::from(n),
12962 Value::BigInt(n) => n,
12963 _ => {
12964 return Err(EngineError::Unsupported(
12965 "ntile() argument must be integer".into(),
12966 ));
12967 }
12968 };
12969 if bucket_count < 1 {
12970 return Err(EngineError::Unsupported(
12971 "ntile() argument must be >= 1".into(),
12972 ));
12973 }
12974 #[allow(clippy::cast_sign_loss)]
12975 let buckets = bucket_count as usize;
12976 let n = slice.len();
12977 let base = n / buckets;
12980 let extras = n % buckets;
12981 let mut bucket: usize = 1;
12982 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
12983 let mut buckets_with_extra_remaining = extras;
12984 for (_, _, idx) in slice {
12985 if remaining_in_bucket == 0 {
12986 bucket += 1;
12987 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
12988 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
12989 base + 1
12990 } else {
12991 base
12992 };
12993 if remaining_in_bucket == 0 {
12996 remaining_in_bucket = 1;
12997 }
12998 }
12999 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
13000 remaining_in_bucket -= 1;
13001 }
13002 Ok(())
13003 }
13004 "percent_rank" => {
13005 let n = slice.len();
13008 let mut prev_key: Option<&[(Value, bool)]> = None;
13009 let mut current_rank: i64 = 1;
13010 for (i, (_, okey, idx)) in slice.iter().enumerate() {
13011 if let Some(p) = prev_key
13012 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
13013 {
13014 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
13015 }
13016 if prev_key.is_none() {
13017 current_rank = 1;
13018 }
13019 #[allow(clippy::cast_precision_loss)]
13020 let pr = if n <= 1 {
13021 0.0
13022 } else {
13023 (current_rank - 1) as f64 / (n - 1) as f64
13024 };
13025 out_vals[*idx] = Value::Float(pr);
13026 prev_key = Some(okey.as_slice());
13027 }
13028 Ok(())
13029 }
13030 "cume_dist" => {
13031 let n = slice.len();
13033 for i in 0..slice.len() {
13035 let peer_end = peer_group_end(slice, i);
13036 #[allow(clippy::cast_precision_loss)]
13037 let cd = (peer_end + 1) as f64 / n as f64;
13038 let (_, _, idx) = &slice[i];
13039 out_vals[*idx] = Value::Float(cd);
13040 }
13041 Ok(())
13042 }
13043 other => Err(EngineError::Unsupported(alloc::format!(
13044 "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)"
13045 ))),
13046 }
13047}
13048
13049fn effective_frame(
13056 frame: Option<&WindowFrame>,
13057 ordered: bool,
13058) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
13059 match frame {
13060 None => {
13061 if ordered {
13062 Ok((
13063 FrameKind::Range,
13064 FrameBound::UnboundedPreceding,
13065 FrameBound::CurrentRow,
13066 ))
13067 } else {
13068 Ok((
13069 FrameKind::Rows,
13070 FrameBound::UnboundedPreceding,
13071 FrameBound::UnboundedFollowing,
13072 ))
13073 }
13074 }
13075 Some(fr) => {
13076 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
13077 if matches!(fr.start, FrameBound::UnboundedFollowing)
13079 || matches!(end, FrameBound::UnboundedPreceding)
13080 {
13081 return Err(EngineError::Unsupported(alloc::format!(
13082 "invalid frame: start={:?} end={:?}",
13083 fr.start,
13084 end
13085 )));
13086 }
13087 if fr.kind == FrameKind::Range
13092 && (matches!(
13093 fr.start,
13094 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13095 ) || matches!(
13096 end,
13097 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
13098 ))
13099 {
13100 return Err(EngineError::Unsupported(
13101 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
13102 ));
13103 }
13104 Ok((fr.kind, fr.start.clone(), end))
13105 }
13106 }
13107}
13108
13109#[allow(clippy::type_complexity)]
13113fn frame_bounds_for_row(
13114 eff: &(FrameKind, FrameBound, FrameBound),
13115 i: usize,
13116 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
13117) -> (usize, usize) {
13118 let (kind, start, end) = eff;
13119 let n = slice.len();
13120 let last = n.saturating_sub(1);
13121 let (mut lo, mut hi) = match kind {
13122 FrameKind::Rows => {
13123 let lo = match start {
13124 FrameBound::UnboundedPreceding => 0,
13125 FrameBound::OffsetPreceding(k) => {
13126 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13127 i.saturating_sub(k)
13128 }
13129 FrameBound::CurrentRow => i,
13130 FrameBound::OffsetFollowing(k) => {
13131 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13132 i.saturating_add(k).min(last)
13133 }
13134 FrameBound::UnboundedFollowing => last,
13135 };
13136 let hi = match end {
13137 FrameBound::UnboundedPreceding => 0,
13138 FrameBound::OffsetPreceding(k) => {
13139 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13140 i.saturating_sub(k)
13141 }
13142 FrameBound::CurrentRow => i,
13143 FrameBound::OffsetFollowing(k) => {
13144 let k = usize::try_from(*k).unwrap_or(usize::MAX);
13145 i.saturating_add(k).min(last)
13146 }
13147 FrameBound::UnboundedFollowing => last,
13148 };
13149 (lo, hi)
13150 }
13151 FrameKind::Range => {
13152 let lo = match start {
13158 FrameBound::UnboundedPreceding => 0,
13159 FrameBound::CurrentRow => peer_group_start(slice, i),
13160 FrameBound::UnboundedFollowing => last,
13161 _ => unreachable!("offset bounds rejected for RANGE"),
13162 };
13163 let hi = match end {
13164 FrameBound::UnboundedPreceding => 0,
13165 FrameBound::CurrentRow => peer_group_end(slice, i),
13166 FrameBound::UnboundedFollowing => last,
13167 _ => unreachable!("offset bounds rejected for RANGE"),
13168 };
13169 (lo, hi)
13170 }
13171 };
13172 if hi >= n {
13173 hi = last;
13174 }
13175 if lo >= n {
13176 lo = last;
13177 }
13178 (lo, hi)
13179}
13180
13181#[allow(clippy::type_complexity)]
13185fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13186 let key = &slice[i].1;
13187 let mut j = i;
13188 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13189 j -= 1;
13190 }
13191 j
13192}
13193
13194#[allow(clippy::type_complexity)]
13197fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13198 let key = &slice[i].1;
13199 let mut j = i;
13200 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13201 j += 1;
13202 }
13203 j
13204}
13205
13206fn value_to_f64(v: &Value) -> Option<f64> {
13207 match v {
13208 Value::SmallInt(n) => Some(f64::from(*n)),
13209 Value::Int(n) => Some(f64::from(*n)),
13210 #[allow(clippy::cast_precision_loss)]
13211 Value::BigInt(n) => Some(*n as f64),
13212 Value::Float(x) => Some(*x),
13213 _ => None,
13214 }
13215}
13216
13217fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13221 let mut any = false;
13222 for item in &stmt.items {
13223 if let SelectItem::Expr { expr, .. } = item {
13224 any = any || expr_has_subquery(expr);
13225 }
13226 }
13227 if let Some(w) = &stmt.where_ {
13228 any = any || expr_has_subquery(w);
13229 }
13230 if let Some(h) = &stmt.having {
13231 any = any || expr_has_subquery(h);
13232 }
13233 for o in &stmt.order_by {
13234 any = any || expr_has_subquery(&o.expr);
13235 }
13236 for (_, peer) in &stmt.unions {
13237 any = any || expr_tree_has_subquery(peer);
13238 }
13239 any
13240}
13241
13242fn expr_has_subquery(e: &Expr) -> bool {
13243 match e {
13244 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13245 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13246 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13247 expr_has_subquery(expr)
13248 }
13249 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13250 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13251 Expr::Extract { source, .. } => expr_has_subquery(source),
13252 Expr::WindowFunction {
13253 args,
13254 partition_by,
13255 order_by,
13256 ..
13257 } => {
13258 args.iter().any(expr_has_subquery)
13259 || partition_by.iter().any(expr_has_subquery)
13260 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13261 }
13262 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13263 Expr::Array(items) => items.iter().any(expr_has_subquery),
13264 Expr::ArraySubscript { target, index } => {
13265 expr_has_subquery(target) || expr_has_subquery(index)
13266 }
13267 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13268 Expr::Case {
13269 operand,
13270 branches,
13271 else_branch,
13272 } => {
13273 operand.as_deref().is_some_and(expr_has_subquery)
13274 || branches
13275 .iter()
13276 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13277 || else_branch.as_deref().is_some_and(expr_has_subquery)
13278 }
13279 }
13280}
13281
13282fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13289 let lit = match v {
13290 Value::Null => Literal::Null,
13291 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13292 Value::Int(n) => Literal::Integer(i64::from(n)),
13293 Value::BigInt(n) => Literal::Integer(n),
13294 Value::Float(x) => Literal::Float(x),
13295 Value::Text(s) | Value::Json(s) => Literal::String(s),
13296 Value::Bool(b) => Literal::Bool(b),
13297 other => {
13298 return Err(EngineError::Unsupported(alloc::format!(
13299 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13300 other.data_type()
13301 )));
13302 }
13303 };
13304 Ok(Expr::Literal(lit))
13305}
13306
13307fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13313 let lit = match v {
13314 Value::Null => Literal::Null,
13315 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13316 Value::Int(n) => Literal::Integer(i64::from(n)),
13317 Value::BigInt(n) => Literal::Integer(n),
13318 Value::Float(x) => Literal::Float(x),
13319 Value::Text(s) | Value::Json(s) => Literal::String(s),
13320 Value::Bool(b) => Literal::Bool(b),
13321 Value::Vector(xs) => Literal::Vector(xs),
13322 Value::Date(days) => {
13326 let micros = (i64::from(days)) * 86_400_000_000;
13327 Literal::String(format_timestamp_micros_as_date(micros))
13328 }
13329 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13330 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13331 other => {
13332 return Err(EngineError::Unsupported(alloc::format!(
13333 "INSERT … SELECT cannot materialise value of type {:?}; \
13334 add an explicit CAST in the inner SELECT",
13335 other.data_type()
13336 )));
13337 }
13338 };
13339 Ok(Expr::Literal(lit))
13340}
13341
13342fn format_timestamp_micros(us: i64) -> String {
13343 let days = us.div_euclid(86_400_000_000);
13345 let intra_day = us.rem_euclid(86_400_000_000);
13346 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13347 let secs = intra_day / 1_000_000;
13348 let us_rem = intra_day % 1_000_000;
13349 let h = (secs / 3600) % 24;
13350 let m = (secs / 60) % 60;
13351 let s = secs % 60;
13352 if us_rem == 0 {
13353 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13354 } else {
13355 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13356 }
13357}
13358
13359fn format_timestamp_micros_as_date(us: i64) -> String {
13360 let days = us.div_euclid(86_400_000_000);
13363 let jdn = days + 2_440_588;
13365 let (y, mo, d) = jdn_to_ymd(jdn);
13366 alloc::format!("{y:04}-{mo:02}-{d:02}")
13367}
13368
13369fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13370 let l = jdn + 68569;
13372 let n = (4 * l) / 146_097;
13373 let l = l - (146_097 * n + 3) / 4;
13374 let i = (4000 * (l + 1)) / 1_461_001;
13375 let l = l - (1461 * i) / 4 + 31;
13376 let j = (80 * l) / 2447;
13377 let day = (l - (2447 * j) / 80) as u32;
13378 let l = j / 11;
13379 let month = (j + 2 - 12 * l) as u32;
13380 let year = 100 * (n - 49) + i + l;
13381 (year, month, day)
13382}
13383
13384fn format_numeric(scaled: i128, scale: u8) -> String {
13385 if scale == 0 {
13386 return alloc::format!("{scaled}");
13387 }
13388 let abs = scaled.unsigned_abs();
13389 let divisor = 10u128.pow(u32::from(scale));
13390 let whole = abs / divisor;
13391 let frac = abs % divisor;
13392 let sign = if scaled < 0 { "-" } else { "" };
13393 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13394}
13395
13396fn rewrite_column_in_source(
13420 src: &str,
13421 old: &str,
13422 new: &str,
13423) -> Result<alloc::string::String, EngineError> {
13424 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13425 EngineError::Unsupported(alloc::format!(
13426 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13427 failed to parse for rewrite ({e})"
13428 ))
13429 })?;
13430 rewrite_column_in_expr(&mut expr, old, new);
13431 Ok(alloc::format!("{expr}"))
13432}
13433
13434fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13442 match e {
13443 Expr::Column(c) => {
13444 if c.name.eq_ignore_ascii_case(old) {
13445 c.name = new.to_string();
13446 }
13447 }
13448 Expr::Binary { lhs, rhs, .. } => {
13449 rewrite_column_in_expr(lhs, old, new);
13450 rewrite_column_in_expr(rhs, old, new);
13451 }
13452 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13453 rewrite_column_in_expr(expr, old, new);
13454 }
13455 Expr::FunctionCall { args, .. } => {
13456 for a in args {
13457 rewrite_column_in_expr(a, old, new);
13458 }
13459 }
13460 Expr::Like { expr, pattern, .. } => {
13461 rewrite_column_in_expr(expr, old, new);
13462 rewrite_column_in_expr(pattern, old, new);
13463 }
13464 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13465 Expr::WindowFunction {
13466 args,
13467 partition_by,
13468 order_by,
13469 ..
13470 } => {
13471 for a in args {
13472 rewrite_column_in_expr(a, old, new);
13473 }
13474 for p in partition_by {
13475 rewrite_column_in_expr(p, old, new);
13476 }
13477 for (o, _) in order_by {
13478 rewrite_column_in_expr(o, old, new);
13479 }
13480 }
13481 Expr::Array(items) => {
13482 for elem in items {
13483 rewrite_column_in_expr(elem, old, new);
13484 }
13485 }
13486 Expr::ArraySubscript { target, index } => {
13487 rewrite_column_in_expr(target, old, new);
13488 rewrite_column_in_expr(index, old, new);
13489 }
13490 Expr::AnyAll { expr, array, .. } => {
13491 rewrite_column_in_expr(expr, old, new);
13492 rewrite_column_in_expr(array, old, new);
13493 }
13494 Expr::Case {
13495 operand,
13496 branches,
13497 else_branch,
13498 } => {
13499 if let Some(o) = operand {
13500 rewrite_column_in_expr(o, old, new);
13501 }
13502 for (w, t) in branches {
13503 rewrite_column_in_expr(w, old, new);
13504 rewrite_column_in_expr(t, old, new);
13505 }
13506 if let Some(e) = else_branch {
13507 rewrite_column_in_expr(e, old, new);
13508 }
13509 }
13510 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13514 Expr::Literal(_) | Expr::Placeholder(_) => {}
13515 }
13516}
13517
13518pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13526 match stmt {
13527 Statement::Select(s) => substitute_select(s, params)?,
13528 Statement::Insert(ins) => {
13529 for row in &mut ins.rows {
13530 for e in row {
13531 substitute_expr(e, params)?;
13532 }
13533 }
13534 if let Some(clause) = &mut ins.on_conflict
13538 && let spg_sql::ast::OnConflictAction::Update {
13539 assignments,
13540 where_,
13541 } = &mut clause.action
13542 {
13543 for (_, e) in assignments.iter_mut() {
13544 substitute_expr(e, params)?;
13545 }
13546 if let Some(w) = where_ {
13547 substitute_expr(w, params)?;
13548 }
13549 }
13550 }
13551 Statement::Update(u) => {
13552 for (_, e) in &mut u.assignments {
13553 substitute_expr(e, params)?;
13554 }
13555 if let Some(w) = &mut u.where_ {
13556 substitute_expr(w, params)?;
13557 }
13558 }
13559 Statement::Delete(d) => {
13560 if let Some(w) = &mut d.where_ {
13561 substitute_expr(w, params)?;
13562 }
13563 }
13564 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13565 _ => {}
13568 }
13569 Ok(())
13570}
13571
13572fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13573 for item in &mut s.items {
13574 if let SelectItem::Expr { expr, .. } = item {
13575 substitute_expr(expr, params)?;
13576 }
13577 }
13578 if let Some(w) = &mut s.where_ {
13579 substitute_expr(w, params)?;
13580 }
13581 if let Some(gs) = &mut s.group_by {
13582 for g in gs {
13583 substitute_expr(g, params)?;
13584 }
13585 }
13586 if let Some(h) = &mut s.having {
13587 substitute_expr(h, params)?;
13588 }
13589 for o in &mut s.order_by {
13590 substitute_expr(&mut o.expr, params)?;
13591 }
13592 for (_, peer) in &mut s.unions {
13593 substitute_select(peer, params)?;
13594 }
13595 if let Some(le) = s.limit {
13600 s.limit = Some(resolve_limit_placeholder(le, params)?);
13601 }
13602 if let Some(le) = s.offset {
13603 s.offset = Some(resolve_limit_placeholder(le, params)?);
13604 }
13605 Ok(())
13606}
13607
13608fn resolve_limit_placeholder(
13609 le: spg_sql::ast::LimitExpr,
13610 params: &[Value],
13611) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13612 use spg_sql::ast::LimitExpr;
13613 match le {
13614 LimitExpr::Literal(_) => Ok(le),
13615 LimitExpr::Placeholder(n) => {
13616 let idx = usize::from(n).saturating_sub(1);
13617 let v = params.get(idx).ok_or_else(|| {
13618 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13619 n,
13620 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13621 })
13622 })?;
13623 let int = match v {
13624 Value::SmallInt(x) => Some(i64::from(*x)),
13625 Value::Int(x) => Some(i64::from(*x)),
13626 Value::BigInt(x) => Some(*x),
13627 _ => None,
13628 }
13629 .ok_or_else(|| {
13630 EngineError::Unsupported(alloc::format!(
13631 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13632 ))
13633 })?;
13634 if int < 0 {
13635 return Err(EngineError::Unsupported(alloc::format!(
13636 "LIMIT/OFFSET ${n} bound to negative value {int}"
13637 )));
13638 }
13639 let bounded = u32::try_from(int).map_err(|_| {
13640 EngineError::Unsupported(alloc::format!(
13641 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13642 ))
13643 })?;
13644 Ok(LimitExpr::Literal(bounded))
13645 }
13646 }
13647}
13648
13649fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13650 if let Expr::Placeholder(n) = e {
13651 let idx = usize::from(*n).saturating_sub(1);
13652 let v = params.get(idx).ok_or_else(|| {
13653 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13654 n: *n,
13655 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13656 })
13657 })?;
13658 *e = Expr::Literal(value_to_literal(v.clone()));
13659 return Ok(());
13660 }
13661 match e {
13662 Expr::Binary { lhs, rhs, .. } => {
13663 substitute_expr(lhs, params)?;
13664 substitute_expr(rhs, params)?;
13665 }
13666 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13667 substitute_expr(expr, params)?;
13668 }
13669 Expr::FunctionCall { args, .. } => {
13670 for a in args {
13671 substitute_expr(a, params)?;
13672 }
13673 }
13674 Expr::Like { expr, pattern, .. } => {
13675 substitute_expr(expr, params)?;
13676 substitute_expr(pattern, params)?;
13677 }
13678 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13679 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13680 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13681 Expr::InSubquery { expr, subquery, .. } => {
13682 substitute_expr(expr, params)?;
13683 substitute_select(subquery, params)?;
13684 }
13685 Expr::WindowFunction {
13686 args,
13687 partition_by,
13688 order_by,
13689 ..
13690 } => {
13691 for a in args {
13692 substitute_expr(a, params)?;
13693 }
13694 for p in partition_by {
13695 substitute_expr(p, params)?;
13696 }
13697 for (e, _) in order_by {
13698 substitute_expr(e, params)?;
13699 }
13700 }
13701 Expr::Literal(_) | Expr::Column(_) => {}
13702 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13704 Expr::Array(items) => {
13705 for elem in items {
13706 substitute_expr(elem, params)?;
13707 }
13708 }
13709 Expr::ArraySubscript { target, index } => {
13710 substitute_expr(target, params)?;
13711 substitute_expr(index, params)?;
13712 }
13713 Expr::AnyAll { expr, array, .. } => {
13714 substitute_expr(expr, params)?;
13715 substitute_expr(array, params)?;
13716 }
13717 Expr::Case {
13718 operand,
13719 branches,
13720 else_branch,
13721 } => {
13722 if let Some(o) = operand {
13723 substitute_expr(o, params)?;
13724 }
13725 for (w, t) in branches {
13726 substitute_expr(w, params)?;
13727 substitute_expr(t, params)?;
13728 }
13729 if let Some(e) = else_branch {
13730 substitute_expr(e, params)?;
13731 }
13732 }
13733 }
13734 Ok(())
13735}
13736
13737fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13755 use core::cmp::Ordering;
13756 match (a, b) {
13757 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13758 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13759 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13760 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13761 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13762 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13763 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13764 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13765 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13766 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13767 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13768 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13769 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13770 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13771 (Value::SmallInt(n), Value::Float(x)) => {
13773 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13774 }
13775 (Value::Float(x), Value::SmallInt(n)) => {
13776 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13777 }
13778 (Value::Int(n), Value::Float(x)) => {
13779 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13780 }
13781 (Value::Float(x), Value::Int(n)) => {
13782 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13783 }
13784 (Value::BigInt(n), Value::Float(x)) => {
13785 #[allow(clippy::cast_precision_loss)]
13786 let nf = *n as f64;
13787 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13788 }
13789 (Value::Float(x), Value::BigInt(n)) => {
13790 #[allow(clippy::cast_precision_loss)]
13791 let nf = *n as f64;
13792 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13793 }
13794 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13797 }
13798}
13799
13800fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13807 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13808 out.push('[');
13809 for (i, b) in bounds.iter().enumerate() {
13810 if i > 0 {
13811 out.push_str(", ");
13812 }
13813 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13814 if needs_quote {
13815 out.push('"');
13816 for ch in b.chars() {
13817 if ch == '"' || ch == '\\' {
13818 out.push('\\');
13819 }
13820 out.push(ch);
13821 }
13822 out.push('"');
13823 } else {
13824 out.push_str(b);
13825 }
13826 }
13827 out.push(']');
13828 out
13829}
13830
13831pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13841 match v {
13842 Value::Null => "NULL".to_string(),
13843 Value::SmallInt(n) => alloc::format!("{n}"),
13844 Value::Int(n) => alloc::format!("{n}"),
13845 Value::BigInt(n) => alloc::format!("{n}"),
13846 Value::Float(x) => alloc::format!("{x:?}"),
13847 Value::Text(s) | Value::Json(s) => s.clone(),
13848 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13849 Value::Date(d) => eval::format_date(*d),
13850 Value::Timestamp(t) => eval::format_timestamp(*t),
13851 Value::Time(us) => eval::format_time(*us),
13853 Value::Year(y) => alloc::format!("{y:04}"),
13855 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13857 Value::Money(c) => eval::format_money(*c),
13859 v @ Value::Range { .. } => format_range_str(v),
13861 Value::Hstore(pairs) => format_hstore_str(pairs),
13863 Value::IntArray2D(rows) => format_int_2d_text(rows),
13865 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13866 Value::TextArray2D(rows) => format_text_2d_text(rows),
13867 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13868 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13869 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13870 alloc::format!("{v:?}")
13874 }
13875 _ => alloc::format!("{v:?}"),
13879 }
13880}
13881
13882const fn is_internal_table_name(_name: &str) -> bool {
13889 false
13890}
13891
13892fn value_to_literal(v: Value) -> Literal {
13893 match v {
13894 Value::Null => Literal::Null,
13895 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13896 Value::Int(n) => Literal::Integer(i64::from(n)),
13897 Value::BigInt(n) => Literal::Integer(n),
13898 Value::Float(x) => Literal::Float(x),
13899 Value::Text(s) | Value::Json(s) => Literal::String(s),
13900 Value::Bool(b) => Literal::Bool(b),
13901 Value::Vector(v) => Literal::Vector(v),
13902 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
13903 Value::Date(d) => Literal::String(eval::format_date(d)),
13904 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
13905 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
13911 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
13916 Value::TextArray(items) => Literal::TextArray(items),
13921 Value::IntArray(items) => Literal::IntArray(items),
13922 Value::BigIntArray(items) => Literal::BigIntArray(items),
13923 Value::Interval { months, micros } => Literal::Interval {
13924 months,
13925 micros,
13926 text: eval::format_interval(months, micros),
13927 },
13928 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
13931 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
13932 v => Literal::String(alloc::format!("{v:?}")),
13936 }
13937}
13938
13939fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
13940 let Some(now) = now_micros else {
13941 return;
13942 };
13943 match stmt {
13944 Statement::Select(s) => rewrite_select_clock(s, now),
13945 Statement::Insert(ins) => {
13946 for row in &mut ins.rows {
13947 for e in row {
13948 rewrite_expr_clock(e, now);
13949 }
13950 }
13951 if let Some(clause) = &mut ins.on_conflict
13955 && let spg_sql::ast::OnConflictAction::Update {
13956 assignments,
13957 where_,
13958 } = &mut clause.action
13959 {
13960 for (_, e) in assignments.iter_mut() {
13961 rewrite_expr_clock(e, now);
13962 }
13963 if let Some(w) = where_ {
13964 rewrite_expr_clock(w, now);
13965 }
13966 }
13967 }
13968 Statement::Update(u) => {
13972 for (_, e) in &mut u.assignments {
13973 rewrite_expr_clock(e, now);
13974 }
13975 if let Some(w) = &mut u.where_ {
13976 rewrite_expr_clock(w, now);
13977 }
13978 }
13979 Statement::Delete(d) => {
13980 if let Some(w) = &mut d.where_ {
13981 rewrite_expr_clock(w, now);
13982 }
13983 }
13984 _ => {}
13985 }
13986}
13987
13988fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
13989 for item in &mut s.items {
13990 if let SelectItem::Expr { expr, .. } = item {
13991 rewrite_expr_clock(expr, now);
13992 }
13993 }
13994 if let Some(w) = &mut s.where_ {
13995 rewrite_expr_clock(w, now);
13996 }
13997 if let Some(gs) = &mut s.group_by {
13998 for g in gs {
13999 rewrite_expr_clock(g, now);
14000 }
14001 }
14002 if let Some(h) = &mut s.having {
14003 rewrite_expr_clock(h, now);
14004 }
14005 for o in &mut s.order_by {
14006 rewrite_expr_clock(&mut o.expr, now);
14007 }
14008 for (_, peer) in &mut s.unions {
14009 rewrite_select_clock(peer, now);
14010 }
14011}
14012
14013fn rewrite_expr_clock(e: &mut Expr, now: i64) {
14021 if let Some(replacement) = clock_replacement_for(e, now) {
14025 *e = replacement;
14026 return;
14027 }
14028 match e {
14029 Expr::Binary { lhs, rhs, .. } => {
14030 rewrite_expr_clock(lhs, now);
14031 rewrite_expr_clock(rhs, now);
14032 }
14033 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
14034 rewrite_expr_clock(expr, now);
14035 }
14036 Expr::FunctionCall { args, .. } => {
14037 for a in args {
14038 rewrite_expr_clock(a, now);
14039 }
14040 }
14041 Expr::Like { expr, pattern, .. } => {
14042 rewrite_expr_clock(expr, now);
14043 rewrite_expr_clock(pattern, now);
14044 }
14045 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
14046 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
14050 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
14051 Expr::InSubquery { expr, subquery, .. } => {
14052 rewrite_expr_clock(expr, now);
14053 rewrite_select_clock(subquery, now);
14054 }
14055 Expr::WindowFunction {
14058 args,
14059 partition_by,
14060 order_by,
14061 ..
14062 } => {
14063 for a in args {
14064 rewrite_expr_clock(a, now);
14065 }
14066 for p in partition_by {
14067 rewrite_expr_clock(p, now);
14068 }
14069 for (e, _) in order_by {
14070 rewrite_expr_clock(e, now);
14071 }
14072 }
14073 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
14074 Expr::Array(items) => {
14075 for elem in items {
14076 rewrite_expr_clock(elem, now);
14077 }
14078 }
14079 Expr::ArraySubscript { target, index } => {
14080 rewrite_expr_clock(target, now);
14081 rewrite_expr_clock(index, now);
14082 }
14083 Expr::AnyAll { expr, array, .. } => {
14084 rewrite_expr_clock(expr, now);
14085 rewrite_expr_clock(array, now);
14086 }
14087 Expr::Case {
14088 operand,
14089 branches,
14090 else_branch,
14091 } => {
14092 if let Some(o) = operand {
14093 rewrite_expr_clock(o, now);
14094 }
14095 for (w, t) in branches {
14096 rewrite_expr_clock(w, now);
14097 rewrite_expr_clock(t, now);
14098 }
14099 if let Some(e) = else_branch {
14100 rewrite_expr_clock(e, now);
14101 }
14102 }
14103 }
14104}
14105
14106fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
14113 let (kind, name) = match e {
14114 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
14115 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
14116 _ => return None,
14117 };
14118 enum ClockShape {
14126 Timestamp,
14127 Date,
14128 UnixSeconds,
14129 }
14130 let shape = match name.len() {
14131 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
14132 Some(ClockShape::Timestamp)
14133 }
14134 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
14135 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
14136 Some(ClockShape::UnixSeconds)
14137 }
14138 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
14139 _ => None,
14140 };
14141 let shape = shape?;
14142 let payload = match shape {
14143 ClockShape::Timestamp => now,
14144 ClockShape::Date => now.div_euclid(86_400_000_000),
14145 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
14146 };
14147 let target = match shape {
14148 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
14149 ClockShape::Date => spg_sql::ast::CastTarget::Date,
14150 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
14151 };
14152 Some(Expr::Cast {
14153 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
14154 target,
14155 })
14156}
14157
14158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14159enum ClockSite {
14160 Fn,
14161 BareIdent,
14162}
14163
14164fn expand_group_by_all(s: &mut SelectStatement) {
14175 if !s.group_by_all {
14176 for (_, peer) in &mut s.unions {
14177 expand_group_by_all(peer);
14178 }
14179 return;
14180 }
14181 let mut groups: Vec<Expr> = Vec::new();
14182 for item in &s.items {
14183 if let SelectItem::Expr { expr, .. } = item
14184 && !aggregate::contains_aggregate(expr)
14185 {
14186 groups.push(expr.clone());
14187 }
14188 }
14189 s.group_by = Some(groups);
14190 s.group_by_all = false;
14191 for (_, peer) in &mut s.unions {
14192 expand_group_by_all(peer);
14193 }
14194}
14195
14196fn resolve_order_by_position(s: &mut SelectStatement) {
14197 for order in &mut s.order_by {
14202 match &order.expr {
14203 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
14204 if let Ok(idx_one_based) = usize::try_from(*n) {
14205 let idx = idx_one_based - 1;
14206 if idx < s.items.len()
14207 && let SelectItem::Expr { expr, .. } = &s.items[idx]
14208 {
14209 order.expr = expr.clone();
14210 }
14211 }
14212 }
14213 Expr::Column(c) if c.qualifier.is_none() => {
14214 for item in &s.items {
14216 if let SelectItem::Expr {
14217 expr,
14218 alias: Some(a),
14219 } = item
14220 && a == &c.name
14221 {
14222 order.expr = expr.clone();
14223 break;
14224 }
14225 }
14226 }
14227 _ => {}
14228 }
14229 }
14230 for (_, peer) in &mut s.unions {
14231 resolve_order_by_position(peer);
14232 }
14233}
14234
14235fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14248 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14249 match keep {
14250 Some(k) if k < tagged.len() && k > 0 => {
14251 let pivot = k - 1;
14252 tagged.select_nth_unstable_by(pivot, cmp);
14253 tagged[..k].sort_by(cmp);
14254 tagged.truncate(k);
14255 }
14256 _ => {
14257 tagged.sort_by(cmp);
14258 }
14259 }
14260}
14261
14262fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14263 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14264}
14265
14266fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14270 use core::cmp::Ordering;
14271 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14272 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14273 let ord = if descs.get(i).copied().unwrap_or(false) {
14274 ord.reverse()
14275 } else {
14276 ord
14277 };
14278 if ord != Ordering::Equal {
14279 return ord;
14280 }
14281 }
14282 Ordering::Equal
14283}
14284
14285fn build_order_keys(
14288 order_by: &[OrderBy],
14289 row: &Row,
14290 ctx: &EvalContext,
14291) -> Result<Vec<f64>, EngineError> {
14292 let mut keys = Vec::with_capacity(order_by.len());
14293 for o in order_by {
14294 let v = eval::eval_expr(&o.expr, row, ctx)?;
14295 keys.push(value_to_order_key(&v)?);
14296 }
14297 Ok(keys)
14298}
14299
14300fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14304 if let Some(off) = offset {
14305 let off = off as usize;
14306 if off >= rows.len() {
14307 rows.clear();
14308 } else {
14309 rows.drain(..off);
14310 }
14311 }
14312 if let Some(n) = limit {
14313 rows.truncate(n as usize);
14314 }
14315}
14316
14317fn apply_offset_and_limit_tagged(
14328 tagged: &mut Vec<(Vec<f64>, Row)>,
14329 offset: Option<u32>,
14330 limit: Option<u32>,
14331 with_ties: bool,
14332) {
14333 if let Some(off) = offset {
14334 let off = off as usize;
14335 if off >= tagged.len() {
14336 tagged.clear();
14337 } else {
14338 tagged.drain(..off);
14339 }
14340 }
14341 if let Some(n) = limit {
14342 let n = n as usize;
14343 if with_ties && n > 0 && n < tagged.len() {
14344 let cutoff_key = tagged[n - 1].0.clone();
14345 let mut end = n;
14346 while end < tagged.len() && tagged[end].0 == cutoff_key {
14347 end += 1;
14348 }
14349 tagged.truncate(end);
14350 } else {
14351 tagged.truncate(n);
14352 }
14353 }
14354}
14355
14356fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14362 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14363 return Err(EngineError::Unsupported(alloc::string::String::from(
14364 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14365 )));
14366 }
14367 Ok(())
14368}
14369
14370fn resolve_foreign_key(
14384 local_table_name: &str,
14385 local_cols: &[ColumnSchema],
14386 fk: spg_sql::ast::ForeignKeyConstraint,
14387 catalog: &Catalog,
14388) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14389 let mut local_columns = Vec::with_capacity(fk.columns.len());
14391 for name in &fk.columns {
14392 let pos = local_cols
14393 .iter()
14394 .position(|c| c.name == *name)
14395 .ok_or_else(|| {
14396 EngineError::Unsupported(alloc::format!(
14397 "FOREIGN KEY references unknown local column {name:?}"
14398 ))
14399 })?;
14400 local_columns.push(pos);
14401 }
14402 let is_self_ref = fk.parent_table == local_table_name;
14406 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14407 (local_cols, local_table_name)
14408 } else {
14409 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14410 EngineError::Storage(StorageError::TableNotFound {
14411 name: fk.parent_table.clone(),
14412 })
14413 })?;
14414 (
14415 parent_table.schema().columns.as_slice(),
14416 fk.parent_table.as_str(),
14417 )
14418 };
14419 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14424 if fk.columns.len() != 1 {
14425 return Err(EngineError::Unsupported(
14426 "composite FOREIGN KEY without explicit parent column list is not supported \
14427 — list the parent columns explicitly"
14428 .into(),
14429 ));
14430 }
14431 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14433 .ok_or_else(|| {
14434 EngineError::Unsupported(alloc::format!(
14435 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14436 to default the FOREIGN KEY against"
14437 ))
14438 })?;
14439 alloc::vec![pos]
14440 } else {
14441 let mut out = Vec::with_capacity(fk.parent_columns.len());
14442 for name in &fk.parent_columns {
14443 let pos = parent_cols_for_lookup
14444 .iter()
14445 .position(|c| c.name == *name)
14446 .ok_or_else(|| {
14447 EngineError::Unsupported(alloc::format!(
14448 "FOREIGN KEY references unknown parent column \
14449 {name:?} on table {parent_table_str:?}"
14450 ))
14451 })?;
14452 out.push(pos);
14453 }
14454 out
14455 };
14456 if parent_columns.len() != local_columns.len() {
14457 return Err(EngineError::Unsupported(alloc::format!(
14458 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14459 local_columns.len(),
14460 parent_columns.len()
14461 )));
14462 }
14463 if !is_self_ref {
14473 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14474 let primary_parent_col = parent_columns[0];
14475 let has_btree = parent_table
14476 .schema()
14477 .columns
14478 .get(primary_parent_col)
14479 .is_some()
14480 && parent_table.indices().iter().any(|idx| {
14481 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14482 && idx.column_position == primary_parent_col
14483 && idx.partial_predicate.is_none()
14484 });
14485 if !has_btree {
14486 return Err(EngineError::Unsupported(alloc::format!(
14487 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14488 index — create one with `CREATE INDEX ... ON {} ({})` first",
14489 parent_table_str,
14490 parent_table_str,
14491 parent_table.schema().columns[primary_parent_col].name,
14492 )));
14493 }
14494 }
14495 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14496 let on_update = fk_action_sql_to_storage(fk.on_update);
14497 Ok(spg_storage::ForeignKeyConstraint {
14498 name: fk.name,
14499 local_columns,
14500 parent_table: fk.parent_table,
14501 parent_columns,
14502 on_delete,
14503 on_update,
14504 })
14505}
14506
14507fn pick_pk_index_column(
14513 catalog: &Catalog,
14514 parent_name: &str,
14515 is_self_ref: bool,
14516 local_cols: &[ColumnSchema],
14517) -> Option<usize> {
14518 if is_self_ref {
14519 let _ = local_cols;
14523 return Some(0);
14524 }
14525 let parent = catalog.get(parent_name)?;
14526 parent.indices().iter().find_map(|idx| {
14527 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14528 && idx.partial_predicate.is_none()
14529 && idx.included_columns.is_empty()
14530 && idx.expression.is_none()
14531 {
14532 Some(idx.column_position)
14533 } else {
14534 None
14535 }
14536 })
14537}
14538
14539fn resolve_on_conflict_columns(
14546 catalog: &Catalog,
14547 table_name: &str,
14548 target: &[String],
14549) -> Result<Vec<usize>, EngineError> {
14550 let table = catalog.get(table_name).ok_or_else(|| {
14551 EngineError::Storage(StorageError::TableNotFound {
14552 name: table_name.into(),
14553 })
14554 })?;
14555 if target.is_empty() {
14556 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14566 return Ok(uc.columns.clone());
14567 }
14568 let pos = table
14569 .indices()
14570 .iter()
14571 .find_map(|idx| {
14572 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14573 && idx.partial_predicate.is_none()
14574 && idx.included_columns.is_empty()
14575 && idx.expression.is_none()
14576 {
14577 Some(idx.column_position)
14578 } else {
14579 None
14580 }
14581 })
14582 .ok_or_else(|| {
14583 EngineError::Unsupported(alloc::format!(
14584 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14585 ))
14586 })?;
14587 return Ok(alloc::vec![pos]);
14588 }
14589 let mut out = Vec::with_capacity(target.len());
14590 for name in target {
14591 let pos = table
14592 .schema()
14593 .columns
14594 .iter()
14595 .position(|c| c.name == *name)
14596 .ok_or_else(|| {
14597 EngineError::Unsupported(alloc::format!(
14598 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14599 ))
14600 })?;
14601 out.push(pos);
14602 }
14603 Ok(out)
14604}
14605
14606fn on_conflict_key_exists(
14609 catalog: &Catalog,
14610 table_name: &str,
14611 column_pos: usize,
14612 key: &Value,
14613) -> bool {
14614 let Some(table) = catalog.get(table_name) else {
14615 return false;
14616 };
14617 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14618 return false;
14619 };
14620 table.indices().iter().any(|idx| {
14621 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14622 && idx.column_position == column_pos
14623 && idx.partial_predicate.is_none()
14624 && !idx.lookup_eq(&idx_key).is_empty()
14625 })
14626}
14627
14628fn lookup_row_position_by_keys(
14634 catalog: &Catalog,
14635 table_name: &str,
14636 column_positions: &[usize],
14637 key: &[&Value],
14638) -> Option<usize> {
14639 let table = catalog.get(table_name)?;
14640 table.rows().iter().position(|r| {
14641 column_positions
14642 .iter()
14643 .enumerate()
14644 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14645 })
14646}
14647
14648fn on_conflict_keys_exist(
14653 catalog: &Catalog,
14654 table_name: &str,
14655 column_positions: &[usize],
14656 key: &[&Value],
14657) -> bool {
14658 if column_positions.len() == 1 {
14659 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14660 }
14661 let Some(table) = catalog.get(table_name) else {
14662 return false;
14663 };
14664 table.rows().iter().any(|r| {
14665 column_positions
14666 .iter()
14667 .enumerate()
14668 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14669 })
14670}
14671
14672fn apply_on_conflict_assignments(
14685 catalog: &Catalog,
14686 table_name: &str,
14687 target_pos: usize,
14688 incoming: &[Value],
14689 assignments: &[(String, Expr)],
14690 where_: Option<&Expr>,
14691) -> Result<Option<Vec<Value>>, EngineError> {
14692 let table = catalog.get(table_name).ok_or_else(|| {
14693 EngineError::Storage(StorageError::TableNotFound {
14694 name: table_name.into(),
14695 })
14696 })?;
14697 let schema_cols = table.schema().columns.clone();
14698 let existing = table
14699 .rows()
14700 .get(target_pos)
14701 .ok_or_else(|| {
14702 EngineError::Unsupported(alloc::format!(
14703 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14704 ))
14705 })?
14706 .clone();
14707 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14708 if let Some(w) = where_ {
14710 let pred = w.clone();
14711 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14712 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14713 if !matches!(v, Value::Bool(true)) {
14714 return Ok(None);
14715 }
14716 }
14717 let mut new_values = existing.values.clone();
14718 for (col_name, expr) in assignments {
14719 let target_idx = schema_cols
14720 .iter()
14721 .position(|c| c.name == *col_name)
14722 .ok_or_else(|| {
14723 EngineError::Eval(EvalError::ColumnNotFound {
14724 name: col_name.clone(),
14725 })
14726 })?;
14727 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14728 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14729 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14730 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14731 new_values[target_idx] = coerced;
14732 }
14733 Ok(Some(new_values))
14734}
14735
14736fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14741 use spg_sql::ast::ColumnName;
14742 match expr {
14743 Expr::Column(ColumnName { qualifier, name })
14744 if qualifier
14745 .as_deref()
14746 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14747 {
14748 let pos = schema_cols.iter().position(|c| c.name == name);
14749 match pos {
14750 Some(p) => {
14751 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14752 value_to_literal_expr(v)
14753 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14754 }
14755 None => Expr::Column(ColumnName { qualifier, name }),
14756 }
14757 }
14758 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14759 op,
14760 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14761 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14762 },
14763 Expr::Unary { op, expr } => Expr::Unary {
14764 op,
14765 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14766 },
14767 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14768 name,
14769 args: args
14770 .into_iter()
14771 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14772 .collect(),
14773 },
14774 other => other,
14775 }
14776}
14777
14778fn enforce_uniqueness_inserts(
14801 catalog: &Catalog,
14802 child_table: &str,
14803 constraints: &[spg_storage::UniquenessConstraint],
14804 rows: &[Vec<Value>],
14805) -> Result<(), EngineError> {
14806 if constraints.is_empty() {
14807 return Ok(());
14808 }
14809 let table = catalog.get(child_table).ok_or_else(|| {
14810 EngineError::Storage(StorageError::TableNotFound {
14811 name: child_table.into(),
14812 })
14813 })?;
14814 let schema = table.schema();
14815 for uc in constraints {
14816 for (batch_idx, row_values) in rows.iter().enumerate() {
14817 let key: Vec<Value> = uc
14826 .columns
14827 .iter()
14828 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14829 .collect();
14830 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14831 if has_null && !uc.nulls_not_distinct {
14836 continue;
14837 }
14838 let collides_in_table = table.rows().iter().any(|prow| {
14840 uc.columns.iter().enumerate().all(|(i, &p)| {
14841 prow.values
14842 .get(p)
14843 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14844 })
14845 });
14846 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14848 uc.columns.iter().enumerate().all(|(i, &p)| {
14849 earlier
14850 .get(p)
14851 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14852 })
14853 });
14854 if collides_in_table || collides_in_batch {
14855 let kind = if uc.is_primary_key {
14856 "PRIMARY KEY"
14857 } else {
14858 "UNIQUE"
14859 };
14860 let col_names: Vec<String> = uc
14861 .columns
14862 .iter()
14863 .map(|&i| table.schema().columns[i].name.clone())
14864 .collect();
14865 return Err(EngineError::Unsupported(alloc::format!(
14866 "{kind} violation on {child_table:?} columns {col_names:?}: \
14867 row #{batch_idx} duplicates an existing key"
14868 )));
14869 }
14870 }
14871 }
14872 Ok(())
14873}
14874
14875fn collated_key_cell(
14882 v: &spg_storage::Value,
14883 column_position: usize,
14884 schema: &spg_storage::TableSchema,
14885) -> spg_storage::Value {
14886 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14887 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14888 spg_storage::Value::Text(s.to_ascii_lowercase())
14889 }
14890 _ => v.clone(),
14891 }
14892}
14893
14894fn predicate_truthy(v: &spg_storage::Value) -> bool {
14902 use spg_storage::Value as V;
14903 match v {
14904 V::Bool(b) => *b,
14905 V::Int(n) => *n != 0,
14906 V::BigInt(n) => *n != 0,
14907 V::SmallInt(n) => *n != 0,
14908 _ => false,
14909 }
14910}
14911
14912fn check_existing_unique_violation(
14917 idx: &spg_storage::Index,
14918 schema: &spg_storage::TableSchema,
14919 rows: &[spg_storage::Row],
14920) -> Result<(), EngineError> {
14921 let predicate_expr = match idx.partial_predicate.as_deref() {
14922 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14923 EngineError::Unsupported(alloc::format!(
14924 "stored partial predicate {s:?} failed to re-parse: {e:?}"
14925 ))
14926 })?),
14927 None => None,
14928 };
14929 let ctx = eval::EvalContext::new(&schema.columns, None);
14930 let key_positions = unique_key_positions(idx);
14931 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
14932 for row in rows {
14933 if let Some(expr) = &predicate_expr {
14934 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
14935 EngineError::Unsupported(alloc::format!(
14936 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
14937 ))
14938 })?;
14939 if !predicate_truthy(&v) {
14940 continue;
14941 }
14942 }
14943 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
14944 .iter()
14945 .map(|&p| {
14946 let v = row
14947 .values
14948 .get(p)
14949 .cloned()
14950 .unwrap_or(spg_storage::Value::Null);
14951 collated_key_cell(&v, p, schema)
14952 })
14953 .collect();
14954 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14955 continue;
14956 }
14957 if seen.iter().any(|other| *other == key) {
14958 return Err(EngineError::Unsupported(alloc::format!(
14959 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
14960 idx.name
14961 )));
14962 }
14963 seen.push(key);
14964 }
14965 Ok(())
14966}
14967
14968fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
14972 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
14973 out.push(idx.column_position);
14974 out.extend_from_slice(&idx.extra_column_positions);
14975 out
14976}
14977
14978fn enforce_unique_index_inserts(
14986 catalog: &Catalog,
14987 table_name: &str,
14988 rows: &[alloc::vec::Vec<spg_storage::Value>],
14989) -> Result<(), EngineError> {
14990 let table = catalog.get(table_name).ok_or_else(|| {
14991 EngineError::Storage(StorageError::TableNotFound {
14992 name: table_name.into(),
14993 })
14994 })?;
14995 let schema = table.schema();
14996 let ctx = eval::EvalContext::new(&schema.columns, None);
14997 for idx in table.indices() {
14998 if !idx.is_unique {
14999 continue;
15000 }
15001 let predicate_expr = match idx.partial_predicate.as_deref() {
15003 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
15004 EngineError::Unsupported(alloc::format!(
15005 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
15006 idx.name
15007 ))
15008 })?),
15009 None => None,
15010 };
15011 let key_positions = unique_key_positions(idx);
15012 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
15013 key_positions
15017 .iter()
15018 .map(|&p| {
15019 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
15020 collated_key_cell(&v, p, schema)
15021 })
15022 .collect()
15023 };
15024 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
15028 let Some(expr) = &predicate_expr else {
15029 return Ok(true);
15030 };
15031 let tmp_row = spg_storage::Row {
15032 values: values.to_vec(),
15033 };
15034 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15035 EngineError::Unsupported(alloc::format!(
15036 "UNIQUE INDEX {:?} predicate eval: {e:?}",
15037 idx.name
15038 ))
15039 })?;
15040 Ok(predicate_truthy(&v))
15041 };
15042 for (batch_idx, row_values) in rows.iter().enumerate() {
15043 if !participates(row_values)? {
15044 continue;
15045 }
15046 let key = key_of(row_values);
15047 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
15048 continue;
15049 }
15050 for prow in table.rows() {
15052 if !participates(&prow.values)? {
15053 continue;
15054 }
15055 if key_of(&prow.values) == key {
15056 return Err(EngineError::Unsupported(alloc::format!(
15057 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15058 row #{batch_idx} duplicates an existing key",
15059 idx.name
15060 )));
15061 }
15062 }
15063 for earlier in &rows[..batch_idx] {
15065 if !participates(earlier)? {
15066 continue;
15067 }
15068 if key_of(earlier) == key {
15069 return Err(EngineError::Unsupported(alloc::format!(
15070 "UNIQUE INDEX {:?} violation on {table_name:?}: \
15071 row #{batch_idx} duplicates an earlier row in the same batch",
15072 idx.name
15073 )));
15074 }
15075 }
15076 }
15077 }
15078 Ok(())
15079}
15080
15081fn any_column_changed(
15089 filter_cols: &[String],
15090 schema_cols: &[ColumnSchema],
15091 old_row: &Row,
15092 new_row: &Row,
15093) -> bool {
15094 for col_name in filter_cols {
15095 let Some(pos) = schema_cols
15096 .iter()
15097 .position(|c| c.name.eq_ignore_ascii_case(col_name))
15098 else {
15099 continue;
15100 };
15101 let old_v = old_row.values.get(pos);
15102 let new_v = new_row.values.get(pos);
15103 if old_v != new_v {
15104 return true;
15105 }
15106 }
15107 false
15108}
15109
15110fn enforce_check_constraints(
15115 catalog: &Catalog,
15116 table_name: &str,
15117 rows: &[alloc::vec::Vec<spg_storage::Value>],
15118) -> Result<(), EngineError> {
15119 let table = catalog.get(table_name).ok_or_else(|| {
15120 EngineError::Storage(StorageError::TableNotFound {
15121 name: table_name.into(),
15122 })
15123 })?;
15124 let schema = table.schema();
15125 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
15129 alloc::vec::Vec::new();
15130 for (idx, col) in schema.columns.iter().enumerate() {
15131 let Some(dname) = &col.user_domain_type else {
15132 continue;
15133 };
15134 let Some(dom) = catalog.domain_types().get(dname) else {
15135 continue;
15136 };
15137 let mut parsed_for_col: alloc::vec::Vec<Expr> =
15138 alloc::vec::Vec::with_capacity(dom.checks.len());
15139 for src in &dom.checks {
15140 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15141 EngineError::Unsupported(alloc::format!(
15142 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
15143 col.name
15144 ))
15145 })?;
15146 parsed_for_col.push(expr);
15147 }
15148 if !parsed_for_col.is_empty() {
15149 domain_checks_per_col.push((idx, parsed_for_col));
15150 }
15151 }
15152 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
15153 return Ok(());
15154 }
15155 let ctx = eval::EvalContext::new(&schema.columns, None);
15156 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
15157 for (i, src) in schema.checks.iter().enumerate() {
15158 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
15159 EngineError::Unsupported(alloc::format!(
15160 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
15161 ))
15162 })?;
15163 parsed.push((i, expr));
15164 }
15165 for (batch_idx, row_values) in rows.iter().enumerate() {
15166 let tmp_row = spg_storage::Row {
15167 values: row_values.clone(),
15168 };
15169 for (i, expr) in &parsed {
15170 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
15171 EngineError::Unsupported(alloc::format!(
15172 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
15173 ))
15174 })?;
15175 if matches!(v, spg_storage::Value::Bool(false)) {
15177 return Err(EngineError::Unsupported(alloc::format!(
15178 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
15179 schema.checks[*i]
15180 )));
15181 }
15182 }
15183 for (col_idx, checks) in &domain_checks_per_col {
15189 let cell = row_values
15190 .get(*col_idx)
15191 .cloned()
15192 .unwrap_or(spg_storage::Value::Null);
15193 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
15194 "value",
15195 schema.columns[*col_idx].ty,
15196 schema.columns[*col_idx].nullable,
15197 )];
15198 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
15199 let synth_row = spg_storage::Row {
15200 values: alloc::vec![cell],
15201 };
15202 for (ci, expr) in checks.iter().enumerate() {
15203 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
15204 EngineError::Unsupported(alloc::format!(
15205 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
15206 schema.columns[*col_idx].name
15207 ))
15208 })?;
15209 if matches!(v, spg_storage::Value::Bool(false)) {
15210 return Err(EngineError::Unsupported(alloc::format!(
15211 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
15212 schema.columns[*col_idx].name
15213 )));
15214 }
15215 }
15216 }
15217 }
15218 Ok(())
15219}
15220
15221fn enforce_fk_inserts(
15222 catalog: &Catalog,
15223 child_table: &str,
15224 fks: &[spg_storage::ForeignKeyConstraint],
15225 rows: &[Vec<Value>],
15226) -> Result<(), EngineError> {
15227 for fk in fks {
15228 let parent_is_self = fk.parent_table == child_table;
15229 let parent = if parent_is_self {
15230 catalog.get(child_table).ok_or_else(|| {
15233 EngineError::Storage(StorageError::TableNotFound {
15234 name: child_table.into(),
15235 })
15236 })?
15237 } else {
15238 catalog.get(&fk.parent_table).ok_or_else(|| {
15239 EngineError::Storage(StorageError::TableNotFound {
15240 name: fk.parent_table.clone(),
15241 })
15242 })?
15243 };
15244 for (batch_idx, row_values) in rows.iter().enumerate() {
15245 if fk.local_columns.len() == 1 {
15249 let v = &row_values[fk.local_columns[0]];
15250 if matches!(v, Value::Null) {
15251 continue;
15252 }
15253 let parent_col = fk.parent_columns[0];
15254 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15255 EngineError::Unsupported(alloc::format!(
15256 "FOREIGN KEY column value of type {:?} is not index-eligible",
15257 v.data_type()
15258 ))
15259 })?;
15260 let present_committed = parent.indices().iter().any(|idx| {
15261 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15262 && idx.column_position == parent_col
15263 && idx.partial_predicate.is_none()
15264 && !idx.lookup_eq(&key).is_empty()
15265 });
15266 let present_in_batch = parent_is_self
15270 && rows[..batch_idx]
15271 .iter()
15272 .any(|earlier| earlier.get(parent_col) == Some(v));
15273 if !(present_committed || present_in_batch) {
15274 return Err(EngineError::Unsupported(alloc::format!(
15275 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15276 fk.parent_table,
15277 parent
15278 .schema()
15279 .columns
15280 .get(parent_col)
15281 .map_or("?", |c| c.name.as_str()),
15282 v,
15283 )));
15284 }
15285 } else {
15286 if fk
15290 .local_columns
15291 .iter()
15292 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15293 {
15294 continue;
15295 }
15296 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15297 let parent_match_committed = parent.rows().iter().any(|prow| {
15298 fk.parent_columns
15299 .iter()
15300 .enumerate()
15301 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15302 });
15303 let parent_match_in_batch = parent_is_self
15304 && rows[..batch_idx].iter().any(|earlier| {
15305 fk.parent_columns
15306 .iter()
15307 .enumerate()
15308 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15309 });
15310 if !(parent_match_committed || parent_match_in_batch) {
15311 return Err(EngineError::Unsupported(alloc::format!(
15312 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15313 fk.parent_table,
15314 )));
15315 }
15316 }
15317 }
15318 }
15319 Ok(())
15320}
15321
15322#[derive(Debug, Clone)]
15326struct FkChildStep {
15327 child_table: String,
15328 action: FkChildAction,
15329}
15330
15331#[derive(Debug, Clone)]
15332enum FkChildAction {
15333 Delete { positions: Vec<usize> },
15335 SetNull {
15339 positions: Vec<usize>,
15340 columns: Vec<usize>,
15341 },
15342 SetDefault {
15346 positions: Vec<usize>,
15347 columns: Vec<usize>,
15348 defaults: Vec<Value>,
15349 },
15350}
15351
15352fn plan_fk_parent_deletions(
15368 catalog: &Catalog,
15369 parent_table_name: &str,
15370 to_delete_positions: &[usize],
15371 to_delete_rows: &[Vec<Value>],
15372) -> Result<Vec<FkChildStep>, EngineError> {
15373 use alloc::collections::{BTreeMap, BTreeSet};
15374 if to_delete_rows.is_empty() {
15375 return Ok(Vec::new());
15376 }
15377 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15378 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15380 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15381 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15382 for &p in to_delete_positions {
15383 visited.insert((parent_table_name.to_string(), p));
15384 }
15385 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15386 .iter()
15387 .map(|r| (parent_table_name.to_string(), r.clone()))
15388 .collect();
15389 while let Some((cur_parent, parent_row)) = work.pop() {
15390 for child_name in catalog.table_names() {
15391 let child = catalog
15392 .get(&child_name)
15393 .expect("table_names → catalog.get round-trip is total");
15394 for fk in &child.schema().foreign_keys {
15395 if fk.parent_table != cur_parent {
15396 continue;
15397 }
15398 let parent_key: Vec<&Value> = fk
15399 .parent_columns
15400 .iter()
15401 .map(|&pi| &parent_row[pi])
15402 .collect();
15403 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15404 continue;
15405 }
15406 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15407 if child_name == cur_parent
15408 && visited.contains(&(child_name.clone(), child_row_idx))
15409 {
15410 continue;
15411 }
15412 let matches_key = fk
15413 .local_columns
15414 .iter()
15415 .enumerate()
15416 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15417 if !matches_key {
15418 continue;
15419 }
15420 match fk.on_delete {
15421 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15422 return Err(EngineError::Unsupported(alloc::format!(
15423 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15424 restricted by FK from {child_name:?}.{:?}",
15425 fk.local_columns,
15426 )));
15427 }
15428 spg_storage::FkAction::Cascade => {
15429 if visited.insert((child_name.clone(), child_row_idx)) {
15430 delete_plan
15431 .entry(child_name.clone())
15432 .or_default()
15433 .insert(child_row_idx);
15434 work.push((child_name.clone(), child_row.values.clone()));
15435 }
15436 }
15437 spg_storage::FkAction::SetNull => {
15438 for &li in &fk.local_columns {
15440 let col = child.schema().columns.get(li).ok_or_else(|| {
15441 EngineError::Unsupported(alloc::format!(
15442 "FK local column {li} missing in {child_name:?}"
15443 ))
15444 })?;
15445 if !col.nullable {
15446 return Err(EngineError::Unsupported(alloc::format!(
15447 "FOREIGN KEY ON DELETE SET NULL: column \
15448 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15449 col.name,
15450 )));
15451 }
15452 }
15453 let entry = setnull_plan.entry(child_name.clone()).or_default();
15454 for &li in &fk.local_columns {
15455 entry.insert((child_row_idx, li));
15456 }
15457 }
15458 spg_storage::FkAction::SetDefault => {
15459 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15461 for &li in &fk.local_columns {
15462 let col = child.schema().columns.get(li).ok_or_else(|| {
15463 EngineError::Unsupported(alloc::format!(
15464 "FK local column {li} missing in {child_name:?}"
15465 ))
15466 })?;
15467 let default = col.default.clone().ok_or_else(|| {
15468 EngineError::Unsupported(alloc::format!(
15469 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15470 {child_name:?}.{:?} has no DEFAULT declared",
15471 col.name,
15472 ))
15473 })?;
15474 entry.insert((child_row_idx, li), default);
15475 }
15476 }
15477 }
15478 }
15479 }
15480 }
15481 }
15482 let mut steps: Vec<FkChildStep> = Vec::new();
15490 for (child_table, entries) in setnull_plan {
15491 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15492 steps.push(FkChildStep {
15493 child_table,
15494 action: FkChildAction::SetNull { positions, columns },
15495 });
15496 }
15497 for (child_table, entries) in setdefault_plan {
15498 let mut positions = Vec::with_capacity(entries.len());
15499 let mut columns = Vec::with_capacity(entries.len());
15500 let mut defaults = Vec::with_capacity(entries.len());
15501 for ((p, c), v) in entries {
15502 positions.push(p);
15503 columns.push(c);
15504 defaults.push(v);
15505 }
15506 steps.push(FkChildStep {
15507 child_table,
15508 action: FkChildAction::SetDefault {
15509 positions,
15510 columns,
15511 defaults,
15512 },
15513 });
15514 }
15515 for (child_table, positions) in delete_plan {
15516 steps.push(FkChildStep {
15517 child_table,
15518 action: FkChildAction::Delete {
15519 positions: positions.into_iter().collect(),
15520 },
15521 });
15522 }
15523 Ok(steps)
15524}
15525
15526fn plan_fk_parent_updates(
15543 catalog: &Catalog,
15544 parent_table_name: &str,
15545 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15546) -> Result<Vec<FkChildStep>, EngineError> {
15547 use alloc::collections::BTreeMap;
15548 if plan_with_old.is_empty() {
15549 return Ok(Vec::new());
15550 }
15551 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15556 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15557 BTreeMap::new();
15558 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15559 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15561
15562 for child_name in catalog.table_names() {
15563 let child = catalog
15564 .get(&child_name)
15565 .expect("table_names → catalog.get total");
15566 for fk in &child.schema().foreign_keys {
15567 if fk.parent_table != parent_table_name {
15568 continue;
15569 }
15570 for (_pos, old_row, new_row) in plan_with_old {
15571 let key_changed = fk
15573 .parent_columns
15574 .iter()
15575 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15576 if !key_changed {
15577 continue;
15578 }
15579 let old_key: Vec<&Value> =
15581 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15582 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15583 continue;
15585 }
15586 let new_key: Vec<&Value> =
15587 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15588 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15589 if child_name == parent_table_name
15592 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15593 {
15594 continue;
15595 }
15596 let matches_key = fk
15597 .local_columns
15598 .iter()
15599 .enumerate()
15600 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15601 if !matches_key {
15602 continue;
15603 }
15604 match fk.on_update {
15605 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15606 return Err(EngineError::Unsupported(alloc::format!(
15607 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15608 restricted by FK from {child_name:?}.{:?}",
15609 fk.local_columns,
15610 )));
15611 }
15612 spg_storage::FkAction::Cascade => {
15613 let entry = cascade_plan.entry(child_name.clone()).or_default();
15615 for (i, &li) in fk.local_columns.iter().enumerate() {
15616 entry.insert((child_row_idx, li), new_key[i].clone());
15617 }
15618 }
15619 spg_storage::FkAction::SetNull => {
15620 for &li in &fk.local_columns {
15621 let col = child.schema().columns.get(li).ok_or_else(|| {
15622 EngineError::Unsupported(alloc::format!(
15623 "FK local column {li} missing in {child_name:?}"
15624 ))
15625 })?;
15626 if !col.nullable {
15627 return Err(EngineError::Unsupported(alloc::format!(
15628 "FOREIGN KEY ON UPDATE SET NULL: column \
15629 {child_name:?}.{:?} is NOT NULL",
15630 col.name,
15631 )));
15632 }
15633 }
15634 let entry = setnull_plan.entry(child_name.clone()).or_default();
15635 for &li in &fk.local_columns {
15636 entry.insert((child_row_idx, li));
15637 }
15638 }
15639 spg_storage::FkAction::SetDefault => {
15640 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15641 for &li in &fk.local_columns {
15642 let col = child.schema().columns.get(li).ok_or_else(|| {
15643 EngineError::Unsupported(alloc::format!(
15644 "FK local column {li} missing in {child_name:?}"
15645 ))
15646 })?;
15647 let default = col.default.clone().ok_or_else(|| {
15648 EngineError::Unsupported(alloc::format!(
15649 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15650 {child_name:?}.{:?} has no DEFAULT",
15651 col.name,
15652 ))
15653 })?;
15654 entry.insert((child_row_idx, li), default);
15655 }
15656 }
15657 }
15658 }
15659 }
15660 }
15661 }
15662 let mut steps: Vec<FkChildStep> = Vec::new();
15665 for (child_table, entries) in cascade_plan {
15666 let mut positions = Vec::with_capacity(entries.len());
15667 let mut columns = Vec::with_capacity(entries.len());
15668 let mut defaults = Vec::with_capacity(entries.len());
15669 for ((p, c), v) in entries {
15670 positions.push(p);
15671 columns.push(c);
15672 defaults.push(v);
15673 }
15674 steps.push(FkChildStep {
15679 child_table,
15680 action: FkChildAction::SetDefault {
15681 positions,
15682 columns,
15683 defaults,
15684 },
15685 });
15686 }
15687 for (child_table, entries) in setnull_plan {
15688 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15689 steps.push(FkChildStep {
15690 child_table,
15691 action: FkChildAction::SetNull { positions, columns },
15692 });
15693 }
15694 for (child_table, entries) in setdefault_plan {
15695 let mut positions = Vec::with_capacity(entries.len());
15696 let mut columns = Vec::with_capacity(entries.len());
15697 let mut defaults = Vec::with_capacity(entries.len());
15698 for ((p, c), v) in entries {
15699 positions.push(p);
15700 columns.push(c);
15701 defaults.push(v);
15702 }
15703 steps.push(FkChildStep {
15704 child_table,
15705 action: FkChildAction::SetDefault {
15706 positions,
15707 columns,
15708 defaults,
15709 },
15710 });
15711 }
15712 let _ = delete_plan; Ok(steps)
15714}
15715
15716fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15720 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15721 EngineError::Storage(StorageError::TableNotFound {
15722 name: step.child_table.clone(),
15723 })
15724 })?;
15725 match &step.action {
15726 FkChildAction::Delete { positions } => {
15727 let _ = child.delete_rows(positions);
15728 }
15729 FkChildAction::SetNull { positions, columns } => {
15730 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15731 }
15732 FkChildAction::SetDefault {
15733 positions,
15734 columns,
15735 defaults,
15736 } => {
15737 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15738 }
15739 }
15740 Ok(())
15741}
15742
15743fn apply_per_cell_writes(
15749 child: &mut spg_storage::Table,
15750 positions: &[usize],
15751 columns: &[usize],
15752 mut value_for: impl FnMut(usize) -> Value,
15753) -> Result<(), EngineError> {
15754 use alloc::collections::BTreeMap;
15755 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15756 for i in 0..positions.len() {
15757 by_row
15758 .entry(positions[i])
15759 .or_default()
15760 .push((columns[i], value_for(i)));
15761 }
15762 for (pos, mutations) in by_row {
15763 let mut new_values = child.rows()[pos].values.clone();
15764 for (col, v) in mutations {
15765 if let Some(slot) = new_values.get_mut(col) {
15766 *slot = v;
15767 }
15768 }
15769 child
15770 .update_row(pos, new_values)
15771 .map_err(EngineError::Storage)?;
15772 }
15773 Ok(())
15774}
15775
15776fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15777 match a {
15778 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15779 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15780 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15781 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15782 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15783 }
15784}
15785
15786fn resolve_column_default_free(
15792 col: &ColumnSchema,
15793 clock_fn: Option<ClockFn>,
15794) -> Result<Value, EngineError> {
15795 if let Some(rt) = &col.runtime_default {
15796 return eval_runtime_default_free(rt, col.ty, clock_fn);
15797 }
15798 Ok(col.default.clone().unwrap_or(Value::Null))
15799}
15800
15801fn eval_runtime_default_free(
15802 rt: &str,
15803 ty: DataType,
15804 clock_fn: Option<ClockFn>,
15805) -> Result<Value, EngineError> {
15806 let s = rt.trim().to_ascii_lowercase();
15807 let with_no_parens = s.trim_end_matches("()");
15813 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15814 if with_no_parens.ends_with(')') {
15815 &with_no_parens[..open_idx]
15816 } else {
15817 with_no_parens
15818 }
15819 } else {
15820 with_no_parens
15821 };
15822 let now_us = match clock_fn {
15823 Some(f) => f(),
15824 None => 0,
15825 };
15826 let v = match canonical {
15827 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15828 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15829 "current_time" | "localtime" => Value::Timestamp(now_us),
15830 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15836 other => {
15837 return Err(EngineError::Unsupported(alloc::format!(
15838 "runtime DEFAULT expression {other:?} not supported \
15839 (v7.17.0 whitelist: now() / current_timestamp / \
15840 current_date / current_time / localtimestamp / \
15841 localtime / gen_random_uuid() / \
15842 uuid_generate_v4())"
15843 )));
15844 }
15845 };
15846 coerce_value(v, ty, "DEFAULT", 0)
15847}
15848
15849fn is_runtime_default_expr(expr: &Expr) -> bool {
15855 match expr {
15856 Expr::FunctionCall { .. } => true,
15857 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15858 _ => false,
15859 }
15860}
15861
15862fn canonicalize_set_value(
15875 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15876 col_idx: usize,
15877 col_name: &str,
15878 value: Value,
15879) -> Result<Value, EngineError> {
15880 let Some(variants) = lookup.get(&col_idx) else {
15881 return Ok(value);
15882 };
15883 match value {
15884 Value::Null => Ok(Value::Null),
15885 Value::Text(s) => {
15886 if s.is_empty() {
15887 return Ok(Value::Text(alloc::string::String::new()));
15888 }
15889 let mut present = alloc::vec![false; variants.len()];
15892 for raw in s.split(',') {
15893 let tok = raw.trim();
15894 if tok.is_empty() {
15895 continue;
15896 }
15897 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15898 EngineError::Unsupported(alloc::format!(
15899 "column {col_name:?}: invalid SET token {tok:?}; \
15900 allowed: {variants:?}"
15901 ))
15902 })?;
15903 present[idx] = true;
15904 }
15905 let mut out = alloc::string::String::new();
15907 let mut first = true;
15908 for (i, keep) in present.iter().enumerate() {
15909 if !keep {
15910 continue;
15911 }
15912 if !first {
15913 out.push(',');
15914 }
15915 first = false;
15916 out.push_str(&variants[i]);
15917 }
15918 Ok(Value::Text(out))
15919 }
15920 other => Err(EngineError::Unsupported(alloc::format!(
15921 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
15922 other.data_type()
15923 ))),
15924 }
15925}
15926
15927fn enforce_enum_label(
15928 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15929 col_idx: usize,
15930 col_name: &str,
15931 value: &Value,
15932) -> Result<(), EngineError> {
15933 if let Some(labels) = lookup.get(&col_idx) {
15934 match value {
15935 Value::Null => Ok(()),
15936 Value::Text(s) => {
15937 if labels.iter().any(|l| l == s) {
15938 Ok(())
15939 } else {
15940 Err(EngineError::Unsupported(alloc::format!(
15941 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
15942 )))
15943 }
15944 }
15945 other => Err(EngineError::Unsupported(alloc::format!(
15946 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
15947 other.data_type()
15948 ))),
15949 }
15950 } else {
15951 Ok(())
15952 }
15953}
15954
15955fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
15956 let ty = column_type_to_data_type(c.ty);
15957 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
15958 if let Some(name) = c.user_type_ref {
15965 schema.user_enum_type = Some(name);
15966 }
15967 if let Some(expr) = c.on_update_runtime {
15970 schema.on_update_runtime = Some(alloc::format!("{expr}"));
15971 }
15972 schema.collation = match c.collation {
15976 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
15977 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
15978 };
15979 schema.is_unsigned = c.is_unsigned;
15982 schema.inline_enum_variants = c.inline_enum_variants;
15986 schema.inline_set_variants = c.inline_set_variants;
15990 if let Some(default_expr) = c.default {
15991 if is_runtime_default_expr(&default_expr) {
15997 let display = alloc::format!("{default_expr}");
15998 schema = schema.with_runtime_default(display);
15999 } else {
16000 let raw = literal_expr_to_value(default_expr)?;
16001 let coerced = coerce_value(raw, ty, &c.name, 0)?;
16002 schema = schema.with_default(coerced);
16003 }
16004 }
16005 if c.auto_increment {
16006 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
16008 return Err(EngineError::Unsupported(alloc::format!(
16009 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
16010 )));
16011 }
16012 schema = schema.with_auto_increment();
16013 }
16014 Ok(schema)
16015}
16016
16017fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
16022 let s = s.trim();
16023 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
16024 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
16026 if cleaned.len() % 2 != 0 {
16027 return Err("odd-length hex literal");
16028 }
16029 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
16030 let cleaned_bytes = cleaned.as_bytes();
16031 for i in (0..cleaned_bytes.len()).step_by(2) {
16032 let hi = hex_nibble(cleaned_bytes[i])?;
16033 let lo = hex_nibble(cleaned_bytes[i + 1])?;
16034 out.push((hi << 4) | lo);
16035 }
16036 return Ok(out);
16037 }
16038 let bytes = s.as_bytes();
16041 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
16042 let mut i = 0;
16043 while i < bytes.len() {
16044 let b = bytes[i];
16045 if b == b'\\' && i + 1 < bytes.len() {
16046 let n = bytes[i + 1];
16047 if n == b'\\' {
16048 out.push(b'\\');
16049 i += 2;
16050 continue;
16051 }
16052 if n.is_ascii_digit()
16053 && i + 3 < bytes.len()
16054 && bytes[i + 2].is_ascii_digit()
16055 && bytes[i + 3].is_ascii_digit()
16056 {
16057 let oct = |x: u8| (x - b'0') as u32;
16058 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
16059 if v <= 0xFF {
16060 out.push(v as u8);
16061 i += 4;
16062 continue;
16063 }
16064 }
16065 }
16066 out.push(b);
16067 i += 1;
16068 }
16069 Ok(out)
16070}
16071
16072fn hex_nibble(b: u8) -> Result<u8, &'static str> {
16073 match b {
16074 b'0'..=b'9' => Ok(b - b'0'),
16075 b'a'..=b'f' => Ok(b - b'a' + 10),
16076 b'A'..=b'F' => Ok(b - b'A' + 10),
16077 _ => Err("invalid hex digit"),
16078 }
16079}
16080
16081fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
16095 let mut has_text = false;
16096 let mut has_bigint = false;
16097 let mut has_int = false;
16098 for v in &items {
16099 match v {
16100 Value::Null => {}
16101 Value::Text(_) | Value::Json(_) => has_text = true,
16102 Value::BigInt(_) => has_bigint = true,
16103 Value::Int(_) | Value::SmallInt(_) => has_int = true,
16104 _ => has_text = true,
16105 }
16106 }
16107 if has_text || (!has_bigint && !has_int) {
16108 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
16109 .into_iter()
16110 .map(|v| match v {
16111 Value::Null => None,
16112 Value::Text(s) | Value::Json(s) => Some(s),
16113 other => Some(alloc::format!("{other:?}")),
16114 })
16115 .collect();
16116 return Value::TextArray(out);
16117 }
16118 if has_bigint {
16119 let out: alloc::vec::Vec<Option<i64>> = items
16120 .into_iter()
16121 .map(|v| match v {
16122 Value::Null => None,
16123 Value::Int(n) => Some(i64::from(n)),
16124 Value::SmallInt(n) => Some(i64::from(n)),
16125 Value::BigInt(n) => Some(n),
16126 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
16127 })
16128 .collect();
16129 return Value::BigIntArray(out);
16130 }
16131 let out: alloc::vec::Vec<Option<i32>> = items
16132 .into_iter()
16133 .map(|v| match v {
16134 Value::Null => None,
16135 Value::Int(n) => Some(n),
16136 Value::SmallInt(n) => Some(i32::from(n)),
16137 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
16138 })
16139 .collect();
16140 Value::IntArray(out)
16141}
16142
16143fn decode_text_array_literal(
16144 s: &str,
16145) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
16146 let trimmed = s.trim();
16147 let inner = trimmed
16148 .strip_prefix('{')
16149 .and_then(|x| x.strip_suffix('}'))
16150 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
16151 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
16152 if inner.trim().is_empty() {
16153 return Ok(out);
16154 }
16155 let bytes = inner.as_bytes();
16156 let mut i = 0;
16157 while i <= bytes.len() {
16158 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16160 i += 1;
16161 }
16162 if i < bytes.len() && bytes[i] == b'"' {
16164 i += 1; let mut buf = alloc::string::String::new();
16166 while i < bytes.len() && bytes[i] != b'"' {
16167 if bytes[i] == b'\\' && i + 1 < bytes.len() {
16168 buf.push(bytes[i + 1] as char);
16169 i += 2;
16170 } else {
16171 buf.push(bytes[i] as char);
16172 i += 1;
16173 }
16174 }
16175 if i >= bytes.len() {
16176 return Err("unterminated quoted element");
16177 }
16178 i += 1; out.push(Some(buf));
16180 } else {
16181 let start = i;
16183 while i < bytes.len() && bytes[i] != b',' {
16184 i += 1;
16185 }
16186 let raw = inner[start..i].trim();
16187 if raw.eq_ignore_ascii_case("NULL") {
16188 out.push(None);
16189 } else {
16190 out.push(Some(alloc::string::ToString::to_string(raw)));
16191 }
16192 }
16193 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
16195 i += 1;
16196 }
16197 if i >= bytes.len() {
16198 break;
16199 }
16200 if bytes[i] != b',' {
16201 return Err("expected ',' between TEXT[] elements");
16202 }
16203 i += 1;
16204 }
16205 Ok(out)
16206}
16207
16208fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
16213 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
16214 out.push('{');
16215 for (i, item) in items.iter().enumerate() {
16216 if i > 0 {
16217 out.push(',');
16218 }
16219 match item {
16220 None => out.push_str("NULL"),
16221 Some(s) => {
16222 let needs_quote = s.is_empty()
16223 || s.eq_ignore_ascii_case("NULL")
16224 || s.chars()
16225 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16226 if needs_quote {
16227 out.push('"');
16228 for c in s.chars() {
16229 if c == '"' || c == '\\' {
16230 out.push('\\');
16231 }
16232 out.push(c);
16233 }
16234 out.push('"');
16235 } else {
16236 out.push_str(s);
16237 }
16238 }
16239 }
16240 }
16241 out.push('}');
16242 out
16243}
16244
16245fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16249 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16250 out.push_str("\\x");
16251 for byte in b {
16252 let hi = byte >> 4;
16253 let lo = byte & 0x0F;
16254 out.push(hex_digit(hi));
16255 out.push(hex_digit(lo));
16256 }
16257 out
16258}
16259
16260const fn hex_digit(n: u8) -> char {
16261 match n {
16262 0..=9 => (b'0' + n) as char,
16263 10..=15 => (b'a' + n - 10) as char,
16264 _ => '?',
16265 }
16266}
16267
16268fn parse_hstore_str(
16280 s: &str,
16281) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16282 let bytes = s.as_bytes();
16283 let mut i = 0;
16284 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16285 let skip_ws = |bytes: &[u8], i: &mut usize| {
16286 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16287 *i += 1;
16288 }
16289 };
16290 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16291 if *i >= bytes.len() {
16292 return None;
16293 }
16294 if bytes[*i] == b'"' {
16295 *i += 1;
16296 let mut out = alloc::string::String::new();
16297 while *i < bytes.len() {
16298 match bytes[*i] {
16299 b'"' => {
16300 *i += 1;
16301 return Some(out);
16302 }
16303 b'\\' if *i + 1 < bytes.len() => {
16304 out.push(bytes[*i + 1] as char);
16305 *i += 2;
16306 }
16307 c => {
16308 out.push(c as char);
16309 *i += 1;
16310 }
16311 }
16312 }
16313 None
16314 } else {
16315 let start = *i;
16316 while *i < bytes.len()
16317 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16318 {
16319 *i += 1;
16320 }
16321 if *i == start {
16322 return None;
16323 }
16324 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16325 }
16326 };
16327 skip_ws(bytes, &mut i);
16328 while i < bytes.len() {
16329 let key = parse_token(bytes, &mut i)?;
16330 skip_ws(bytes, &mut i);
16331 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16332 return None;
16333 }
16334 i += 2;
16335 skip_ws(bytes, &mut i);
16336 let val_token = if i + 4 <= bytes.len()
16338 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16339 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16340 {
16341 i += 4;
16342 None
16343 } else {
16344 Some(parse_token(bytes, &mut i)?)
16345 };
16346 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16348 out[pos] = (key, val_token);
16349 } else {
16350 out.push((key, val_token));
16351 }
16352 skip_ws(bytes, &mut i);
16353 if i >= bytes.len() {
16354 break;
16355 }
16356 if bytes[i] == b',' {
16357 i += 1;
16358 skip_ws(bytes, &mut i);
16359 continue;
16360 }
16361 return None;
16362 }
16363 Some(out)
16364}
16365
16366fn format_hstore_str(
16370 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16371) -> alloc::string::String {
16372 let mut out = alloc::string::String::new();
16373 for (i, (k, v)) in pairs.iter().enumerate() {
16374 if i > 0 {
16375 out.push_str(", ");
16376 }
16377 out.push('"');
16378 out.push_str(k);
16379 out.push_str("\"=>");
16380 match v {
16381 None => out.push_str("NULL"),
16382 Some(val) => {
16383 out.push('"');
16384 out.push_str(val);
16385 out.push('"');
16386 }
16387 }
16388 }
16389 out
16390}
16391
16392pub fn format_hstore_text(
16395 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16396) -> alloc::string::String {
16397 format_hstore_str(pairs)
16398}
16399
16400fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16405 let s = s.trim();
16406 let outer = s
16407 .strip_prefix('{')
16408 .and_then(|x| x.strip_suffix('}'))
16409 .ok_or("missing outer '{...}' braces")?;
16410 let trimmed = outer.trim();
16411 if trimmed.is_empty() {
16412 return Ok(Vec::new());
16413 }
16414 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16415 let mut i = 0;
16416 let bytes = trimmed.as_bytes();
16417 while i < bytes.len() {
16418 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16419 i += 1;
16420 }
16421 if i >= bytes.len() {
16422 break;
16423 }
16424 if bytes[i] != b'{' {
16425 return Err("expected '{' opening a row");
16426 }
16427 i += 1;
16428 let row_start = i;
16429 let mut depth = 1;
16430 while i < bytes.len() && depth > 0 {
16431 match bytes[i] {
16432 b'{' => depth += 1,
16433 b'}' => depth -= 1,
16434 _ => {}
16435 }
16436 if depth > 0 {
16437 i += 1;
16438 }
16439 }
16440 if depth != 0 {
16441 return Err("unbalanced '{...}' in row");
16442 }
16443 let row_text = &trimmed[row_start..i];
16444 i += 1;
16445 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16446 Vec::new()
16447 } else {
16448 row_text.split(',').map(|t| t.trim().to_string()).collect()
16449 };
16450 rows.push(cells);
16451 }
16452 if let Some(first) = rows.first() {
16453 let cols = first.len();
16454 for r in &rows {
16455 if r.len() != cols {
16456 return Err("ragged 2D array (rows have different column counts)");
16457 }
16458 }
16459 }
16460 Ok(rows)
16461}
16462
16463fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16464 let raw = split_2d_literal(s)?;
16465 raw.into_iter()
16466 .map(|row| {
16467 row.into_iter()
16468 .map(|cell| {
16469 if cell.eq_ignore_ascii_case("NULL") {
16470 Ok(None)
16471 } else {
16472 cell.parse::<i32>()
16473 .map(Some)
16474 .map_err(|_| "invalid int element")
16475 }
16476 })
16477 .collect()
16478 })
16479 .collect()
16480}
16481
16482fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16483 let raw = split_2d_literal(s)?;
16484 raw.into_iter()
16485 .map(|row| {
16486 row.into_iter()
16487 .map(|cell| {
16488 if cell.eq_ignore_ascii_case("NULL") {
16489 Ok(None)
16490 } else {
16491 cell.parse::<i64>()
16492 .map(Some)
16493 .map_err(|_| "invalid bigint element")
16494 }
16495 })
16496 .collect()
16497 })
16498 .collect()
16499}
16500
16501fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16502 let raw = split_2d_literal(s)?;
16503 Ok(raw
16504 .into_iter()
16505 .map(|row| {
16506 row.into_iter()
16507 .map(|cell| {
16508 if cell.eq_ignore_ascii_case("NULL") {
16509 None
16510 } else {
16511 Some(cell.trim_matches('"').to_string())
16512 }
16513 })
16514 .collect()
16515 })
16516 .collect())
16517}
16518
16519fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16520 let mut out = alloc::string::String::from("{");
16521 for (i, row) in rows.iter().enumerate() {
16522 if i > 0 {
16523 out.push(',');
16524 }
16525 out.push('{');
16526 for (j, cell) in row.iter().enumerate() {
16527 if j > 0 {
16528 out.push(',');
16529 }
16530 match cell {
16531 None => out.push_str("NULL"),
16532 Some(n) => out.push_str(&alloc::format!("{n}")),
16533 }
16534 }
16535 out.push('}');
16536 }
16537 out.push('}');
16538 out
16539}
16540
16541fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16542 let mut out = alloc::string::String::from("{");
16543 for (i, row) in rows.iter().enumerate() {
16544 if i > 0 {
16545 out.push(',');
16546 }
16547 out.push('{');
16548 for (j, cell) in row.iter().enumerate() {
16549 if j > 0 {
16550 out.push(',');
16551 }
16552 match cell {
16553 None => out.push_str("NULL"),
16554 Some(n) => out.push_str(&alloc::format!("{n}")),
16555 }
16556 }
16557 out.push('}');
16558 }
16559 out.push('}');
16560 out
16561}
16562
16563fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16564 let mut out = alloc::string::String::from("{");
16565 for (i, row) in rows.iter().enumerate() {
16566 if i > 0 {
16567 out.push(',');
16568 }
16569 out.push('{');
16570 for (j, cell) in row.iter().enumerate() {
16571 if j > 0 {
16572 out.push(',');
16573 }
16574 match cell {
16575 None => out.push_str("NULL"),
16576 Some(s) => out.push_str(s),
16577 }
16578 }
16579 out.push('}');
16580 }
16581 out.push('}');
16582 out
16583}
16584
16585pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16588 format_int_2d_text(rows)
16589}
16590pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16591 format_bigint_2d_text(rows)
16592}
16593pub fn format_text_2d_text_pub(
16594 rows: &[Vec<Option<alloc::string::String>>],
16595) -> alloc::string::String {
16596 format_text_2d_text(rows)
16597}
16598
16599fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16604 let s = s.trim();
16605 if s.eq_ignore_ascii_case("empty") {
16606 return Some(Value::Range {
16607 kind,
16608 lower: None,
16609 upper: None,
16610 lower_inc: false,
16611 upper_inc: false,
16612 empty: true,
16613 });
16614 }
16615 let bytes = s.as_bytes();
16616 if bytes.len() < 3 {
16617 return None;
16618 }
16619 let lower_inc = match bytes[0] {
16620 b'[' => true,
16621 b'(' => false,
16622 _ => return None,
16623 };
16624 let upper_inc = match bytes[bytes.len() - 1] {
16625 b']' => true,
16626 b')' => false,
16627 _ => return None,
16628 };
16629 let inner = &s[1..s.len() - 1];
16630 let (lo_text, up_text) = inner.split_once(',')?;
16631 let lower = if lo_text.is_empty() {
16632 None
16633 } else {
16634 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16635 };
16636 let upper = if up_text.is_empty() {
16637 None
16638 } else {
16639 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16640 };
16641 Some(Value::Range {
16642 kind,
16643 lower,
16644 upper,
16645 lower_inc,
16646 upper_inc,
16647 empty: false,
16648 })
16649}
16650
16651fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16654 let text = text.trim().trim_matches('"');
16655 use spg_storage::RangeKind as K;
16656 match kind {
16657 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16658 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16659 K::Num => {
16660 let dot = text.find('.');
16663 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16664 let digits: alloc::string::String = text
16665 .chars()
16666 .filter(|c| *c == '-' || c.is_ascii_digit())
16667 .collect();
16668 let scaled: i128 = digits.parse().ok()?;
16669 Some(Value::Numeric { scaled, scale })
16670 }
16671 K::Ts | K::TsTz => {
16672 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16677 }
16678 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16679 }
16680}
16681
16682pub fn format_range_text(v: &Value) -> alloc::string::String {
16686 format_range_str(v)
16687}
16688
16689fn format_range_str(v: &Value) -> alloc::string::String {
16690 let Value::Range {
16691 lower,
16692 upper,
16693 lower_inc,
16694 upper_inc,
16695 empty,
16696 ..
16697 } = v
16698 else {
16699 return alloc::string::String::new();
16700 };
16701 if *empty {
16702 return "empty".into();
16703 }
16704 let mut out = alloc::string::String::new();
16705 out.push(if *lower_inc { '[' } else { '(' });
16706 if let Some(l) = lower {
16707 out.push_str(&format_range_element(l));
16708 }
16709 out.push(',');
16710 if let Some(u) = upper {
16711 out.push_str(&format_range_element(u));
16712 }
16713 out.push(if *upper_inc { ']' } else { ')' });
16714 out
16715}
16716
16717fn format_range_element(v: &Value) -> alloc::string::String {
16718 match v {
16719 Value::Int(n) => alloc::format!("{n}"),
16720 Value::BigInt(n) => alloc::format!("{n}"),
16721 Value::Date(d) => crate::eval::format_date(*d),
16722 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16723 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16724 other => alloc::format!("{other:?}"),
16725 }
16726}
16727
16728fn parse_money_str(s: &str) -> Option<i64> {
16739 let s = s.trim();
16740 let (neg, rest) = match s.strip_prefix('-') {
16741 Some(r) => (true, r.trim_start()),
16742 None => (false, s),
16743 };
16744 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16745 let (int_part, frac_part) = match rest.split_once('.') {
16746 Some((i, f)) => (i, Some(f)),
16747 None => (rest, None),
16748 };
16749 if int_part.is_empty() {
16750 return None;
16751 }
16752 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16754 for b in int_part.bytes() {
16755 match b {
16756 b',' => {}
16757 b'0'..=b'9' => int_digits.push(b as char),
16758 _ => return None,
16759 }
16760 }
16761 if int_digits.is_empty() {
16762 return None;
16763 }
16764 let dollars: i64 = int_digits.parse().ok()?;
16765 let cents: i64 = match frac_part {
16766 None => 0,
16767 Some(f) => {
16768 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16769 return None;
16770 }
16771 let padded = if f.len() == 1 {
16772 alloc::format!("{f}0")
16773 } else {
16774 f.to_string()
16775 };
16776 padded.parse().ok()?
16777 }
16778 };
16779 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16780 Some(if neg { -total } else { total })
16781}
16782
16783fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16794 let s = s.trim();
16795 let bytes = s.as_bytes();
16799 let sign_pos = bytes
16800 .iter()
16801 .enumerate()
16802 .rev()
16803 .find(|&(_, &b)| b == b'+' || b == b'-')
16804 .map(|(i, _)| i)?;
16805 if sign_pos == 0 {
16806 return None; }
16808 let time_part = &s[..sign_pos];
16809 let offset_part = &s[sign_pos..];
16810 let us = parse_time_str(time_part)?;
16811 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16812 let offset_body = &offset_part[1..];
16813 let (hh_str, mm_str) = match offset_body.split_once(':') {
16814 Some((h, m)) => (h, m),
16815 None => (offset_body, "0"),
16816 };
16817 let hh: i32 = hh_str.parse().ok()?;
16818 let mm: i32 = mm_str.parse().ok()?;
16819 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16820 return None;
16821 }
16822 let total = sign * (hh * 3600 + mm * 60);
16823 if total.abs() > 50_400 {
16824 return None;
16825 }
16826 Some((us, total))
16827}
16828
16829fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16834 if n == 0 || (1901..=2155).contains(&n) {
16835 return Ok(Value::Year(n as u16));
16838 }
16839 Err(EngineError::Eval(EvalError::TypeMismatch {
16840 detail: alloc::format!(
16841 "year value out of range: {n} (column `{col_name}`; \
16842 MySQL accepts 0 or 1901..=2155)"
16843 ),
16844 }))
16845}
16846
16847fn parse_time_str(s: &str) -> Option<i64> {
16859 let s = s.trim();
16860 let (hms, frac) = match s.split_once('.') {
16861 Some((h, f)) => (h, Some(f)),
16862 None => (s, None),
16863 };
16864 let mut parts = hms.split(':');
16865 let hh: u32 = parts.next()?.parse().ok()?;
16866 let mm: u32 = parts.next()?.parse().ok()?;
16867 let ss: u32 = parts.next()?.parse().ok()?;
16868 if parts.next().is_some() {
16869 return None;
16870 }
16871 if hh > 23 || mm > 59 || ss > 59 {
16872 return None;
16873 }
16874 let frac_us: i64 = match frac {
16875 None => 0,
16876 Some(f) => {
16877 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16878 return None;
16879 }
16880 let mut padded = alloc::string::String::with_capacity(6);
16882 padded.push_str(f);
16883 while padded.len() < 6 {
16884 padded.push('0');
16885 }
16886 padded.parse().ok()?
16887 }
16888 };
16889 Some(
16890 i64::from(hh) * 3_600_000_000
16891 + i64::from(mm) * 60_000_000
16892 + i64::from(ss) * 1_000_000
16893 + frac_us,
16894 )
16895}
16896
16897const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16898 match t {
16899 ColumnTypeName::SmallInt => DataType::SmallInt,
16900 ColumnTypeName::Int => DataType::Int,
16901 ColumnTypeName::BigInt => DataType::BigInt,
16902 ColumnTypeName::Float => DataType::Float,
16903 ColumnTypeName::Text => DataType::Text,
16904 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
16905 ColumnTypeName::Char(n) => DataType::Char(n),
16906 ColumnTypeName::Bool => DataType::Bool,
16907 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
16908 dim,
16909 encoding: match encoding {
16910 SqlVecEncoding::F32 => VecEncoding::F32,
16911 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
16912 SqlVecEncoding::F16 => VecEncoding::F16,
16913 },
16914 },
16915 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
16916 ColumnTypeName::Date => DataType::Date,
16917 ColumnTypeName::Timestamp => DataType::Timestamp,
16918 ColumnTypeName::Timestamptz => DataType::Timestamptz,
16919 ColumnTypeName::Json => DataType::Json,
16920 ColumnTypeName::Jsonb => DataType::Jsonb,
16921 ColumnTypeName::Bytes => DataType::Bytes,
16922 ColumnTypeName::TextArray => DataType::TextArray,
16923 ColumnTypeName::IntArray => DataType::IntArray,
16924 ColumnTypeName::BigIntArray => DataType::BigIntArray,
16925 ColumnTypeName::TsVector => DataType::TsVector,
16926 ColumnTypeName::TsQuery => DataType::TsQuery,
16927 ColumnTypeName::Uuid => DataType::Uuid,
16928 ColumnTypeName::Time => DataType::Time,
16929 ColumnTypeName::Year => DataType::Year,
16930 ColumnTypeName::TimeTz => DataType::TimeTz,
16931 ColumnTypeName::Money => DataType::Money,
16932 ColumnTypeName::Range(k) => DataType::Range(match k {
16933 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
16934 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
16935 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
16936 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
16937 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
16938 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
16939 }),
16940 ColumnTypeName::Hstore => DataType::Hstore,
16941 ColumnTypeName::IntArray2D => DataType::IntArray2D,
16942 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
16943 ColumnTypeName::TextArray2D => DataType::TextArray2D,
16944 }
16945}
16946
16947fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
16951 match expr {
16952 Expr::Literal(l) => Ok(literal_to_value(l)),
16953 Expr::Cast { expr, target } => {
16954 let inner_value = literal_expr_to_value(*expr)?;
16955 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
16956 }
16957 Expr::Unary {
16958 op: UnOp::Neg,
16959 expr,
16960 } => match *expr {
16961 Expr::Literal(Literal::Integer(n)) => {
16962 let neg = n.checked_neg().ok_or_else(|| {
16965 EngineError::Unsupported("integer literal overflow on negation".into())
16966 })?;
16967 Ok(int_value_for(neg))
16968 }
16969 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
16970 other => Err(EngineError::Unsupported(alloc::format!(
16971 "unary minus over non-literal expression: {other:?}"
16972 ))),
16973 },
16974 Expr::Array(items) => {
16982 let mut materialised: alloc::vec::Vec<Value> =
16983 alloc::vec::Vec::with_capacity(items.len());
16984 for elem in items {
16985 materialised.push(literal_expr_to_value(elem)?);
16986 }
16987 Ok(array_literal_widen(materialised))
16988 }
16989 other => {
17002 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
17003 let ctx = EvalContext::new(&empty_schema, None);
17004 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
17005 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
17006 }
17007 }
17008}
17009
17010fn literal_to_value(l: Literal) -> Value {
17011 match l {
17012 Literal::Integer(n) => int_value_for(n),
17013 Literal::Float(x) => Value::Float(x),
17014 Literal::String(s) => Value::Text(s),
17015 Literal::Bool(b) => Value::Bool(b),
17016 Literal::Null => Value::Null,
17017 Literal::Vector(v) => Value::Vector(v),
17018 Literal::TextArray(items) => Value::TextArray(items),
17019 Literal::IntArray(items) => Value::IntArray(items),
17020 Literal::BigIntArray(items) => Value::BigIntArray(items),
17021 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
17022 }
17023}
17024
17025fn int_value_for(n: i64) -> Value {
17029 if let Ok(small) = i32::try_from(n) {
17030 Value::Int(small)
17031 } else {
17032 Value::BigInt(n)
17033 }
17034}
17035
17036#[allow(clippy::too_many_lines)]
17042fn check_unsigned_range(
17047 v: &Value,
17048 schema: &ColumnSchema,
17049 position: usize,
17050) -> Result<(), EngineError> {
17051 if !schema.is_unsigned {
17052 return Ok(());
17053 }
17054 let n = match v {
17055 Value::SmallInt(x) => i64::from(*x),
17056 Value::Int(x) => i64::from(*x),
17057 Value::BigInt(x) => *x,
17058 _ => return Ok(()), };
17060 if n < 0 {
17061 return Err(EngineError::Unsupported(alloc::format!(
17062 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
17063 schema.name
17064 )));
17065 }
17066 Ok(())
17067}
17068
17069fn coerce_value(
17070 v: Value,
17071 expected: DataType,
17072 col_name: &str,
17073 position: usize,
17074) -> Result<Value, EngineError> {
17075 if v.is_null() {
17076 return Ok(Value::Null);
17077 }
17078 let actual = v.data_type().expect("non-null");
17079 if actual == expected {
17080 return Ok(v);
17081 }
17082 let coerced = match (v, expected) {
17083 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17084 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17085 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17086 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17087 i128::from(n),
17088 precision,
17089 scale,
17090 col_name,
17091 )?),
17092 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
17093 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
17094 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
17095 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17096 i128::from(n),
17097 precision,
17098 scale,
17099 col_name,
17100 )?),
17101 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
17102 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
17103 #[allow(clippy::cast_precision_loss)]
17104 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
17105 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
17106 i128::from(n),
17107 precision,
17108 scale,
17109 col_name,
17110 )?),
17111 (Value::Float(x), DataType::Numeric { precision, scale }) => {
17112 Some(numeric_from_float(x, precision, scale, col_name)?)
17113 }
17114 (Value::Text(s), DataType::Numeric { precision, scale }) => {
17125 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
17126 return Err(EngineError::Eval(EvalError::TypeMismatch {
17127 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
17128 }));
17129 };
17130 Some(numeric_rescale(
17131 mantissa, src_scale, precision, scale, col_name,
17132 )?)
17133 }
17134 (Value::Text(s), DataType::Date) => {
17136 let d = eval::parse_date_literal(&s).ok_or_else(|| {
17137 EngineError::Eval(EvalError::TypeMismatch {
17138 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
17139 })
17140 })?;
17141 Some(Value::Date(d))
17142 }
17143 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
17150 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
17151 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
17152 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
17153 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
17154 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
17155 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
17156 _ => None,
17157 },
17158 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17167 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17168 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
17169 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
17173 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
17174 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
17182 (Value::Text(s), DataType::Bytes) => {
17189 let bytes = decode_bytea_literal(&s).map_err(|e| {
17190 EngineError::Eval(EvalError::TypeMismatch {
17191 detail: alloc::format!(
17192 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
17193 ),
17194 })
17195 })?;
17196 Some(Value::Bytes(bytes))
17197 }
17198 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
17202 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
17210 Some(b) => Some(Value::Uuid(b)),
17211 None => {
17212 return Err(EngineError::Eval(EvalError::TypeMismatch {
17213 detail: alloc::format!(
17214 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
17215 ),
17216 }));
17217 }
17218 },
17219 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17224 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17230 Some(us) => Some(Value::Time(us)),
17231 None => {
17232 return Err(EngineError::Eval(EvalError::TypeMismatch {
17233 detail: alloc::format!(
17234 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17235 ),
17236 }));
17237 }
17238 },
17239 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17241 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17246 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17247 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17248 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17252 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17253 Err(_) => {
17254 return Err(EngineError::Eval(EvalError::TypeMismatch {
17255 detail: alloc::format!(
17256 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17257 ),
17258 }));
17259 }
17260 },
17261 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17263 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17267 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17268 None => {
17269 return Err(EngineError::Eval(EvalError::TypeMismatch {
17270 detail: alloc::format!(
17271 "invalid input syntax for type time with time zone: \
17272 {s:?} (column `{col_name}`)"
17273 ),
17274 }));
17275 }
17276 },
17277 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17279 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17280 }
17281 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17285 Some(c) => Some(Value::Money(c)),
17286 None => {
17287 return Err(EngineError::Eval(EvalError::TypeMismatch {
17288 detail: alloc::format!(
17289 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17290 ),
17291 }));
17292 }
17293 },
17294 (Value::SmallInt(n), DataType::Money) => {
17298 Some(Value::Money(i64::from(n).saturating_mul(100)))
17299 }
17300 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17301 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17302 (Value::Float(x), DataType::Money) => {
17303 let scaled = x * 100.0;
17306 let cents = if scaled >= 0.0 {
17307 (scaled + 0.5) as i64
17308 } else {
17309 (scaled - 0.5) as i64
17310 };
17311 Some(Value::Money(cents))
17312 }
17313 (Value::Numeric { scaled, scale }, DataType::Money) => {
17314 let cents = if scale == 2 {
17317 scaled
17318 } else if scale < 2 {
17319 let mult = 10_i128.pow(u32::from(2 - scale));
17320 scaled.saturating_mul(mult)
17321 } else {
17322 let div = 10_i128.pow(u32::from(scale - 2));
17323 let half = div / 2;
17324 let bias = if scaled >= 0 { half } else { -half };
17325 (scaled + bias) / div
17326 };
17327 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17328 }
17329 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17331 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17335 Some(v) => Some(v),
17336 None => {
17337 return Err(EngineError::Eval(EvalError::TypeMismatch {
17338 detail: alloc::format!(
17339 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17340 ),
17341 }));
17342 }
17343 },
17344 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17346 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17348 Some(pairs) => Some(Value::Hstore(pairs)),
17349 None => {
17350 return Err(EngineError::Eval(EvalError::TypeMismatch {
17351 detail: alloc::format!(
17352 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17353 ),
17354 }));
17355 }
17356 },
17357 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17359 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17362 Ok(m) => Some(Value::IntArray2D(m)),
17363 Err(e) => {
17364 return Err(EngineError::Eval(EvalError::TypeMismatch {
17365 detail: alloc::format!(
17366 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17367 ),
17368 }));
17369 }
17370 },
17371 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17372 Ok(m) => Some(Value::BigIntArray2D(m)),
17373 Err(e) => {
17374 return Err(EngineError::Eval(EvalError::TypeMismatch {
17375 detail: alloc::format!(
17376 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17377 ),
17378 }));
17379 }
17380 },
17381 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17382 Ok(m) => Some(Value::TextArray2D(m)),
17383 Err(e) => {
17384 return Err(EngineError::Eval(EvalError::TypeMismatch {
17385 detail: alloc::format!(
17386 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17387 ),
17388 }));
17389 }
17390 },
17391 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17393 (Value::BigIntArray2D(rows), DataType::Text) => {
17394 Some(Value::Text(format_bigint_2d_text(&rows)))
17395 }
17396 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17397 (Value::Text(s), DataType::TextArray) => {
17402 let arr = decode_text_array_literal(&s).map_err(|e| {
17403 EngineError::Eval(EvalError::TypeMismatch {
17404 detail: alloc::format!(
17405 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17406 ),
17407 })
17408 })?;
17409 Some(Value::TextArray(arr))
17410 }
17411 (Value::Text(s), DataType::IntArray) => {
17417 let arr = decode_text_array_literal(&s).map_err(|e| {
17418 EngineError::Eval(EvalError::TypeMismatch {
17419 detail: alloc::format!(
17420 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17421 ),
17422 })
17423 })?;
17424 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17425 for elem in arr {
17426 match elem {
17427 None => out.push(None),
17428 Some(t) => {
17429 let n: i32 = t.parse().map_err(|_| {
17430 EngineError::Eval(EvalError::TypeMismatch {
17431 detail: alloc::format!(
17432 "cannot parse {t:?} as INT element for `{col_name}`"
17433 ),
17434 })
17435 })?;
17436 out.push(Some(n));
17437 }
17438 }
17439 }
17440 Some(Value::IntArray(out))
17441 }
17442 (Value::Text(s), DataType::BigIntArray) => {
17443 let arr = decode_text_array_literal(&s).map_err(|e| {
17444 EngineError::Eval(EvalError::TypeMismatch {
17445 detail: alloc::format!(
17446 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17447 ),
17448 })
17449 })?;
17450 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17451 for elem in arr {
17452 match elem {
17453 None => out.push(None),
17454 Some(t) => {
17455 let n: i64 = t.parse().map_err(|_| {
17456 EngineError::Eval(EvalError::TypeMismatch {
17457 detail: alloc::format!(
17458 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17459 ),
17460 })
17461 })?;
17462 out.push(Some(n));
17463 }
17464 }
17465 }
17466 Some(Value::BigIntArray(out))
17467 }
17468 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17472 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17481 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17482 EngineError::Eval(EvalError::TypeMismatch {
17483 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17484 })
17485 })?;
17486 if parsed.len() != dim as usize {
17487 return Err(EngineError::Eval(EvalError::TypeMismatch {
17488 detail: alloc::format!(
17489 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17490 parsed.len()
17491 ),
17492 }));
17493 }
17494 Some(match encoding {
17495 VecEncoding::F32 => Value::Vector(parsed),
17496 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17497 VecEncoding::F16 => {
17498 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17499 }
17500 })
17501 }
17502 (Value::Text(s), DataType::TsVector) => {
17512 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17513 EngineError::Eval(EvalError::TypeMismatch {
17514 detail: alloc::format!(
17515 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17516 ),
17517 })
17518 })?;
17519 Some(Value::TsVector(lexs))
17520 }
17521 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17522 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17523 EngineError::Eval(EvalError::TypeMismatch {
17524 detail: alloc::format!(
17525 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17526 ),
17527 })
17528 })?;
17529 Some(Value::Timestamp(t))
17530 }
17531 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17534 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17535 }
17536 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17540 (Value::Timestamp(t), DataType::Date) => {
17541 let days = t.div_euclid(86_400_000_000);
17542 i32::try_from(days).ok().map(Value::Date)
17543 }
17544 (
17545 Value::Numeric {
17546 scaled,
17547 scale: src_scale,
17548 },
17549 DataType::Numeric { precision, scale },
17550 ) => Some(numeric_rescale(
17551 scaled, src_scale, precision, scale, col_name,
17552 )?),
17553 #[allow(clippy::cast_precision_loss)]
17554 (Value::Numeric { scaled, scale }, DataType::Float) => {
17555 let mut div = 1.0_f64;
17556 for _ in 0..scale {
17557 div *= 10.0;
17558 }
17559 Some(Value::Float((scaled as f64) / div))
17560 }
17561 (Value::Numeric { scaled, scale }, DataType::Int) => {
17562 let truncated = numeric_truncate_to_integer(scaled, scale);
17563 i32::try_from(truncated).ok().map(Value::Int)
17564 }
17565 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17566 let truncated = numeric_truncate_to_integer(scaled, scale);
17567 i64::try_from(truncated).ok().map(Value::BigInt)
17568 }
17569 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17570 let truncated = numeric_truncate_to_integer(scaled, scale);
17571 i16::try_from(truncated).ok().map(Value::SmallInt)
17572 }
17573 (Value::Text(s), DataType::Varchar(max)) => {
17575 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17576 Some(Value::Text(s))
17577 } else {
17578 return Err(EngineError::Unsupported(alloc::format!(
17579 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17580 {} chars",
17581 s.chars().count()
17582 )));
17583 }
17584 }
17585 (
17593 Value::Vector(v),
17594 DataType::Vector {
17595 dim,
17596 encoding: VecEncoding::Sq8,
17597 },
17598 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17599 (
17604 Value::Vector(v),
17605 DataType::Vector {
17606 dim,
17607 encoding: VecEncoding::F16,
17608 },
17609 ) if v.len() == dim as usize => Some(Value::HalfVector(
17610 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17611 )),
17612 (Value::Text(s), DataType::Char(size)) => {
17616 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17617 if len > size {
17618 return Err(EngineError::Unsupported(alloc::format!(
17619 "value for CHAR({size}) column `{col_name}` exceeds length: \
17620 {len} chars"
17621 )));
17622 }
17623 let need = (size - len) as usize;
17624 let mut padded = s;
17625 padded.reserve(need);
17626 for _ in 0..need {
17627 padded.push(' ');
17628 }
17629 Some(Value::Text(padded))
17630 }
17631 _ => None,
17632 };
17633 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17634 column: col_name.into(),
17635 expected,
17636 actual,
17637 position,
17638 }))
17639}
17640
17641fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17647 use core::fmt::Write;
17648 let mut out = alloc::string::String::from("(");
17649 for (i, a) in args.iter().enumerate() {
17650 if i > 0 {
17651 out.push_str(", ");
17652 }
17653 match a.mode {
17654 spg_sql::ast::FunctionArgMode::In => {}
17655 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17656 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17657 }
17658 if let Some(n) = &a.name {
17659 out.push_str(n);
17660 out.push(' ');
17661 }
17662 match &a.ty {
17663 spg_sql::ast::FunctionArgType::Typed(t) => {
17664 let _ = write!(out, "{t}");
17665 }
17666 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17667 }
17668 }
17669 out.push(')');
17670 out
17671}
17672
17673fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
17682 match expr {
17683 spg_sql::ast::Expr::FunctionCall { name, args } => {
17684 name.eq_ignore_ascii_case("unnest") && args.len() == 1
17685 }
17686 _ => false,
17687 }
17688}
17689
17690fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
17694 match expr {
17695 spg_sql::ast::Expr::FunctionCall { name, args }
17696 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
17697 {
17698 Some(&args[0])
17699 }
17700 _ => None,
17701 }
17702}
17703
17704fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
17709 match v {
17710 Value::Null => Ok(Vec::new()),
17711 Value::TextArray(items) => Ok(items
17712 .iter()
17713 .map(|opt| {
17714 opt.as_ref()
17715 .map(|s| Value::Text(s.clone()))
17716 .unwrap_or(Value::Null)
17717 })
17718 .collect()),
17719 Value::IntArray(items) => Ok(items
17720 .iter()
17721 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
17722 .collect()),
17723 Value::BigIntArray(items) => Ok(items
17724 .iter()
17725 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
17726 .collect()),
17727 other => Err(EngineError::Eval(EvalError::TypeMismatch {
17728 detail: alloc::format!(
17729 "unnest() expects an array argument, got {:?}",
17730 other.data_type()
17731 ),
17732 })),
17733 }
17734}
17735
17736#[cfg(test)]
17737mod tests {
17738 use super::*;
17739 use alloc::vec;
17740
17741 fn unwrap_command_ok(r: &QueryResult) -> usize {
17742 match r {
17743 QueryResult::CommandOk { affected, .. } => *affected,
17744 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17745 }
17746 }
17747
17748 #[test]
17749 fn update_seek_positions_engages_on_indexed_eq() {
17750 let mut e = Engine::new();
17751 e.execute("CREATE TABLE b (id INT NOT NULL, v INT NOT NULL)")
17752 .unwrap();
17753 e.execute("CREATE INDEX b_id ON b (id)").unwrap();
17754 for i in 0..100 {
17755 e.execute(&alloc::format!("INSERT INTO b VALUES ({i}, {i})"))
17756 .unwrap();
17757 }
17758 let stmt = spg_sql::parser::parse_statement("UPDATE b SET v = v + 1 WHERE id = 42")
17759 .expect("parse");
17760 let Statement::Update(u) = stmt else {
17761 panic!("expected Update, got {stmt:?}");
17762 };
17763 let w = u.where_.as_ref().expect("where");
17764 let table = e.catalog().get("b").unwrap();
17765 let schema_cols = table.schema().columns.clone();
17766 let Expr::Binary { lhs, op, rhs } = w else {
17768 panic!("WHERE not Binary: {w:?}");
17769 };
17770 assert_eq!(*op, BinOp::Eq, "op not Eq");
17771 let pair = resolve_col_literal_pair(lhs, rhs, &schema_cols, "b");
17772 assert!(
17773 pair.is_some(),
17774 "resolve_col_literal_pair None: lhs={lhs:?} rhs={rhs:?}"
17775 );
17776 let (col_pos, value) = pair.unwrap();
17777 assert!(
17778 table.index_on(col_pos).is_some(),
17779 "no index on col {col_pos}"
17780 );
17781 assert!(
17782 IndexKey::from_value(&value).is_some(),
17783 "IndexKey::from_value None for {value:?}"
17784 );
17785 let positions = try_index_seek_positions(w, &schema_cols, table, "b");
17786 assert_eq!(positions, Some(vec![42]), "seek did not engage");
17787 }
17788
17789 #[test]
17790 fn create_table_registers_schema() {
17791 let mut e = Engine::new();
17792 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17793 .unwrap();
17794 assert_eq!(e.catalog().table_count(), 1);
17795 let t = e.catalog().get("foo").unwrap();
17796 assert_eq!(t.schema().columns.len(), 2);
17797 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17798 assert!(!t.schema().columns[0].nullable);
17799 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17800 }
17801
17802 #[test]
17803 fn create_table_vector_default_is_f32_encoded() {
17804 let mut e = Engine::new();
17805 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17806 let t = e.catalog().get("t").unwrap();
17807 assert_eq!(
17808 t.schema().columns[0].ty,
17809 DataType::Vector {
17810 dim: 8,
17811 encoding: VecEncoding::F32,
17812 },
17813 );
17814 }
17815
17816 #[test]
17817 fn create_table_vector_using_sq8_succeeds() {
17818 let mut e = Engine::new();
17822 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17823 let t = e.catalog().get("t").unwrap();
17824 assert_eq!(
17825 t.schema().columns[0].ty,
17826 DataType::Vector {
17827 dim: 8,
17828 encoding: VecEncoding::Sq8,
17829 },
17830 );
17831 }
17832
17833 #[test]
17834 fn insert_into_sq8_column_quantises_f32_payload() {
17835 let mut e = Engine::new();
17842 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17843 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17844 .unwrap();
17845 let t = e.catalog().get("t").unwrap();
17846 assert_eq!(t.rows().len(), 1);
17847 match &t.rows()[0].values[0] {
17848 Value::Sq8Vector(q) => {
17849 assert_eq!(q.bytes.len(), 4);
17850 assert!((q.min - 0.0).abs() < 1e-6);
17852 assert!((q.max - 1.0).abs() < 1e-6);
17853 }
17854 other => panic!("expected Sq8Vector cell, got {other:?}"),
17855 }
17856 }
17857
17858 #[test]
17859 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17860 let mut e = Engine::new();
17867 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17868 .unwrap();
17869 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17870 .unwrap();
17871 let t = e.catalog().get("t").unwrap();
17872 assert_eq!(t.rows().len(), 1);
17873 match &t.rows()[0].values[0] {
17874 Value::HalfVector(h) => {
17875 assert_eq!(h.dim(), 4);
17876 let back = h.to_f32_vec();
17877 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17878 for (g, e) in back.iter().zip(expected.iter()) {
17879 assert!(
17880 (g - e).abs() < 1e-6,
17881 "{g} vs {e} should be exact on f16 grid"
17882 );
17883 }
17884 }
17885 other => panic!("expected HalfVector cell, got {other:?}"),
17886 }
17887 }
17888
17889 #[test]
17890 fn alter_index_rebuild_in_place_succeeds() {
17891 let mut e = Engine::new();
17896 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17897 .unwrap();
17898 for i in 0..8_i32 {
17899 #[allow(clippy::cast_precision_loss)]
17900 let base = (i as f32) * 0.1;
17901 e.execute(&alloc::format!(
17902 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
17903 b1 = base + 0.01,
17904 b2 = base + 0.02,
17905 ))
17906 .unwrap();
17907 }
17908 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17909 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
17910 assert_eq!(
17912 e.catalog().get("t").unwrap().schema().columns[1].ty,
17913 DataType::Vector {
17914 dim: 3,
17915 encoding: VecEncoding::F32,
17916 },
17917 );
17918 }
17919
17920 #[test]
17921 fn alter_index_rebuild_with_encoding_switches_cell_type() {
17922 let mut e = Engine::new();
17927 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
17928 .unwrap();
17929 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
17930 .unwrap();
17931 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17932 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
17933 .unwrap();
17934 let t = e.catalog().get("t").unwrap();
17935 assert_eq!(
17936 t.schema().columns[1].ty,
17937 DataType::Vector {
17938 dim: 4,
17939 encoding: VecEncoding::Sq8,
17940 },
17941 );
17942 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
17943 }
17944
17945 #[test]
17946 fn alter_index_rebuild_unknown_index_errors() {
17947 let mut e = Engine::new();
17948 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
17949 assert!(
17950 matches!(
17951 &err,
17952 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
17953 ),
17954 "got: {err}"
17955 );
17956 }
17957
17958 #[test]
17959 fn alter_index_rebuild_on_btree_index_errors() {
17960 let mut e = Engine::new();
17963 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17964 e.execute("INSERT INTO t VALUES (1)").unwrap();
17965 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
17966 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
17967 assert!(
17968 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
17969 "got: {err}"
17970 );
17971 }
17972
17973 #[test]
17974 fn prepared_insert_substitutes_placeholders() {
17975 let mut e = Engine::new();
17981 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17982 .unwrap();
17983 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
17984 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
17985 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
17986 .unwrap();
17987 }
17988 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
17990 let QueryResult::Rows { rows, .. } = rows_result else {
17991 panic!("expected Rows")
17992 };
17993 assert_eq!(rows.len(), 3);
17994 }
17995
17996 #[test]
17997 fn prepared_select_with_placeholder_filters_rows() {
17998 let mut e = Engine::new();
17999 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18000 .unwrap();
18001 for i in 0..10_i32 {
18002 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18003 .unwrap();
18004 }
18005 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18006 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
18007 else {
18008 panic!("expected Rows")
18009 };
18010 assert_eq!(rows.len(), 1);
18012 assert_eq!(rows[0].values[0], Value::Int(5));
18013 }
18014
18015 #[test]
18016 fn prepared_too_few_params_errors() {
18017 let mut e = Engine::new();
18018 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18019 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18020 let err = e.execute_prepared(stmt, &[]).unwrap_err();
18021 assert!(
18022 matches!(
18023 &err,
18024 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
18025 ),
18026 "got: {err}"
18027 );
18028 }
18029
18030 #[test]
18031 fn bytea_cast_round_trips_text_input() {
18032 let e = Engine::new();
18035 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
18036 let QueryResult::Rows { rows, .. } = r else {
18037 panic!("expected Rows")
18038 };
18039 assert_eq!(rows.len(), 1);
18040 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
18041 }
18042
18043 #[test]
18044 fn bytea_cast_pg_escape_hex_form() {
18045 let e = Engine::new();
18049 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
18050 let QueryResult::Rows { rows, .. } = r else {
18051 panic!("expected Rows")
18052 };
18053 assert_eq!(
18054 rows[0].values[0],
18055 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
18056 );
18057 }
18058
18059 #[test]
18060 fn bytea_cast_chains_through_octet_length() {
18061 let e = Engine::new();
18065 let r = e
18066 .execute_readonly("SELECT octet_length('hello'::bytea)")
18067 .unwrap();
18068 let QueryResult::Rows { rows, .. } = r else {
18069 panic!("expected Rows")
18070 };
18071 match &rows[0].values[0] {
18072 Value::Int(n) => assert_eq!(*n, 5),
18073 Value::BigInt(n) => assert_eq!(*n, 5),
18074 other => panic!("expected integer length, got {other:?}"),
18075 }
18076 }
18077
18078 #[test]
18079 fn readonly_prepared_on_snapshot_select_with_placeholder() {
18080 let mut e = Engine::new();
18086 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
18087 .unwrap();
18088 for i in 0..10_i32 {
18089 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
18090 .unwrap();
18091 }
18092 let snapshot = e.clone_snapshot();
18093 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
18094 let QueryResult::Rows { rows, .. } =
18095 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
18096 .unwrap()
18097 else {
18098 panic!("expected Rows")
18099 };
18100 assert_eq!(rows.len(), 1);
18101 assert_eq!(rows[0].values[0], Value::Int(5));
18102 }
18103
18104 #[test]
18105 fn readonly_prepared_on_snapshot_rejects_writes() {
18106 let mut e = Engine::new();
18110 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18111 let snapshot = e.clone_snapshot();
18112 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
18113 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
18114 .unwrap_err();
18115 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
18116 }
18117
18118 #[test]
18119 fn readonly_prepared_on_snapshot_frozen_view() {
18120 let mut e = Engine::new();
18126 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18127 e.execute("INSERT INTO t VALUES (1)").unwrap();
18128 let snapshot = e.clone_snapshot();
18129 e.execute("INSERT INTO t VALUES (2)").unwrap();
18130 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
18131 let QueryResult::Rows { rows, .. } =
18132 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
18133 .unwrap()
18134 else {
18135 panic!("expected Rows")
18136 };
18137 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
18138 }
18139
18140 #[test]
18141 fn describe_prepared_on_snapshot_resolves_columns() {
18142 let mut e = Engine::new();
18147 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
18148 .unwrap();
18149 let snapshot = e.clone_snapshot();
18150 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
18151 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
18152 assert_eq!(cols.len(), 2);
18153 assert_eq!(cols[0].name, "id");
18154 assert_eq!(cols[0].ty, DataType::Int);
18155 assert_eq!(cols[1].name, "name");
18156 assert_eq!(cols[1].ty, DataType::Text);
18157 }
18158
18159 #[test]
18160 fn insert_into_half_column_dim_mismatch_errors() {
18161 let mut e = Engine::new();
18162 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
18163 .unwrap();
18164 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18165 assert!(matches!(
18166 &err,
18167 EngineError::Storage(StorageError::TypeMismatch { .. })
18168 ));
18169 }
18170
18171 #[test]
18172 fn insert_into_sq8_column_dim_mismatch_errors() {
18173 let mut e = Engine::new();
18178 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
18179 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
18180 assert!(
18181 matches!(
18182 &err,
18183 EngineError::Storage(StorageError::TypeMismatch { .. })
18184 ),
18185 "got: {err}",
18186 );
18187 }
18188
18189 #[test]
18190 fn create_table_duplicate_errors() {
18191 let mut e = Engine::new();
18192 e.execute("CREATE TABLE foo (a INT)").unwrap();
18193 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
18194 assert!(matches!(
18195 err,
18196 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
18197 ));
18198 }
18199
18200 #[test]
18201 fn insert_into_unknown_table_errors() {
18202 let mut e = Engine::new();
18203 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
18204 assert!(matches!(
18205 err,
18206 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
18207 ));
18208 }
18209
18210 #[test]
18211 fn insert_happy_path_reports_one_affected() {
18212 let mut e = Engine::new();
18213 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18214 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
18215 assert_eq!(unwrap_command_ok(&r), 1);
18216 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
18217 }
18218
18219 #[test]
18220 fn insert_arity_mismatch_propagates() {
18221 let mut e = Engine::new();
18222 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
18223 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
18224 assert!(matches!(
18225 err,
18226 EngineError::Storage(StorageError::ArityMismatch { .. })
18227 ));
18228 }
18229
18230 #[test]
18231 fn insert_negative_integer_via_unary_minus() {
18232 let mut e = Engine::new();
18233 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18234 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
18235 let rows = e.catalog().get("foo").unwrap().rows();
18236 assert_eq!(rows[0].values[0], Value::Int(-7));
18237 }
18238
18239 #[test]
18240 fn insert_expression_evaluated_against_empty_context() {
18241 let mut e = Engine::new();
18246 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
18247 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
18248 let rows = e.catalog().get("foo").unwrap().rows();
18249 assert_eq!(rows[0].values[0], Value::Int(3));
18250 }
18251
18252 #[test]
18253 fn select_star_returns_all_rows_in_insertion_order() {
18254 let mut e = Engine::new();
18255 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
18256 .unwrap();
18257 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
18258 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
18259 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
18260
18261 let r = e.execute("SELECT * FROM foo").unwrap();
18262 let QueryResult::Rows { columns, rows } = r else {
18263 panic!("expected Rows")
18264 };
18265 assert_eq!(columns.len(), 2);
18266 assert_eq!(columns[0].name, "a");
18267 assert_eq!(rows.len(), 3);
18268 assert_eq!(
18269 rows[1].values,
18270 vec![Value::Int(2), Value::Text("two".into())]
18271 );
18272 }
18273
18274 #[test]
18275 fn select_star_on_empty_table_returns_zero_rows() {
18276 let mut e = Engine::new();
18277 e.execute("CREATE TABLE foo (a INT)").unwrap();
18278 let r = e.execute("SELECT * FROM foo").unwrap();
18279 match r {
18280 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18281 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18282 }
18283 }
18284
18285 fn make_three_row_users(e: &mut Engine) {
18288 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18289 .unwrap();
18290 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18291 .unwrap();
18292 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18293 .unwrap();
18294 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18295 .unwrap();
18296 }
18297
18298 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18299 match r {
18300 QueryResult::Rows { columns, rows } => (columns, rows),
18301 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18302 }
18303 }
18304
18305 #[test]
18306 fn where_filter_passes_only_true_rows() {
18307 let mut e = Engine::new();
18308 make_three_row_users(&mut e);
18309 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18310 let (_, rows) = unwrap_rows(r);
18311 assert_eq!(rows.len(), 2);
18312 assert_eq!(rows[0].values[0], Value::Int(2));
18313 assert_eq!(rows[1].values[0], Value::Int(3));
18314 }
18315
18316 #[test]
18317 fn where_with_null_result_filters_out_row() {
18318 let mut e = Engine::new();
18319 make_three_row_users(&mut e);
18320 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18322 let (_, rows) = unwrap_rows(r);
18323 assert_eq!(rows.len(), 1);
18324 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18325 }
18326
18327 #[test]
18328 fn projection_named_columns() {
18329 let mut e = Engine::new();
18330 make_three_row_users(&mut e);
18331 let r = e.execute("SELECT name, score FROM users").unwrap();
18332 let (cols, rows) = unwrap_rows(r);
18333 assert_eq!(cols.len(), 2);
18334 assert_eq!(cols[0].name, "name");
18335 assert_eq!(cols[1].name, "score");
18336 assert_eq!(rows.len(), 3);
18337 assert_eq!(
18338 rows[0].values,
18339 vec![Value::Text("alice".into()), Value::Int(90)]
18340 );
18341 }
18342
18343 #[test]
18344 fn projection_with_column_alias() {
18345 let mut e = Engine::new();
18346 make_three_row_users(&mut e);
18347 let r = e
18348 .execute("SELECT name AS who FROM users WHERE id = 1")
18349 .unwrap();
18350 let (cols, rows) = unwrap_rows(r);
18351 assert_eq!(cols[0].name, "who");
18352 assert_eq!(rows.len(), 1);
18353 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18354 }
18355
18356 #[test]
18357 fn qualified_column_with_table_alias_resolves() {
18358 let mut e = Engine::new();
18359 make_three_row_users(&mut e);
18360 let r = e
18361 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18362 .unwrap();
18363 let (cols, rows) = unwrap_rows(r);
18364 assert_eq!(cols.len(), 2);
18365 assert_eq!(rows.len(), 2);
18366 }
18367
18368 #[test]
18369 fn qualified_column_with_wrong_alias_errors() {
18370 let mut e = Engine::new();
18371 make_three_row_users(&mut e);
18372 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18373 assert!(matches!(
18374 err,
18375 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18376 ));
18377 }
18378
18379 #[test]
18380 fn select_unknown_column_errors_in_projection() {
18381 let mut e = Engine::new();
18382 make_three_row_users(&mut e);
18383 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18384 assert!(matches!(
18385 err,
18386 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18387 ));
18388 }
18389
18390 #[test]
18391 fn where_unknown_column_errors() {
18392 let mut e = Engine::new();
18393 make_three_row_users(&mut e);
18394 let err = e
18395 .execute("SELECT * FROM users WHERE ghost = 1")
18396 .unwrap_err();
18397 assert!(matches!(
18398 err,
18399 EngineError::Eval(EvalError::ColumnNotFound { .. })
18400 ));
18401 }
18402
18403 #[test]
18404 fn expression_projection_evaluates_and_renders() {
18405 let mut e = Engine::new();
18408 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18409 e.execute("INSERT INTO t VALUES (3)").unwrap();
18410 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18411 assert_eq!(rows.len(), 1);
18412 assert_eq!(rows[0].values[0], Value::Int(3));
18415 }
18416
18417 #[test]
18418 fn select_unknown_table_errors() {
18419 let mut e = Engine::new();
18420 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18421 assert!(matches!(
18422 err,
18423 EngineError::Storage(StorageError::TableNotFound { .. })
18424 ));
18425 }
18426
18427 #[test]
18428 fn invalid_sql_returns_parse_error() {
18429 let mut e = Engine::new();
18432 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18433 assert!(matches!(err, EngineError::Parse(_)));
18434 }
18435
18436 #[test]
18439 fn create_index_registers_on_table() {
18440 let mut e = Engine::new();
18441 make_three_row_users(&mut e);
18442 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18443 let t = e.catalog().get("users").unwrap();
18444 assert_eq!(t.indices().len(), 1);
18445 assert_eq!(t.indices()[0].name, "by_name");
18446 }
18447
18448 #[test]
18449 fn create_index_on_unknown_table_errors() {
18450 let mut e = Engine::new();
18451 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18452 assert!(matches!(
18453 err,
18454 EngineError::Storage(StorageError::TableNotFound { .. })
18455 ));
18456 }
18457
18458 #[test]
18459 fn create_index_on_unknown_column_errors() {
18460 let mut e = Engine::new();
18461 make_three_row_users(&mut e);
18462 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18463 assert!(matches!(
18464 err,
18465 EngineError::Storage(StorageError::ColumnNotFound { .. })
18466 ));
18467 }
18468
18469 #[test]
18470 fn select_eq_uses_index_returns_same_rows_as_scan() {
18471 let mut without = Engine::new();
18475 make_three_row_users(&mut without);
18476 let mut with = Engine::new();
18477 make_three_row_users(&mut with);
18478 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18479
18480 let q = "SELECT * FROM users WHERE id = 2";
18481 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18482 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18483 assert_eq!(no_idx_rows, idx_rows);
18484 assert_eq!(idx_rows.len(), 1);
18485 }
18486
18487 #[test]
18488 fn select_eq_with_no_matching_index_value_returns_empty() {
18489 let mut e = Engine::new();
18490 make_three_row_users(&mut e);
18491 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18492 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18493 assert_eq!(rows.len(), 0);
18494 }
18495
18496 #[test]
18499 fn begin_sets_in_transaction_flag() {
18500 let mut e = Engine::new();
18501 assert!(!e.in_transaction());
18502 e.execute("BEGIN").unwrap();
18503 assert!(e.in_transaction());
18504 }
18505
18506 #[test]
18507 fn double_begin_errors() {
18508 let mut e = Engine::new();
18509 e.execute("BEGIN").unwrap();
18510 let err = e.execute("BEGIN").unwrap_err();
18511 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18512 }
18513
18514 #[test]
18515 fn commit_without_begin_errors() {
18516 let mut e = Engine::new();
18517 let err = e.execute("COMMIT").unwrap_err();
18518 assert_eq!(err, EngineError::NoActiveTransaction);
18519 }
18520
18521 #[test]
18522 fn rollback_without_begin_errors() {
18523 let mut e = Engine::new();
18524 let err = e.execute("ROLLBACK").unwrap_err();
18525 assert_eq!(err, EngineError::NoActiveTransaction);
18526 }
18527
18528 #[test]
18529 fn commit_applies_shadow_to_committed_catalog() {
18530 let mut e = Engine::new();
18531 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18532 e.execute("BEGIN").unwrap();
18533 e.execute("INSERT INTO t VALUES (1)").unwrap();
18534 e.execute("INSERT INTO t VALUES (2)").unwrap();
18535 e.execute("COMMIT").unwrap();
18536 assert!(!e.in_transaction());
18537 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18538 }
18539
18540 #[test]
18541 fn rollback_discards_shadow() {
18542 let mut e = Engine::new();
18543 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18544 e.execute("BEGIN").unwrap();
18545 e.execute("INSERT INTO t VALUES (1)").unwrap();
18546 e.execute("INSERT INTO t VALUES (2)").unwrap();
18547 e.execute("ROLLBACK").unwrap();
18548 assert!(!e.in_transaction());
18549 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18550 }
18551
18552 #[test]
18553 fn select_during_tx_sees_uncommitted_writes_own_session() {
18554 let mut e = Engine::new();
18557 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18558 e.execute("BEGIN").unwrap();
18559 e.execute("INSERT INTO t VALUES (42)").unwrap();
18560 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18561 assert_eq!(rows.len(), 1);
18562 assert_eq!(rows[0].values[0], Value::Int(42));
18563 }
18564
18565 #[test]
18566 fn snapshot_with_no_users_is_bare_catalog_format() {
18567 let mut e = Engine::new();
18568 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18569 let bytes = e.snapshot();
18570 assert_eq!(
18571 &bytes[..8],
18572 b"SPGDB001",
18573 "must be the bare v3.x catalog magic"
18574 );
18575 let e2 = Engine::restore_envelope(&bytes).unwrap();
18576 assert!(e2.users().is_empty());
18577 assert_eq!(e2.catalog().table_count(), 1);
18578 }
18579
18580 #[test]
18581 fn snapshot_with_users_round_trips_both_via_envelope() {
18582 let mut e = Engine::new();
18583 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18584 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18585 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18586 .unwrap();
18587 let bytes = e.snapshot();
18588 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18589 let e2 = Engine::restore_envelope(&bytes).unwrap();
18590 assert_eq!(e2.users().len(), 2);
18591 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18592 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18593 assert_eq!(e2.verify_user("alice", "wrong"), None);
18594 assert_eq!(e2.catalog().table_count(), 1);
18595 }
18596
18597 #[test]
18598 fn ddl_inside_tx_also_rolled_back() {
18599 let mut e = Engine::new();
18600 e.execute("BEGIN").unwrap();
18601 e.execute("CREATE TABLE t (v INT)").unwrap();
18602 e.execute("SELECT * FROM t").unwrap();
18604 e.execute("ROLLBACK").unwrap();
18605 let err = e.execute("SELECT * FROM t").unwrap_err();
18607 assert!(matches!(
18608 err,
18609 EngineError::Storage(StorageError::TableNotFound { .. })
18610 ));
18611 }
18612
18613 #[test]
18616 fn create_publication_lands_in_catalog() {
18617 let mut e = Engine::new();
18618 assert!(e.publications().is_empty());
18619 e.execute("CREATE PUBLICATION pub_a").unwrap();
18620 assert_eq!(e.publications().len(), 1);
18621 assert!(e.publications().contains("pub_a"));
18622 }
18623
18624 #[test]
18625 fn create_publication_duplicate_errors() {
18626 let mut e = Engine::new();
18627 e.execute("CREATE PUBLICATION pub_a").unwrap();
18628 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18629 assert!(
18630 alloc::format!("{err:?}").contains("DuplicateName"),
18631 "got {err:?}"
18632 );
18633 }
18634
18635 #[test]
18636 fn drop_publication_silent_when_absent() {
18637 let mut e = Engine::new();
18638 let r = e.execute("DROP PUBLICATION nope").unwrap();
18641 match r {
18642 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18643 other => panic!("expected CommandOk, got {other:?}"),
18644 }
18645 }
18646
18647 #[test]
18648 fn drop_publication_present_reports_one_affected() {
18649 let mut e = Engine::new();
18650 e.execute("CREATE PUBLICATION pub_a").unwrap();
18651 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18652 match r {
18653 QueryResult::CommandOk {
18654 affected,
18655 modified_catalog,
18656 } => {
18657 assert_eq!(affected, 1);
18658 assert!(modified_catalog);
18659 }
18660 other => panic!("expected CommandOk, got {other:?}"),
18661 }
18662 assert!(e.publications().is_empty());
18663 }
18664
18665 #[test]
18666 fn publications_persist_across_snapshot_restore() {
18667 let mut e = Engine::new();
18672 e.execute("CREATE PUBLICATION pub_a").unwrap();
18673 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18674 .unwrap();
18675 let snap = e.snapshot();
18676 let e2 = Engine::restore_envelope(&snap).unwrap();
18677 assert_eq!(e2.publications().len(), 2);
18678 assert!(e2.publications().contains("pub_a"));
18679 assert!(e2.publications().contains("pub_b"));
18680 }
18681
18682 #[test]
18683 fn create_publication_allowed_inside_transaction() {
18684 let mut e = Engine::new();
18688 e.execute("BEGIN").unwrap();
18689 e.execute("CREATE PUBLICATION pub_a").unwrap();
18690 e.execute("COMMIT").unwrap();
18691 assert!(e.publications().contains("pub_a"));
18692 }
18693
18694 #[test]
18697 fn create_publication_for_table_list_lands_with_scope() {
18698 let mut e = Engine::new();
18699 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18700 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18701 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18702 .unwrap();
18703 let scope = e.publications().get("pub_a").cloned();
18704 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18705 panic!("expected ForTables scope, got {scope:?}")
18706 };
18707 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18708 }
18709
18710 #[test]
18711 fn create_publication_all_tables_except_lands_with_scope() {
18712 let mut e = Engine::new();
18713 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18714 .unwrap();
18715 let scope = e.publications().get("pub_a").cloned();
18716 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18717 panic!("expected AllTablesExcept scope, got {scope:?}")
18718 };
18719 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18720 }
18721
18722 #[test]
18723 fn show_publications_empty_returns_zero_rows() {
18724 let e = Engine::new();
18725 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18726 let QueryResult::Rows { rows, columns } = r else {
18727 panic!()
18728 };
18729 assert!(rows.is_empty());
18730 assert_eq!(columns.len(), 3);
18731 assert_eq!(columns[0].name, "name");
18732 assert_eq!(columns[1].name, "scope");
18733 assert_eq!(columns[2].name, "table_count");
18734 }
18735
18736 #[test]
18737 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18738 let mut e = Engine::new();
18739 e.execute("CREATE PUBLICATION z_pub").unwrap();
18740 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18741 .unwrap();
18742 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18743 .unwrap();
18744 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18745 let QueryResult::Rows { rows, .. } = r else {
18746 panic!()
18747 };
18748 assert_eq!(rows.len(), 3);
18749 let names: Vec<&str> = rows
18751 .iter()
18752 .map(|r| {
18753 if let Value::Text(s) = &r.values[0] {
18754 s.as_str()
18755 } else {
18756 panic!()
18757 }
18758 })
18759 .collect();
18760 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18761 match &rows[0].values[1] {
18763 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18764 other => panic!("expected Text, got {other:?}"),
18765 }
18766 assert_eq!(rows[0].values[2], Value::Int(2));
18767 match &rows[1].values[1] {
18769 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18770 other => panic!("expected Text, got {other:?}"),
18771 }
18772 assert_eq!(rows[1].values[2], Value::Int(1));
18773 match &rows[2].values[1] {
18775 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18776 other => panic!("expected Text, got {other:?}"),
18777 }
18778 assert_eq!(rows[2].values[2], Value::Null);
18779 }
18780
18781 #[test]
18782 fn for_list_scopes_persist_across_snapshot() {
18783 let mut e = Engine::new();
18786 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18787 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18788 .unwrap();
18789 let snap = e.snapshot();
18790 let e2 = Engine::restore_envelope(&snap).unwrap();
18791 assert_eq!(e2.publications().len(), 2);
18792 let p1 = e2.publications().get("p1").cloned();
18793 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18794 panic!("p1 scope lost: {p1:?}")
18795 };
18796 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18797 let p2 = e2.publications().get("p2").cloned();
18798 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18799 panic!("p2 scope lost: {p2:?}")
18800 };
18801 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18802 }
18803
18804 #[test]
18807 fn create_subscription_lands_in_catalog_with_defaults() {
18808 let mut e = Engine::new();
18809 e.execute(
18810 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18811 )
18812 .unwrap();
18813 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18814 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18815 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18816 assert!(s.enabled);
18817 assert_eq!(s.last_received_pos, 0);
18818 }
18819
18820 #[test]
18821 fn create_subscription_duplicate_name_errors() {
18822 let mut e = Engine::new();
18823 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18824 .unwrap();
18825 let err = e
18826 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18827 .unwrap_err();
18828 assert!(
18829 alloc::format!("{err:?}").contains("DuplicateName"),
18830 "got {err:?}"
18831 );
18832 }
18833
18834 #[test]
18835 fn drop_subscription_silent_when_absent() {
18836 let mut e = Engine::new();
18837 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18838 match r {
18839 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18840 other => panic!("expected CommandOk, got {other:?}"),
18841 }
18842 }
18843
18844 #[test]
18845 fn subscription_advance_updates_last_pos_monotone() {
18846 let mut e = Engine::new();
18847 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18848 .unwrap();
18849 assert!(e.subscription_advance("s", 100));
18850 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18851 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18853 assert!(e.subscription_advance("s", 200));
18854 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18855 assert!(!e.subscription_advance("missing", 1));
18856 }
18857
18858 #[test]
18859 fn show_subscriptions_returns_rows_ordered_by_name() {
18860 let mut e = Engine::new();
18861 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18862 .unwrap();
18863 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18864 .unwrap();
18865 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18866 let QueryResult::Rows { rows, columns } = r else {
18867 panic!()
18868 };
18869 assert_eq!(rows.len(), 2);
18870 assert_eq!(columns.len(), 5);
18871 assert_eq!(columns[0].name, "name");
18872 assert_eq!(columns[4].name, "last_received_pos");
18873 let names: Vec<&str> = rows
18875 .iter()
18876 .map(|r| {
18877 if let Value::Text(s) = &r.values[0] {
18878 s.as_str()
18879 } else {
18880 panic!()
18881 }
18882 })
18883 .collect();
18884 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18885 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18887 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18888 assert_eq!(rows[0].values[3], Value::Bool(true));
18889 assert_eq!(rows[0].values[4], Value::BigInt(0));
18890 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18892 }
18893
18894 #[test]
18895 fn subscriptions_persist_across_snapshot_envelope_v4() {
18896 let mut e = Engine::new();
18897 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18898 .unwrap();
18899 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18900 .unwrap();
18901 e.subscription_advance("s2", 42);
18902 let snap = e.snapshot();
18903 let e2 = Engine::restore_envelope(&snap).unwrap();
18904 assert_eq!(e2.subscriptions().len(), 2);
18905 let s1 = e2.subscriptions().get("s1").unwrap();
18906 assert_eq!(s1.conn_str, "h=A");
18907 assert_eq!(
18908 s1.publications,
18909 alloc::vec!["p1".to_string(), "p2".to_string()]
18910 );
18911 assert_eq!(s1.last_received_pos, 0);
18912 let s2 = e2.subscriptions().get("s2").unwrap();
18913 assert_eq!(s2.last_received_pos, 42);
18914 }
18915
18916 #[test]
18917 fn v3_envelope_loads_with_empty_subscriptions() {
18918 let mut e = Engine::new();
18922 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
18923 let catalog = e.catalog.serialize();
18924 let users = crate::users::serialize_users(&e.users);
18925 let pubs = e.publications.serialize();
18926 let mut buf = Vec::new();
18927 buf.extend_from_slice(b"SPGENV01");
18928 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18930 buf.extend_from_slice(&catalog);
18931 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18932 buf.extend_from_slice(&users);
18933 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18934 buf.extend_from_slice(&pubs);
18935 let crc = spg_crypto::crc32::crc32(&buf);
18936 buf.extend_from_slice(&crc.to_le_bytes());
18937
18938 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
18939 assert!(e2.subscriptions().is_empty());
18940 assert!(e2.publications().contains("pub_legacy"));
18941 }
18942
18943 #[test]
18944 fn create_subscription_allowed_inside_transaction() {
18945 let mut e = Engine::new();
18946 e.execute("BEGIN").unwrap();
18947 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18948 .unwrap();
18949 e.execute("COMMIT").unwrap();
18950 assert!(e.subscriptions().contains("s"));
18951 }
18952
18953 #[test]
18955 fn analyze_populates_histogram_bounds() {
18956 let mut e = Engine::new();
18957 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
18958 .unwrap();
18959 for i in 0..50 {
18960 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
18961 .unwrap();
18962 }
18963 e.execute("ANALYZE t").unwrap();
18964 let stats = e.statistics();
18965 let id_stats = stats.get("t", "id").unwrap();
18966 assert!(id_stats.histogram_bounds.len() >= 2);
18967 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
18968 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
18969 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
18970 assert_eq!(id_stats.n_distinct, 50);
18971 }
18972
18973 #[test]
18974 fn reanalyze_overwrites_prior_stats() {
18975 let mut e = Engine::new();
18976 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18977 for i in 0..10 {
18978 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18979 .unwrap();
18980 }
18981 e.execute("ANALYZE t").unwrap();
18982 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
18983 assert_eq!(n1, 10);
18984 for i in 10..30 {
18985 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18986 .unwrap();
18987 }
18988 e.execute("ANALYZE t").unwrap();
18989 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
18990 assert_eq!(n2, 30);
18991 }
18992
18993 #[test]
18994 fn analyze_unknown_table_errors() {
18995 let mut e = Engine::new();
18996 let err = e.execute("ANALYZE nonexistent").unwrap_err();
18997 assert!(matches!(
18998 err,
18999 EngineError::Storage(StorageError::TableNotFound { .. })
19000 ));
19001 }
19002
19003 #[test]
19004 fn bare_analyze_covers_all_user_tables() {
19005 let mut e = Engine::new();
19006 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
19007 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
19008 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
19009 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
19010 let r = e.execute("ANALYZE").unwrap();
19011 match r {
19012 QueryResult::CommandOk {
19013 affected,
19014 modified_catalog,
19015 } => {
19016 assert_eq!(affected, 2);
19017 assert!(modified_catalog);
19018 }
19019 other => panic!("expected CommandOk, got {other:?}"),
19020 }
19021 assert!(e.statistics().get("t1", "id").is_some());
19022 assert!(e.statistics().get("t2", "name").is_some());
19023 }
19024
19025 #[test]
19026 fn select_from_spg_statistic_returns_rows_per_column() {
19027 let mut e = Engine::new();
19028 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19029 .unwrap();
19030 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
19031 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
19032 e.execute("ANALYZE t").unwrap();
19033 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
19034 let QueryResult::Rows { rows, columns } = r else {
19035 panic!()
19036 };
19037 assert_eq!(columns.len(), 6);
19039 assert_eq!(columns[0].name, "table_name");
19040 assert_eq!(columns[4].name, "histogram_bounds");
19041 assert_eq!(columns[5].name, "cold_row_count");
19042 assert_eq!(rows.len(), 2, "one row per column of t");
19043 match (&rows[0].values[0], &rows[0].values[1]) {
19045 (Value::Text(t), Value::Text(c)) => {
19046 assert_eq!(t, "t");
19047 assert_eq!(c, "id");
19049 }
19050 _ => panic!(),
19051 }
19052 }
19053
19054 #[test]
19055 fn analyze_skips_vector_columns() {
19056 let mut e = Engine::new();
19059 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
19060 .unwrap();
19061 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
19062 e.execute("ANALYZE t").unwrap();
19063 assert!(e.statistics().get("t", "id").is_some());
19064 assert!(e.statistics().get("t", "v").is_none());
19065 }
19066
19067 #[test]
19068 fn statistics_persist_across_envelope_v5_round_trip() {
19069 let mut e = Engine::new();
19070 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19071 for i in 0..20 {
19072 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19073 .unwrap();
19074 }
19075 e.execute("ANALYZE").unwrap();
19076 let snap = e.snapshot();
19077 let e2 = Engine::restore_envelope(&snap).unwrap();
19078 let s = e2.statistics().get("t", "id").unwrap();
19079 assert_eq!(s.n_distinct, 20);
19080 }
19081
19082 #[test]
19085 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
19086 let mut e = Engine::new();
19090 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19091 for i in 0..9 {
19092 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19093 .unwrap();
19094 }
19095 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
19096 e.execute("INSERT INTO t VALUES (9)").unwrap();
19097 let needs = e.tables_needing_analyze();
19098 assert_eq!(needs, alloc::vec!["t".to_string()]);
19099 }
19100
19101 #[test]
19102 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
19103 let mut e = Engine::new();
19109 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19110 for i in 0..1000 {
19111 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19112 .unwrap();
19113 }
19114 e.execute("ANALYZE t").unwrap();
19115 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
19116 for i in 1000..1050 {
19117 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19118 .unwrap();
19119 }
19120 assert!(
19121 e.tables_needing_analyze().is_empty(),
19122 "50 inserts < threshold of ~105"
19123 );
19124 for i in 1050..1200 {
19125 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19126 .unwrap();
19127 }
19128 assert_eq!(
19129 e.tables_needing_analyze(),
19130 alloc::vec!["t".to_string()],
19131 "200 inserts > 0.1 × 1200 threshold"
19132 );
19133 }
19134
19135 #[test]
19136 fn auto_analyze_threshold_resets_after_analyze() {
19137 let mut e = Engine::new();
19138 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
19139 for i in 0..200 {
19140 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
19141 .unwrap();
19142 }
19143 assert!(!e.tables_needing_analyze().is_empty());
19144 e.execute("ANALYZE").unwrap();
19145 assert!(
19146 e.tables_needing_analyze().is_empty(),
19147 "ANALYZE must reset the counter"
19148 );
19149 }
19150
19151 #[test]
19152 fn auto_analyze_threshold_tracks_updates_and_deletes() {
19153 let mut e = Engine::new();
19154 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
19155 .unwrap();
19156 for i in 0..50 {
19157 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
19158 .unwrap();
19159 }
19160 e.execute("ANALYZE t").unwrap();
19161 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
19164 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
19165 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
19166 }
19167
19168 #[test]
19169 fn v4_envelope_loads_with_empty_statistics() {
19170 let mut e = Engine::new();
19174 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19175 .unwrap();
19176 let catalog = e.catalog.serialize();
19177 let users = crate::users::serialize_users(&e.users);
19178 let pubs = e.publications.serialize();
19179 let subs = e.subscriptions.serialize();
19180 let mut buf = Vec::new();
19181 buf.extend_from_slice(b"SPGENV01");
19182 buf.push(4u8);
19183 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19184 buf.extend_from_slice(&catalog);
19185 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19186 buf.extend_from_slice(&users);
19187 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
19188 buf.extend_from_slice(&pubs);
19189 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
19190 buf.extend_from_slice(&subs);
19191 let crc = spg_crypto::crc32::crc32(&buf);
19192 buf.extend_from_slice(&crc.to_le_bytes());
19193 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
19194 assert!(e2.statistics().is_empty());
19195 }
19196
19197 #[test]
19198 fn v1_v2_envelope_loads_with_empty_publications() {
19199 let mut e = Engine::new();
19206 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
19209 .unwrap();
19210
19211 let catalog = e.catalog.serialize();
19213 let users = crate::users::serialize_users(&e.users);
19214 let mut buf = Vec::new();
19215 buf.extend_from_slice(b"SPGENV01");
19216 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
19218 buf.extend_from_slice(&catalog);
19219 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
19220 buf.extend_from_slice(&users);
19221 let crc = spg_crypto::crc32::crc32(&buf);
19222 buf.extend_from_slice(&crc.to_le_bytes());
19223
19224 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
19225 assert!(e2.publications().is_empty());
19226 }
19227}