1#![no_std]
6
7extern crate alloc;
8
9pub mod aggregate;
10pub mod describe;
11pub mod eval;
12pub mod fts;
13pub mod json;
14pub mod memoize;
15pub mod plan_cache;
16pub mod publications;
17pub mod query_stats;
18pub mod reorder;
19pub mod selectivity;
20pub mod statistics;
21pub mod subscriptions;
22pub mod triggers;
23pub mod users;
24
25pub use crate::users::{Role, ScramSecrets, UserError, UserStore};
26
27use alloc::borrow::Cow;
28use alloc::boxed::Box;
29use alloc::collections::BTreeMap;
30use alloc::string::{String, ToString};
31use alloc::vec::Vec;
32use core::fmt;
33
34use spg_sql::ast::{
35 BinOp, ColumnDef, ColumnName, ColumnTypeName, CreateIndexStatement, CreatePublicationStatement,
36 CreateSubscriptionStatement, CreateTableStatement, CreateUserStatement, Expr, FrameBound,
37 FrameKind, FromClause, IndexMethod, InsertStatement, JoinKind, Literal, OrderBy, SelectItem,
38 SelectStatement, Statement, TableRef, UnOp, UnionKind, VecEncoding as SqlVecEncoding,
39 WindowFrame,
40};
41pub use spg_sql::ast::Statement as ParsedStatement;
45use spg_sql::parser::{self, ParseError};
46use spg_storage::{
47 Catalog, ColumnSchema, CompactReport, DataType, IndexKey, IndexKind, Row, StorageError, Table,
48 TableSchema, Value, VecEncoding,
49};
50
51use crate::eval::{EvalContext, EvalError};
52
53#[derive(Debug, Clone, PartialEq)]
55#[non_exhaustive]
56pub enum QueryResult {
57 CommandOk {
66 affected: usize,
67 modified_catalog: bool,
68 },
69 Rows {
71 columns: Vec<ColumnSchema>,
72 rows: Vec<Row>,
73 },
74}
75
76#[derive(Debug, Clone, PartialEq)]
82#[non_exhaustive]
83pub enum EngineError {
84 Parse(ParseError),
85 Storage(StorageError),
86 Eval(EvalError),
87 Unsupported(String),
89 TransactionAlreadyOpen,
91 NoActiveTransaction,
93 WriteRequired,
98 RowLimitExceeded(usize),
101 Cancelled,
107}
108
109impl fmt::Display for EngineError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 Self::Parse(e) => write!(f, "parse: {e}"),
113 Self::Storage(e) => write!(f, "storage: {e}"),
114 Self::Eval(e) => write!(f, "eval: {e}"),
115 Self::Unsupported(s) => write!(f, "unsupported: {s}"),
116 Self::TransactionAlreadyOpen => f.write_str("a transaction is already open"),
117 Self::NoActiveTransaction => f.write_str("no active transaction"),
118 Self::WriteRequired => {
119 f.write_str("statement requires a write lock (use execute, not execute_readonly)")
120 }
121 Self::RowLimitExceeded(n) => {
122 write!(f, "query exceeded max_query_rows={n}")
123 }
124 Self::Cancelled => f.write_str("query cancelled (timeout or client request)"),
125 }
126 }
127}
128
129impl From<ParseError> for EngineError {
130 fn from(e: ParseError) -> Self {
131 Self::Parse(e)
132 }
133}
134impl From<StorageError> for EngineError {
135 fn from(e: StorageError) -> Self {
136 Self::Storage(e)
137 }
138}
139impl From<EvalError> for EngineError {
140 fn from(e: EvalError) -> Self {
141 Self::Eval(e)
142 }
143}
144
145pub type ClockFn = fn() -> i64;
154
155pub type SaltFn = fn() -> [u8; 16];
162
163pub type MonotonicNowFn = fn() -> u64;
179
180#[derive(Debug, Clone, Copy)]
181struct Deadline {
182 now_fn: MonotonicNowFn,
183 deadline_us: u64,
185}
186
187#[derive(Debug, Clone, Copy)]
188pub struct CancelToken<'a> {
189 flag: Option<&'a core::sync::atomic::AtomicBool>,
190 deadline: Option<Deadline>,
197}
198
199impl<'a> CancelToken<'a> {
200 #[must_use]
201 pub const fn none() -> Self {
202 Self {
203 flag: None,
204 deadline: None,
205 }
206 }
207
208 #[must_use]
209 pub const fn from_flag(f: &'a core::sync::atomic::AtomicBool) -> Self {
210 Self {
211 flag: Some(f),
212 deadline: None,
213 }
214 }
215
216 #[must_use]
224 pub const fn with_deadline(mut self, now_fn: MonotonicNowFn, deadline_us: u64) -> Self {
225 self.deadline = Some(Deadline {
226 now_fn,
227 deadline_us,
228 });
229 self
230 }
231
232 #[must_use]
233 pub fn is_cancelled(self) -> bool {
234 if self
235 .flag
236 .is_some_and(|f| f.load(core::sync::atomic::Ordering::Relaxed))
237 {
238 return true;
239 }
240 if let Some(d) = self.deadline
244 && (d.now_fn)() >= d.deadline_us
245 {
246 return true;
247 }
248 false
249 }
250
251 #[inline]
255 pub fn check(self) -> Result<(), EngineError> {
256 if self.is_cancelled() {
257 Err(EngineError::Cancelled)
258 } else {
259 Ok(())
260 }
261 }
262}
263
264const ENVELOPE_MAGIC: &[u8; 8] = b"SPGENV01";
322const ENVELOPE_VERSION_V1: u8 = 1;
323const ENVELOPE_VERSION_V2: u8 = 2;
324const ENVELOPE_VERSION_V3: u8 = 3;
325const ENVELOPE_VERSION_V4: u8 = 4;
326const ENVELOPE_VERSION_V5: u8 = 5;
327
328fn build_envelope(catalog: &[u8], users: &[u8], pubs: &[u8], subs: &[u8], stats: &[u8]) -> Vec<u8> {
329 let mut out = Vec::with_capacity(
330 8 + 1
331 + 4
332 + catalog.len()
333 + 4
334 + users.len()
335 + 4
336 + pubs.len()
337 + 4
338 + subs.len()
339 + 4
340 + stats.len()
341 + 4,
342 );
343 out.extend_from_slice(ENVELOPE_MAGIC);
344 out.push(ENVELOPE_VERSION_V5);
345 out.extend_from_slice(
346 &u32::try_from(catalog.len())
347 .expect("≤ 4G catalog")
348 .to_le_bytes(),
349 );
350 out.extend_from_slice(catalog);
351 out.extend_from_slice(
352 &u32::try_from(users.len())
353 .expect("≤ 4G users")
354 .to_le_bytes(),
355 );
356 out.extend_from_slice(users);
357 out.extend_from_slice(
358 &u32::try_from(pubs.len())
359 .expect("≤ 4G publications")
360 .to_le_bytes(),
361 );
362 out.extend_from_slice(pubs);
363 out.extend_from_slice(
364 &u32::try_from(subs.len())
365 .expect("≤ 4G subscriptions")
366 .to_le_bytes(),
367 );
368 out.extend_from_slice(subs);
369 out.extend_from_slice(
370 &u32::try_from(stats.len())
371 .expect("≤ 4G statistics")
372 .to_le_bytes(),
373 );
374 out.extend_from_slice(stats);
375 let crc = spg_crypto::crc32::crc32(&out);
376 out.extend_from_slice(&crc.to_le_bytes());
377 out
378}
379
380enum EnvelopeParse<'a> {
387 Bare,
388 Pair {
389 catalog: &'a [u8],
390 users: &'a [u8],
391 publications: Option<&'a [u8]>,
392 subscriptions: Option<&'a [u8]>,
393 statistics: Option<&'a [u8]>,
394 },
395 CrcMismatch {
396 expected: u32,
397 computed: u32,
398 },
399}
400
401fn split_envelope(buf: &[u8]) -> EnvelopeParse<'_> {
406 if buf.len() < 8 + 1 + 4 || &buf[..8] != ENVELOPE_MAGIC {
407 return EnvelopeParse::Bare;
408 }
409 let version = buf[8];
410 if !matches!(
411 version,
412 ENVELOPE_VERSION_V1
413 | ENVELOPE_VERSION_V2
414 | ENVELOPE_VERSION_V3
415 | ENVELOPE_VERSION_V4
416 | ENVELOPE_VERSION_V5
417 ) {
418 return EnvelopeParse::Bare;
419 }
420 let mut p = 9usize;
421 let Some(cat_len_bytes) = buf.get(p..p + 4) else {
422 return EnvelopeParse::Bare;
423 };
424 let Ok(cat_len_arr) = cat_len_bytes.try_into() else {
425 return EnvelopeParse::Bare;
426 };
427 let cat_len = u32::from_le_bytes(cat_len_arr) as usize;
428 p += 4;
429 if p + cat_len + 4 > buf.len() {
430 return EnvelopeParse::Bare;
431 }
432 let catalog = &buf[p..p + cat_len];
433 p += cat_len;
434 let Some(user_len_bytes) = buf.get(p..p + 4) else {
435 return EnvelopeParse::Bare;
436 };
437 let Ok(user_len_arr) = user_len_bytes.try_into() else {
438 return EnvelopeParse::Bare;
439 };
440 let user_len = u32::from_le_bytes(user_len_arr) as usize;
441 p += 4;
442 if p + user_len > buf.len() {
443 return EnvelopeParse::Bare;
444 }
445 let users = &buf[p..p + user_len];
446 p += user_len;
447 let publications = if matches!(
448 version,
449 ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
450 ) {
451 let Some(pubs_len_bytes) = buf.get(p..p + 4) else {
453 return EnvelopeParse::Bare;
454 };
455 let Ok(pubs_len_arr) = pubs_len_bytes.try_into() else {
456 return EnvelopeParse::Bare;
457 };
458 let pubs_len = u32::from_le_bytes(pubs_len_arr) as usize;
459 p += 4;
460 if p + pubs_len > buf.len() {
461 return EnvelopeParse::Bare;
462 }
463 let pubs_slice = &buf[p..p + pubs_len];
464 p += pubs_len;
465 Some(pubs_slice)
466 } else {
467 None
468 };
469 let subscriptions = if matches!(version, ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5) {
470 let Some(subs_len_bytes) = buf.get(p..p + 4) else {
472 return EnvelopeParse::Bare;
473 };
474 let Ok(subs_len_arr) = subs_len_bytes.try_into() else {
475 return EnvelopeParse::Bare;
476 };
477 let subs_len = u32::from_le_bytes(subs_len_arr) as usize;
478 p += 4;
479 if p + subs_len > buf.len() {
480 return EnvelopeParse::Bare;
481 }
482 let subs_slice = &buf[p..p + subs_len];
483 p += subs_len;
484 Some(subs_slice)
485 } else {
486 None
487 };
488 let statistics = if version == ENVELOPE_VERSION_V5 {
489 let Some(stats_len_bytes) = buf.get(p..p + 4) else {
491 return EnvelopeParse::Bare;
492 };
493 let Ok(stats_len_arr) = stats_len_bytes.try_into() else {
494 return EnvelopeParse::Bare;
495 };
496 let stats_len = u32::from_le_bytes(stats_len_arr) as usize;
497 p += 4;
498 if p + stats_len > buf.len() {
499 return EnvelopeParse::Bare;
500 }
501 let stats_slice = &buf[p..p + stats_len];
502 p += stats_len;
503 Some(stats_slice)
504 } else {
505 None
506 };
507 if matches!(
508 version,
509 ENVELOPE_VERSION_V2 | ENVELOPE_VERSION_V3 | ENVELOPE_VERSION_V4 | ENVELOPE_VERSION_V5
510 ) {
511 if p + 4 != buf.len() {
512 return EnvelopeParse::Bare;
513 }
514 let Ok(crc_arr) = buf[p..p + 4].try_into() else {
515 return EnvelopeParse::Bare;
516 };
517 let expected = u32::from_le_bytes(crc_arr);
518 let computed = spg_crypto::crc32::crc32(&buf[..p]);
519 if expected != computed {
520 return EnvelopeParse::CrcMismatch { expected, computed };
521 }
522 } else if p != buf.len() {
523 return EnvelopeParse::Bare;
525 }
526 EnvelopeParse::Pair {
527 catalog,
528 users,
529 publications,
530 subscriptions,
531 statistics,
532 }
533}
534
535#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
545pub struct TxId(pub u64);
546
547pub const IMPLICIT_TX: TxId = TxId(0);
550
551pub const COMPACTION_TARGET_DEFAULT_BYTES: u64 = 4 * 1024 * 1024;
557
558#[derive(Debug, Default, Clone)]
563struct TxState {
564 catalog: Catalog,
569 savepoints: Vec<(String, Catalog)>,
575}
576
577#[derive(Debug, Clone)]
591pub struct CatalogSnapshot {
592 catalog: Catalog,
593 statistics: statistics::Statistics,
594 clock: Option<ClockFn>,
595 max_query_rows: Option<usize>,
596}
597
598#[derive(Debug, Default)]
599pub struct Engine {
600 catalog: Catalog,
603 tx_catalogs: BTreeMap<TxId, TxState>,
608 current_tx: Option<TxId>,
613 next_tx_id: u64,
616 clock: Option<ClockFn>,
619 salt_fn: Option<SaltFn>,
623 max_query_rows: Option<usize>,
629 users: UserStore,
635 publications: publications::Publications,
639 subscriptions: subscriptions::Subscriptions,
643 statistics: statistics::Statistics,
647 plan_cache: plan_cache::PlanCache,
651 query_stats: query_stats::QueryStats,
655 activity_provider: Option<ActivityProvider>,
662 audit_chain_provider: Option<AuditChainProvider>,
667 audit_verifier: Option<AuditVerifier>,
668 slow_query_threshold_us: Option<u64>,
674 slow_query_logger: Option<SlowQueryLogger>,
675 session_params: BTreeMap<String, String>,
682 trigger_recursion_depth: u32,
690 foreign_key_checks: bool,
699 meta_views_materialised: bool,
708 pending_foreign_keys: Vec<(alloc::string::String, spg_sql::ast::ForeignKeyConstraint)>,
709}
710
711const MAX_TRIGGER_RECURSION: u32 = 16;
715
716pub type SlowQueryLogger = fn(&str, u64);
720
721fn render_create_table(name: &str, columns: &[ColumnSchema]) -> String {
726 let mut out = alloc::format!("CREATE TABLE {name} (");
727 for (i, col) in columns.iter().enumerate() {
728 if i > 0 {
729 out.push_str(", ");
730 }
731 out.push_str(&col.name);
732 out.push(' ');
733 out.push_str(&render_data_type(col.ty));
734 if !col.nullable {
735 out.push_str(" NOT NULL");
736 }
737 if col.auto_increment {
738 out.push_str(" AUTO_INCREMENT");
739 }
740 }
741 out.push(')');
742 out
743}
744
745fn render_data_type(ty: DataType) -> String {
746 match ty {
747 DataType::SmallInt => "SMALLINT".into(),
748 DataType::Int => "INT".into(),
749 DataType::BigInt => "BIGINT".into(),
750 DataType::Float => "FLOAT".into(),
751 DataType::Text => "TEXT".into(),
752 DataType::Varchar(n) => alloc::format!("VARCHAR({n})"),
753 DataType::Char(n) => alloc::format!("CHAR({n})"),
754 DataType::Bool => "BOOL".into(),
755 DataType::Vector { dim, encoding } => match encoding {
756 spg_storage::VecEncoding::F32 => alloc::format!("VECTOR({dim})"),
757 spg_storage::VecEncoding::Sq8 => alloc::format!("VECTOR({dim}) USING SQ8"),
758 spg_storage::VecEncoding::F16 => alloc::format!("VECTOR({dim}) USING HALF"),
759 },
760 DataType::Numeric { precision, scale } => {
761 alloc::format!("NUMERIC({precision},{scale})")
762 }
763 DataType::Date => "DATE".into(),
764 DataType::Timestamp => "TIMESTAMP".into(),
765 DataType::Interval => "INTERVAL".into(),
766 DataType::Json => "JSON".into(),
767 DataType::Jsonb => "JSONB".into(),
768 DataType::Timestamptz => "TIMESTAMPTZ".into(),
769 DataType::Bytes => "BYTEA".into(),
770 DataType::TextArray => "TEXT[]".into(),
771 DataType::IntArray => "INT[]".into(),
772 DataType::BigIntArray => "BIGINT[]".into(),
773 DataType::TsVector => "TSVECTOR".into(),
774 DataType::TsQuery => "TSQUERY".into(),
775 DataType::Uuid => "UUID".into(),
776 DataType::Time => "TIME".into(),
777 DataType::Year => "YEAR".into(),
778 DataType::TimeTz => "TIMETZ".into(),
779 DataType::Money => "MONEY".into(),
780 DataType::Range(k) => k.keyword().into(),
781 DataType::Hstore => "HSTORE".into(),
782 DataType::IntArray2D => "INT[][]".into(),
783 DataType::BigIntArray2D => "BIGINT[][]".into(),
784 DataType::TextArray2D => "TEXT[][]".into(),
785 }
786}
787
788#[derive(Debug, Clone)]
792pub struct ActivityRow {
793 pub pid: u32,
794 pub user: String,
795 pub started_at_us: i64,
796 pub current_sql: String,
797 pub wait_event: String,
798 pub elapsed_us: i64,
799 pub in_transaction: bool,
800 pub application_name: String,
804}
805
806pub type ActivityProvider = fn() -> Vec<ActivityRow>;
809
810#[derive(Debug, Clone)]
813pub struct AuditRow {
814 pub seq: i64,
815 pub ts_ms: i64,
816 pub prev_hash_hex: String,
817 pub entry_hash_hex: String,
818 pub sql: String,
819}
820
821pub type AuditChainProvider = fn() -> Vec<AuditRow>;
826pub type AuditVerifier = fn() -> (i64, i64);
827
828impl Engine {
829 pub fn new() -> Self {
830 Self {
831 catalog: Catalog::new(),
832 tx_catalogs: BTreeMap::new(),
833 current_tx: None,
834 next_tx_id: 1,
835 clock: None,
836 salt_fn: None,
837 max_query_rows: None,
838 users: UserStore::new(),
839 publications: publications::Publications::new(),
840 subscriptions: subscriptions::Subscriptions::new(),
841 statistics: statistics::Statistics::new(),
842 plan_cache: plan_cache::PlanCache::new(),
843 query_stats: query_stats::QueryStats::new(),
844 activity_provider: None,
845 audit_chain_provider: None,
846 audit_verifier: None,
847 slow_query_threshold_us: None,
848 slow_query_logger: None,
849 session_params: BTreeMap::new(),
850 trigger_recursion_depth: 0,
851 foreign_key_checks: true,
852 meta_views_materialised: false,
853 pending_foreign_keys: Vec::new(),
854 }
855 }
856
857 #[must_use]
866 pub fn clone_snapshot(&self) -> CatalogSnapshot {
867 CatalogSnapshot {
868 catalog: self.active_catalog().clone(),
869 statistics: self.statistics.clone(),
870 clock: self.clock,
871 max_query_rows: self.max_query_rows,
872 }
873 }
874
875 pub fn execute_readonly_on_snapshot(
883 snapshot: &CatalogSnapshot,
884 sql: &str,
885 ) -> Result<QueryResult, EngineError> {
886 Self::execute_readonly_on_snapshot_with_cancel(snapshot, sql, CancelToken::none())
887 }
888
889 pub fn execute_readonly_on_snapshot_with_cancel(
896 snapshot: &CatalogSnapshot,
897 sql: &str,
898 cancel: CancelToken<'_>,
899 ) -> Result<QueryResult, EngineError> {
900 let transient = Engine {
901 catalog: snapshot.catalog.clone(),
902 statistics: snapshot.statistics.clone(),
903 clock: snapshot.clock,
904 max_query_rows: snapshot.max_query_rows,
905 ..Engine::default()
906 };
907 transient.execute_readonly_with_cancel(sql, cancel)
908 }
909
910 pub fn execute_readonly_prepared_on_snapshot(
926 snapshot: &CatalogSnapshot,
927 stmt: Statement,
928 params: &[Value],
929 ) -> Result<QueryResult, EngineError> {
930 Self::execute_readonly_prepared_on_snapshot_with_cancel(
931 snapshot,
932 stmt,
933 params,
934 CancelToken::none(),
935 )
936 }
937
938 pub fn execute_readonly_prepared_on_snapshot_with_cancel(
941 snapshot: &CatalogSnapshot,
942 mut stmt: Statement,
943 params: &[Value],
944 cancel: CancelToken<'_>,
945 ) -> Result<QueryResult, EngineError> {
946 cancel.check()?;
947 substitute_placeholders(&mut stmt, params)?;
948 let transient = Engine {
949 catalog: snapshot.catalog.clone(),
950 statistics: snapshot.statistics.clone(),
951 clock: snapshot.clock,
952 max_query_rows: snapshot.max_query_rows,
953 ..Engine::default()
954 };
955 transient.execute_readonly_stmt_with_cancel(stmt, cancel)
956 }
957
958 pub fn describe_prepared_on_snapshot(
964 snapshot: &CatalogSnapshot,
965 stmt: &Statement,
966 ) -> (Vec<u32>, Vec<ColumnSchema>) {
967 describe::describe_prepared(stmt, &snapshot.catalog)
968 }
969
970 #[must_use]
978 pub fn is_readonly_sql(sql: &str) -> bool {
979 parser::parse_statement(sql)
980 .as_ref()
981 .map(spg_sql::ast::Statement::is_readonly)
982 .unwrap_or(false)
983 }
984
985 pub fn prepare_on_snapshot(
1000 snapshot: &CatalogSnapshot,
1001 sql: &str,
1002 ) -> Result<Statement, ParseError> {
1003 let mut stmt = parser::parse_statement(sql)?;
1004 let now_micros = snapshot.clock.map(|f| f());
1005 rewrite_clock_calls(&mut stmt, now_micros);
1006 if let Statement::Select(s) = &mut stmt {
1007 expand_group_by_all(s);
1008 resolve_order_by_position(s);
1009 reorder::reorder_joins(s, &snapshot.catalog, &snapshot.statistics);
1010 }
1011 Ok(stmt)
1012 }
1013
1014 pub fn restore(catalog: Catalog) -> Self {
1017 Self {
1018 catalog,
1019 tx_catalogs: BTreeMap::new(),
1020 current_tx: None,
1021 next_tx_id: 1,
1022 clock: None,
1023 salt_fn: None,
1024 max_query_rows: None,
1025 users: UserStore::new(),
1026 publications: publications::Publications::new(),
1027 subscriptions: subscriptions::Subscriptions::new(),
1028 statistics: statistics::Statistics::new(),
1029 plan_cache: plan_cache::PlanCache::new(),
1030 query_stats: query_stats::QueryStats::new(),
1031 activity_provider: None,
1032 audit_chain_provider: None,
1033 audit_verifier: None,
1034 slow_query_threshold_us: None,
1035 slow_query_logger: None,
1036 session_params: BTreeMap::new(),
1037 trigger_recursion_depth: 0,
1038 foreign_key_checks: true,
1039 meta_views_materialised: false,
1040 pending_foreign_keys: Vec::new(),
1041 }
1042 }
1043
1044 pub fn restore_envelope(buf: &[u8]) -> Result<Self, EngineError> {
1051 match split_envelope(buf) {
1052 EnvelopeParse::Pair {
1053 catalog: catalog_bytes,
1054 users: user_bytes,
1055 publications: pub_bytes,
1056 subscriptions: sub_bytes,
1057 statistics: stats_bytes,
1058 } => {
1059 let catalog = Catalog::deserialize(catalog_bytes).map_err(EngineError::Storage)?;
1060 let users = users::deserialize_users(user_bytes)
1061 .map_err(|e| EngineError::Unsupported(alloc::format!("users restore: {e}")))?;
1062 let publications = match pub_bytes {
1063 Some(b) => publications::Publications::deserialize(b).map_err(|e| {
1064 EngineError::Unsupported(alloc::format!("publications restore: {e:?}"))
1065 })?,
1066 None => publications::Publications::new(),
1067 };
1068 let subscriptions = match sub_bytes {
1069 Some(b) => subscriptions::Subscriptions::deserialize(b).map_err(|e| {
1070 EngineError::Unsupported(alloc::format!("subscriptions restore: {e:?}"))
1071 })?,
1072 None => subscriptions::Subscriptions::new(),
1073 };
1074 let statistics = match stats_bytes {
1075 Some(b) => statistics::Statistics::deserialize(b).map_err(|e| {
1076 EngineError::Unsupported(alloc::format!("statistics restore: {e:?}"))
1077 })?,
1078 None => statistics::Statistics::new(),
1079 };
1080 Ok(Self {
1081 catalog,
1082 tx_catalogs: BTreeMap::new(),
1083 current_tx: None,
1084 next_tx_id: 1,
1085 clock: None,
1086 salt_fn: None,
1087 max_query_rows: None,
1088 users,
1089 publications,
1090 subscriptions,
1091 statistics,
1092 plan_cache: plan_cache::PlanCache::new(),
1093 query_stats: query_stats::QueryStats::new(),
1094 activity_provider: None,
1095 audit_chain_provider: None,
1096 audit_verifier: None,
1097 slow_query_threshold_us: None,
1098 slow_query_logger: None,
1099 session_params: BTreeMap::new(),
1100 trigger_recursion_depth: 0,
1101 foreign_key_checks: true,
1102 meta_views_materialised: false,
1103 pending_foreign_keys: Vec::new(),
1104 })
1105 }
1106 EnvelopeParse::CrcMismatch { expected, computed } => {
1107 Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1108 "snapshot envelope CRC32 mismatch (expected={expected:#010x}, computed={computed:#010x})"
1109 ))))
1110 }
1111 EnvelopeParse::Bare => {
1112 let catalog = Catalog::deserialize(buf).map_err(EngineError::Storage)?;
1113 Ok(Self::restore(catalog))
1114 }
1115 }
1116 }
1117
1118 pub const fn users(&self) -> &UserStore {
1119 &self.users
1120 }
1121
1122 pub fn create_user(
1126 &mut self,
1127 name: &str,
1128 password: &str,
1129 role: Role,
1130 salt: [u8; 16],
1131 ) -> Result<(), UserError> {
1132 self.users.create(name, password, role, salt)?;
1133 let scram_salt = self.salt_fn.map_or_else(
1139 || {
1140 let mut s = [0u8; users::SCRAM_SALT_LEN];
1141 let digest = spg_crypto::hash(name.as_bytes());
1142 s.copy_from_slice(&digest[16..32]);
1145 s
1146 },
1147 |f| f(),
1148 );
1149 self.users
1150 .enable_scram(name, password, scram_salt, users::SCRAM_DEFAULT_ITERS)?;
1151 Ok(())
1152 }
1153
1154 pub fn drop_user(&mut self, name: &str) -> Result<(), UserError> {
1155 self.users.drop(name)
1156 }
1157
1158 pub fn verify_user(&self, name: &str, password: &str) -> Option<Role> {
1159 self.users.verify(name, password)
1160 }
1161
1162 #[must_use]
1165 pub const fn with_clock(mut self, clock: ClockFn) -> Self {
1166 self.clock = Some(clock);
1167 self
1168 }
1169
1170 #[must_use]
1173 pub const fn with_salt_fn(mut self, f: SaltFn) -> Self {
1174 self.salt_fn = Some(f);
1175 self
1176 }
1177
1178 #[must_use]
1184 pub const fn with_max_query_rows(mut self, n: usize) -> Self {
1185 self.max_query_rows = Some(n);
1186 self
1187 }
1188
1189 pub const fn catalog(&self) -> &Catalog {
1193 &self.catalog
1194 }
1195
1196 pub fn snapshot(&self) -> Vec<u8> {
1204 if self.users.is_empty()
1205 && self.publications.is_empty()
1206 && self.subscriptions.is_empty()
1207 && self.statistics.is_empty()
1208 {
1209 self.catalog.serialize()
1210 } else {
1211 build_envelope(
1212 &self.catalog.serialize(),
1213 &users::serialize_users(&self.users),
1214 &self.publications.serialize(),
1215 &self.subscriptions.serialize(),
1216 &self.statistics.serialize(),
1217 )
1218 }
1219 }
1220
1221 pub fn in_transaction(&self) -> bool {
1226 !self.tx_catalogs.is_empty()
1227 }
1228
1229 pub fn alloc_tx_id(&mut self) -> TxId {
1238 let id = TxId(self.next_tx_id);
1239 self.next_tx_id = self.next_tx_id.saturating_add(1);
1240 id
1241 }
1242
1243 pub fn replace_catalog(&mut self, catalog: Catalog) {
1263 self.catalog = catalog;
1264 }
1265
1266 pub fn freeze_oldest_to_cold(
1274 &mut self,
1275 table_name: &str,
1276 index_name: &str,
1277 max_rows: usize,
1278 ) -> Result<spg_storage::FreezeReport, EngineError> {
1279 let report = self
1280 .active_catalog_mut()
1281 .freeze_oldest_to_cold(table_name, index_name, max_rows)
1282 .map_err(EngineError::Storage)?;
1283 if let Some(t) = self.active_catalog_mut().get_mut(table_name) {
1284 t.mark_cold_row_count_stale();
1285 }
1286 Ok(report)
1287 }
1288
1289 pub fn receive_cold_segment(
1303 &mut self,
1304 segment_id: u32,
1305 bytes: Vec<u8>,
1306 ) -> Result<(), EngineError> {
1307 let mut new_cat = self.catalog.clone();
1308 match new_cat.load_segment_bytes_at(segment_id, bytes) {
1309 Ok(()) => {
1310 self.replace_catalog(new_cat);
1311 Ok(())
1312 }
1313 Err(StorageError::Corrupt(msg)) if msg.contains("already occupied") => Ok(()),
1314 Err(e) => Err(EngineError::Storage(e)),
1315 }
1316 }
1317
1318 pub fn compact_cold_segments_with_target(
1332 &mut self,
1333 target_segment_bytes: u64,
1334 ) -> Result<Vec<(String, String, CompactReport)>, EngineError> {
1335 let table_names = self.active_catalog().table_names();
1336 let mut reports: Vec<(String, String, CompactReport)> = Vec::new();
1337 for tname in table_names {
1338 if is_internal_table_name(&tname) {
1339 continue;
1340 }
1341 let idx_names: Vec<String> = {
1342 let Some(t) = self.active_catalog().get(&tname) else {
1343 continue;
1344 };
1345 t.indices()
1346 .iter()
1347 .filter(|i| matches!(i.kind, IndexKind::BTree(_)))
1348 .map(|i| i.name.clone())
1349 .collect()
1350 };
1351 for iname in idx_names {
1352 let report = self
1353 .active_catalog_mut()
1354 .compact_cold_segments(&tname, &iname, target_segment_bytes)
1355 .map_err(EngineError::Storage)?;
1356 if report.merged_segment_id.is_some() {
1357 if let Some(t) = self.active_catalog_mut().get_mut(&tname) {
1358 t.mark_cold_row_count_stale();
1359 }
1360 reports.push((tname.clone(), iname, report));
1361 }
1362 }
1363 }
1364 Ok(reports)
1365 }
1366
1367 fn active_catalog(&self) -> &Catalog {
1368 match self.current_tx {
1369 Some(t) => self
1370 .tx_catalogs
1371 .get(&t)
1372 .map_or(&self.catalog, |s| &s.catalog),
1373 None => &self.catalog,
1374 }
1375 }
1376
1377 fn resolve_plpgsql_block_subqueries(
1399 &self,
1400 block: &mut spg_sql::ast::PlPgSqlBlock,
1401 cancel: CancelToken<'_>,
1402 ) -> Result<(), EngineError> {
1403 for d in &mut block.declarations {
1404 if let Some(e) = &mut d.default {
1405 self.resolve_expr_subqueries(e, cancel)?;
1406 }
1407 }
1408 self.resolve_plpgsql_stmts_subqueries(&mut block.statements, cancel)
1409 }
1410
1411 fn resolve_plpgsql_stmts_subqueries(
1412 &self,
1413 stmts: &mut [spg_sql::ast::PlPgSqlStmt],
1414 cancel: CancelToken<'_>,
1415 ) -> Result<(), EngineError> {
1416 use spg_sql::ast::PlPgSqlStmt;
1417 for stmt in stmts {
1418 match stmt {
1419 PlPgSqlStmt::Assign { value, .. } => {
1420 self.resolve_expr_subqueries(value, cancel)?;
1421 }
1422 PlPgSqlStmt::Return(spg_sql::ast::ReturnTarget::Expr(e)) => {
1423 self.resolve_expr_subqueries(e, cancel)?;
1424 }
1425 PlPgSqlStmt::Return(_) => {}
1426 PlPgSqlStmt::If {
1427 branches,
1428 else_branch,
1429 } => {
1430 for (cond, body) in branches.iter_mut() {
1431 self.resolve_expr_subqueries(cond, cancel)?;
1432 self.resolve_plpgsql_stmts_subqueries(body, cancel)?;
1433 }
1434 self.resolve_plpgsql_stmts_subqueries(else_branch, cancel)?;
1435 }
1436 PlPgSqlStmt::Raise { args, .. } => {
1437 for a in args {
1438 self.resolve_expr_subqueries(a, cancel)?;
1439 }
1440 }
1441 PlPgSqlStmt::EmbeddedSql(_) => {
1442 }
1446 PlPgSqlStmt::SelectInto { body, .. } => {
1447 self.resolve_select_subqueries(body, cancel)?;
1453 }
1454 }
1455 }
1456 Ok(())
1457 }
1458
1459 fn exec_do_block(
1460 &mut self,
1461 body: spg_sql::ast::PlPgSqlBlock,
1462 ) -> Result<QueryResult, EngineError> {
1463 let mut body = body;
1472 self.resolve_plpgsql_block_subqueries(&mut body, CancelToken::none())?;
1473 let dts = self
1474 .session_param("default_text_search_config")
1475 .map(String::from);
1476 let engine_cell = core::cell::RefCell::new(&mut *self);
1489 let resolver_fn =
1490 |stmt: &spg_sql::ast::Statement| -> Result<Value, triggers::TriggerError> {
1491 let mut eng = engine_cell.borrow_mut();
1492 let r = eng
1493 .execute_stmt_with_cancel(stmt.clone(), CancelToken::none())
1494 .map_err(|e| triggers::TriggerError::EvalFailed {
1495 function: "DO".into(),
1496 cause: eval::EvalError::TypeMismatch {
1497 detail: alloc::format!("SELECT … INTO failed: {e}"),
1498 },
1499 })?;
1500 match r {
1501 QueryResult::Rows { rows, .. } => match rows.into_iter().next() {
1502 Some(row) => Ok(row.values.into_iter().next().unwrap_or(Value::Null)),
1503 None => Ok(Value::Null),
1504 },
1505 _ => Err(triggers::TriggerError::EvalFailed {
1506 function: "DO".into(),
1507 cause: eval::EvalError::TypeMismatch {
1508 detail: "SELECT … INTO body must be a SELECT".into(),
1509 },
1510 }),
1511 }
1512 };
1513 let collected =
1514 triggers::execute_do_block_top_level(&body, dts.as_deref(), Some(&resolver_fn))
1515 .map_err(|e| {
1516 EngineError::Storage(StorageError::Corrupt(alloc::format!("DO: {e}")))
1517 })?;
1518 for stmt in collected {
1524 self.execute_stmt_with_cancel(stmt, CancelToken::none())?;
1528 }
1529 Ok(QueryResult::CommandOk {
1530 affected: 0,
1531 modified_catalog: !self.in_transaction(),
1532 })
1533 }
1534
1535 fn snapshot_row_triggers(
1536 &self,
1537 table: &str,
1538 event: &str,
1539 timing: &str,
1540 ) -> Vec<spg_storage::FunctionDef> {
1541 let cat = self.active_catalog();
1542 cat.triggers()
1543 .iter()
1544 .filter(|t| {
1545 t.enabled
1548 && t.table == table
1549 && t.timing.eq_ignore_ascii_case(timing)
1550 && t.for_each.eq_ignore_ascii_case("row")
1551 && t.events.iter().any(|e| e.eq_ignore_ascii_case(event))
1552 })
1553 .filter_map(|t| cat.functions().get(&t.function).cloned())
1554 .collect()
1555 }
1556
1557 fn snapshot_update_row_triggers(
1562 &self,
1563 table: &str,
1564 timing: &str,
1565 ) -> Vec<(spg_storage::FunctionDef, Vec<String>)> {
1566 let cat = self.active_catalog();
1567 cat.triggers()
1568 .iter()
1569 .filter(|t| {
1570 t.enabled
1572 && t.table == table
1573 && t.timing.eq_ignore_ascii_case(timing)
1574 && t.for_each.eq_ignore_ascii_case("row")
1575 && t.events.iter().any(|e| e.eq_ignore_ascii_case("UPDATE"))
1576 })
1577 .filter_map(|t| {
1578 cat.functions()
1579 .get(&t.function)
1580 .cloned()
1581 .map(|fd| (fd, t.update_columns.clone()))
1582 })
1583 .collect()
1584 }
1585
1586 fn execute_deferred_trigger_stmts(
1595 &mut self,
1596 deferred: Vec<triggers::DeferredEmbeddedStmt>,
1597 cancel: CancelToken<'_>,
1598 ) -> Result<(), EngineError> {
1599 for d in deferred {
1600 if self.trigger_recursion_depth >= MAX_TRIGGER_RECURSION {
1601 return Err(EngineError::Storage(StorageError::Corrupt(alloc::format!(
1602 "trigger embedded SQL recursion depth {} exceeded (trigger function \
1603 {:?} would push past the {} cap — check for trigger cycles)",
1604 self.trigger_recursion_depth,
1605 d.function,
1606 MAX_TRIGGER_RECURSION,
1607 ))));
1608 }
1609 self.trigger_recursion_depth += 1;
1610 let res = self.execute_stmt_with_cancel(d.stmt, cancel);
1611 self.trigger_recursion_depth -= 1;
1612 res?;
1613 }
1614 Ok(())
1615 }
1616
1617 fn active_catalog_mut(&mut self) -> &mut Catalog {
1618 let tx = self.current_tx;
1619 match tx {
1620 Some(t) => match self.tx_catalogs.get_mut(&t) {
1621 Some(s) => &mut s.catalog,
1622 None => &mut self.catalog,
1623 },
1624 None => &mut self.catalog,
1625 }
1626 }
1627
1628 pub fn execute_readonly(&self, sql: &str) -> Result<QueryResult, EngineError> {
1640 self.execute_readonly_with_cancel(sql, CancelToken::none())
1641 }
1642
1643 pub fn execute_readonly_with_cancel(
1649 &self,
1650 sql: &str,
1651 cancel: CancelToken<'_>,
1652 ) -> Result<QueryResult, EngineError> {
1653 cancel.check()?;
1654 let mut stmt = parser::parse_statement(sql)?;
1655 let now_micros = self.clock.map(|f| f());
1656 rewrite_clock_calls(&mut stmt, now_micros);
1657 if let Statement::Select(s) = &mut stmt {
1658 resolve_order_by_position(s);
1659 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1661 }
1662 self.execute_readonly_stmt_with_cancel(stmt, cancel)
1663 }
1664
1665 fn execute_readonly_stmt_with_cancel(
1675 &self,
1676 stmt: Statement,
1677 cancel: CancelToken<'_>,
1678 ) -> Result<QueryResult, EngineError> {
1679 let result = match stmt {
1680 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1681 Statement::ShowTables => Ok(self.exec_show_tables()),
1682 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1683 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1684 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1685 Statement::ShowStatus => Ok(self.exec_show_status()),
1686 Statement::ShowVariables => Ok(self.exec_show_variables()),
1687 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1688 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1689 Statement::ShowUsers => Ok(self.exec_show_users()),
1690 Statement::ShowPublications => Ok(self.exec_show_publications()),
1691 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1692 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
1693 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
1694 )),
1695 Statement::Explain(e) => self.exec_explain(&e, cancel),
1696 _ => Err(EngineError::WriteRequired),
1697 };
1698 self.enforce_row_limit(result)
1699 }
1700
1701 fn enforce_row_limit(
1705 &self,
1706 result: Result<QueryResult, EngineError>,
1707 ) -> Result<QueryResult, EngineError> {
1708 if let (Ok(QueryResult::Rows { rows, .. }), Some(cap)) = (&result, self.max_query_rows)
1709 && rows.len() > cap
1710 {
1711 return Err(EngineError::RowLimitExceeded(cap));
1712 }
1713 result
1714 }
1715
1716 pub fn execute(&mut self, sql: &str) -> Result<QueryResult, EngineError> {
1717 self.execute_in_with_cancel(sql, IMPLICIT_TX, CancelToken::none())
1718 }
1719
1720 pub fn execute_with_cancel(
1725 &mut self,
1726 sql: &str,
1727 cancel: CancelToken<'_>,
1728 ) -> Result<QueryResult, EngineError> {
1729 self.execute_in_with_cancel(sql, IMPLICIT_TX, cancel)
1730 }
1731
1732 pub fn execute_in(&mut self, sql: &str, tx_id: TxId) -> Result<QueryResult, EngineError> {
1739 self.execute_in_with_cancel(sql, tx_id, CancelToken::none())
1740 }
1741
1742 pub fn execute_in_with_cancel(
1748 &mut self,
1749 sql: &str,
1750 tx_id: TxId,
1751 cancel: CancelToken<'_>,
1752 ) -> Result<QueryResult, EngineError> {
1753 let saved = self.current_tx;
1754 self.current_tx = Some(tx_id);
1755 let result = self.execute_inner_with_cancel(sql, cancel);
1756 self.current_tx = saved;
1757 result
1758 }
1759
1760 pub fn prepare(&self, sql: &str) -> Result<Statement, ParseError> {
1772 let mut stmt = parser::parse_statement(sql)?;
1773 let now_micros = self.clock.map(|f| f());
1774 rewrite_clock_calls(&mut stmt, now_micros);
1775 if let Statement::Select(s) = &mut stmt {
1776 expand_group_by_all(s);
1780 resolve_order_by_position(s);
1781 reorder::reorder_joins(s, &self.catalog, &self.statistics);
1784 }
1785 Ok(stmt)
1786 }
1787
1788 pub fn prepare_cached(&mut self, sql: &str) -> Result<Statement, ParseError> {
1800 let current_version = self.statistics.version();
1803 if let Some(plan) = self.plan_cache.get(sql) {
1804 if plan.statistics_version == current_version {
1805 return Ok(plan.stmt.clone());
1806 }
1807 }
1809 self.plan_cache.evict(sql);
1810 let stmt = self.prepare(sql)?;
1811 let source_tables = plan_cache::collect_source_tables(&stmt);
1812 let plan = plan_cache::PreparedPlan {
1813 stmt: stmt.clone(),
1814 statistics_version: current_version,
1815 source_tables,
1816 describe_columns: alloc::vec::Vec::new(),
1817 };
1818 self.plan_cache.insert(String::from(sql), plan);
1819 Ok(stmt)
1820 }
1821
1822 pub fn plan_cache(&self) -> &plan_cache::PlanCache {
1824 &self.plan_cache
1825 }
1826
1827 pub fn plan_cache_mut(&mut self) -> &mut plan_cache::PlanCache {
1829 &mut self.plan_cache
1830 }
1831
1832 pub fn describe_prepared(&self, stmt: &Statement) -> (Vec<u32>, Vec<ColumnSchema>) {
1838 describe::describe_prepared(stmt, self.active_catalog())
1839 }
1840
1841 pub fn execute_prepared(
1851 &mut self,
1852 stmt: Statement,
1853 params: &[Value],
1854 ) -> Result<QueryResult, EngineError> {
1855 self.execute_prepared_with_cancel(stmt, params, CancelToken::none())
1856 }
1857
1858 pub fn execute_prepared_with_cancel(
1863 &mut self,
1864 mut stmt: Statement,
1865 params: &[Value],
1866 cancel: CancelToken<'_>,
1867 ) -> Result<QueryResult, EngineError> {
1868 substitute_placeholders(&mut stmt, params)?;
1869 let saved = self.current_tx;
1880 self.current_tx = Some(IMPLICIT_TX);
1881 let result = self.execute_stmt_with_cancel(stmt, cancel);
1882 self.current_tx = saved;
1883 result
1884 }
1885
1886 fn execute_inner_with_cancel(
1887 &mut self,
1888 sql: &str,
1889 cancel: CancelToken<'_>,
1890 ) -> Result<QueryResult, EngineError> {
1891 cancel.check()?;
1892 let stmt = self.prepare(sql)?;
1893 let start_us = self.clock.map(|f| f());
1897 let result = self.execute_stmt_with_cancel(stmt, cancel);
1898 if let (Some(t0), Ok(_)) = (start_us, &result) {
1899 let now = self.clock.map_or(t0, |f| f());
1900 let elapsed = now.saturating_sub(t0).max(0) as u64;
1901 self.query_stats.record(sql, elapsed, now as u64);
1902 if let (Some(threshold), Some(logger)) =
1905 (self.slow_query_threshold_us, self.slow_query_logger)
1906 && elapsed >= threshold
1907 {
1908 logger(sql, elapsed);
1909 }
1910 }
1911 result
1912 }
1913
1914 fn execute_stmt_with_cancel(
1915 &mut self,
1916 stmt: Statement,
1917 cancel: CancelToken<'_>,
1918 ) -> Result<QueryResult, EngineError> {
1919 cancel.check()?;
1920 let mut stmt = stmt;
1934 self.pre_resolve_sequence_calls_in_statement(&mut stmt)?;
1943 let result = match stmt {
1944 Statement::CreateTable(s) => self.exec_create_table(s),
1945 Statement::CreateExtension(_) => Ok(QueryResult::CommandOk {
1949 affected: 0,
1950 modified_catalog: false,
1951 }),
1952 Statement::DoBlock(body) => self.exec_do_block(body),
1962 Statement::Empty => Ok(QueryResult::CommandOk {
1966 affected: 0,
1967 modified_catalog: false,
1968 }),
1969 Statement::DropTable { names, if_exists } => self.exec_drop_table(names, if_exists),
1970 Statement::DropIndex { name, if_exists } => self.exec_drop_index(name, if_exists),
1971 Statement::CreateIndex(s) => self.exec_create_index(s),
1972 Statement::Insert(s) => self.exec_insert(s),
1973 Statement::Update(s) => self.exec_update_cancel(&s, cancel),
1974 Statement::Delete(s) => self.exec_delete_cancel(&s, cancel),
1975 Statement::Merge(s) => self.exec_merge_cancel(&s, cancel),
1976 Statement::Select(s) => self.exec_select_cancel(&s, cancel),
1977 Statement::Begin => self.exec_begin(),
1978 Statement::Commit => self.exec_commit(),
1979 Statement::Rollback => self.exec_rollback(),
1980 Statement::Savepoint(name) => self.exec_savepoint(name),
1981 Statement::RollbackToSavepoint(name) => self.exec_rollback_to_savepoint(&name),
1982 Statement::ReleaseSavepoint(name) => self.exec_release_savepoint(&name),
1983 Statement::ShowTables => Ok(self.exec_show_tables()),
1984 Statement::ShowDatabases => Ok(self.exec_show_databases()),
1985 Statement::ShowCreateTable(name) => self.exec_show_create_table(&name),
1986 Statement::ShowIndexes(name) => self.exec_show_indexes(&name),
1987 Statement::ShowStatus => Ok(self.exec_show_status()),
1988 Statement::ShowVariables => Ok(self.exec_show_variables()),
1989 Statement::ShowProcesslist => Ok(self.exec_show_processlist()),
1990 Statement::ShowColumns(table) => self.exec_show_columns(&table),
1991 Statement::ShowUsers => Ok(self.exec_show_users()),
1992 Statement::ShowPublications => Ok(self.exec_show_publications()),
1993 Statement::ShowSubscriptions => Ok(self.exec_show_subscriptions()),
1994 Statement::CreateUser(s) => self.exec_create_user(&s),
1995 Statement::DropUser(name) => self.exec_drop_user(&name),
1996 Statement::Explain(e) => self.exec_explain(&e, cancel),
1997 Statement::AlterIndex(s) => self.exec_alter_index(s),
1998 Statement::AlterTable(s) => self.exec_alter_table(s),
1999 Statement::CreatePublication(s) => self.exec_create_publication(s),
2000 Statement::DropPublication(name) => self.exec_drop_publication(&name),
2001 Statement::CreateSubscription(s) => self.exec_create_subscription(s),
2002 Statement::DropSubscription(name) => self.exec_drop_subscription(&name),
2003 Statement::WaitForWalPosition { .. } => Err(EngineError::Unsupported(
2010 "WAIT FOR WAL POSITION must be handled by the server layer".into(),
2011 )),
2012 Statement::Analyze(target) => self.exec_analyze(target.as_deref()),
2014 Statement::CompactColdSegments => self.exec_compact_cold_segments(),
2016 Statement::SetParameter { name, value } => {
2021 self.set_session_param(name, value);
2022 Ok(QueryResult::CommandOk {
2023 affected: 0,
2024 modified_catalog: false,
2025 })
2026 }
2027 Statement::SetParameterList(pairs) => {
2033 for (name, value) in pairs {
2034 self.set_session_param(name, value);
2035 }
2036 Ok(QueryResult::CommandOk {
2037 affected: 0,
2038 modified_catalog: false,
2039 })
2040 }
2041 Statement::CreateFunction(s) => self.exec_create_function(s),
2045 Statement::CreateTrigger(s) => self.exec_create_trigger(s),
2046 Statement::DropTrigger {
2047 name,
2048 table,
2049 if_exists,
2050 } => self.exec_drop_trigger(&name, &table, if_exists),
2051 Statement::DropFunction { name, if_exists } => {
2052 self.exec_drop_function(&name, if_exists)
2053 }
2054 Statement::CreateSequence(s) => self.exec_create_sequence(s),
2055 Statement::AlterSequence(s) => self.exec_alter_sequence(s),
2056 Statement::DropSequence { names, if_exists } => {
2057 self.exec_drop_sequence(&names, if_exists)
2058 }
2059 Statement::CreateView(s) => self.exec_create_view(s),
2060 Statement::DropView { names, if_exists } => self.exec_drop_view(&names, if_exists),
2061 Statement::CreateMaterializedView(s) => self.exec_create_materialized_view(s),
2062 Statement::RefreshMaterializedView { name, with_data } => {
2063 self.exec_refresh_materialized_view(&name, with_data)
2064 }
2065 Statement::DropMaterializedView { names, if_exists } => {
2066 self.exec_drop_materialized_view(&names, if_exists)
2067 }
2068 Statement::CreateType(s) => self.exec_create_type(s),
2069 Statement::DropType { names, if_exists } => self.exec_drop_type(&names, if_exists),
2070 Statement::CreateDomain(s) => self.exec_create_domain(s),
2071 Statement::DropDomain { names, if_exists } => self.exec_drop_domain(&names, if_exists),
2072 Statement::CreateSchema {
2073 name,
2074 if_not_exists,
2075 } => self.exec_create_schema(name, if_not_exists),
2076 Statement::DropSchema { names, if_exists } => self.exec_drop_schema(&names, if_exists),
2077 Statement::ResetParameter(target) => {
2078 match target {
2079 None => self.session_params.clear(),
2080 Some(name) => {
2081 self.session_params.remove(&name.to_ascii_lowercase());
2082 }
2083 }
2084 Ok(QueryResult::CommandOk {
2085 affected: 0,
2086 modified_catalog: false,
2087 })
2088 }
2089 };
2090 self.enforce_row_limit(result)
2091 }
2092
2093 fn exec_create_publication(
2101 &mut self,
2102 s: CreatePublicationStatement,
2103 ) -> Result<QueryResult, EngineError> {
2104 self.publications
2110 .create(s.name, s.scope)
2111 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE PUBLICATION: {e:?}")))?;
2112 Ok(QueryResult::CommandOk {
2113 affected: 1,
2114 modified_catalog: true,
2115 })
2116 }
2117
2118 fn exec_drop_publication(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2123 let removed = self.publications.drop(name);
2124 Ok(QueryResult::CommandOk {
2125 affected: usize::from(removed),
2126 modified_catalog: removed,
2127 })
2128 }
2129
2130 pub const fn publications(&self) -> &publications::Publications {
2135 &self.publications
2136 }
2137
2138 fn exec_create_subscription(
2143 &mut self,
2144 s: CreateSubscriptionStatement,
2145 ) -> Result<QueryResult, EngineError> {
2146 let sub = subscriptions::Subscription {
2150 conn_str: s.conn_str,
2151 publications: s.publications,
2152 enabled: true,
2153 last_received_pos: 0,
2154 };
2155 self.subscriptions
2156 .create(s.name, sub)
2157 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE SUBSCRIPTION: {e:?}")))?;
2158 Ok(QueryResult::CommandOk {
2159 affected: 1,
2160 modified_catalog: true,
2161 })
2162 }
2163
2164 fn exec_drop_subscription(&mut self, name: &str) -> Result<QueryResult, EngineError> {
2172 let removed = self.subscriptions.drop(name);
2173 Ok(QueryResult::CommandOk {
2174 affected: usize::from(removed),
2175 modified_catalog: removed,
2176 })
2177 }
2178
2179 pub const fn subscriptions(&self) -> &subscriptions::Subscriptions {
2184 &self.subscriptions
2185 }
2186
2187 pub fn subscription_advance(&mut self, name: &str, pos: u64) -> bool {
2193 self.subscriptions.update_last_received_pos(name, pos)
2194 }
2195
2196 fn exec_show_subscriptions(&self) -> QueryResult {
2202 let columns = alloc::vec![
2203 ColumnSchema::new("name", DataType::Text, false),
2204 ColumnSchema::new("conn_str", DataType::Text, false),
2205 ColumnSchema::new("publications", DataType::Text, false),
2206 ColumnSchema::new("enabled", DataType::Bool, false),
2207 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2208 ];
2209 let rows: Vec<Row> = self
2210 .subscriptions
2211 .iter()
2212 .map(|(name, sub)| {
2213 Row::new(alloc::vec![
2214 Value::Text(name.clone()),
2215 Value::Text(sub.conn_str.clone()),
2216 Value::Text(sub.publications.join(", ")),
2217 Value::Bool(sub.enabled),
2218 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2219 ])
2220 })
2221 .collect();
2222 QueryResult::Rows { columns, rows }
2223 }
2224
2225 fn exec_spg_statistic(&self) -> QueryResult {
2230 let columns = alloc::vec![
2231 ColumnSchema::new("table_name", DataType::Text, false),
2232 ColumnSchema::new("column_name", DataType::Text, false),
2233 ColumnSchema::new("null_frac", DataType::Float, false),
2234 ColumnSchema::new("n_distinct", DataType::BigInt, false),
2235 ColumnSchema::new("histogram_bounds", DataType::Text, false),
2236 ColumnSchema::new("cold_row_count", DataType::BigInt, false),
2241 ];
2242 let rows: Vec<Row> = self
2243 .statistics
2244 .iter()
2245 .map(|((t, c), s)| {
2246 let cold = self
2247 .catalog
2248 .get(t)
2249 .map_or(0, |table| table.cold_row_count());
2250 Row::new(alloc::vec![
2251 Value::Text(t.clone()),
2252 Value::Text(c.clone()),
2253 Value::Float(f64::from(s.null_frac)),
2254 Value::BigInt(i64::try_from(s.n_distinct).unwrap_or(i64::MAX)),
2255 Value::Text(render_histogram_bounds(&s.histogram_bounds)),
2256 Value::BigInt(i64::try_from(cold).unwrap_or(i64::MAX)),
2257 ])
2258 })
2259 .collect();
2260 QueryResult::Rows { columns, rows }
2261 }
2262
2263 fn exec_spg_stat_replication(&self) -> QueryResult {
2270 let columns = alloc::vec![
2271 ColumnSchema::new("name", DataType::Text, false),
2272 ColumnSchema::new("conn_str", DataType::Text, false),
2273 ColumnSchema::new("publications", DataType::Text, false),
2274 ColumnSchema::new("last_received_pos", DataType::BigInt, false),
2275 ColumnSchema::new("enabled", DataType::Bool, false),
2276 ];
2277 let rows: Vec<Row> = self
2278 .subscriptions
2279 .iter()
2280 .map(|(name, sub)| {
2281 Row::new(alloc::vec![
2282 Value::Text(name.clone()),
2283 Value::Text(sub.conn_str.clone()),
2284 Value::Text(sub.publications.join(",")),
2285 Value::BigInt(i64::try_from(sub.last_received_pos).unwrap_or(i64::MAX)),
2286 Value::Bool(sub.enabled),
2287 ])
2288 })
2289 .collect();
2290 QueryResult::Rows { columns, rows }
2291 }
2292
2293 fn exec_spg_stat_segment(&self) -> QueryResult {
2305 let columns = alloc::vec![
2306 ColumnSchema::new("segment_id", DataType::BigInt, false),
2307 ColumnSchema::new("table_name", DataType::Text, false),
2308 ColumnSchema::new("num_rows", DataType::BigInt, false),
2309 ColumnSchema::new("num_pages", DataType::BigInt, false),
2310 ColumnSchema::new("total_bytes", DataType::BigInt, false),
2311 ];
2312 let mut segment_owners: alloc::collections::BTreeMap<u32, String> = BTreeMap::new();
2318 for tname in self.catalog.table_names() {
2319 if is_internal_table_name(&tname) {
2320 continue;
2321 }
2322 let Some(t) = self.catalog.get(&tname) else {
2323 continue;
2324 };
2325 for idx in t.indices() {
2326 if let spg_storage::IndexKind::BTree(map) = &idx.kind {
2327 for (_, locs) in map.iter() {
2328 for loc in locs {
2329 if let spg_storage::RowLocator::Cold { segment_id, .. } = loc {
2330 segment_owners
2331 .entry(*segment_id)
2332 .or_insert_with(|| tname.clone());
2333 }
2334 }
2335 }
2336 }
2337 }
2338 }
2339 let rows: Vec<Row> = self
2340 .catalog
2341 .cold_segment_ids_global()
2342 .iter()
2343 .filter_map(|&id| {
2344 let seg = self.catalog.cold_segment(id)?;
2345 let meta = seg.meta();
2346 let owner = segment_owners.get(&id).cloned().unwrap_or_default();
2347 Some(Row::new(alloc::vec![
2348 Value::BigInt(i64::from(id)),
2349 Value::Text(owner),
2350 Value::BigInt(i64::try_from(meta.num_rows).unwrap_or(i64::MAX)),
2351 Value::BigInt(i64::from(meta.num_pages)),
2352 Value::BigInt(i64::try_from(meta.total_bytes).unwrap_or(i64::MAX)),
2353 ]))
2354 })
2355 .collect();
2356 QueryResult::Rows { columns, rows }
2357 }
2358
2359 fn exec_spg_stat_query(&self) -> QueryResult {
2365 let columns = alloc::vec![
2366 ColumnSchema::new("sql", DataType::Text, false),
2367 ColumnSchema::new("exec_count", DataType::BigInt, false),
2368 ColumnSchema::new("total_us", DataType::BigInt, false),
2369 ColumnSchema::new("mean_us", DataType::BigInt, false),
2370 ColumnSchema::new("max_us", DataType::BigInt, false),
2371 ColumnSchema::new("last_seen_us", DataType::BigInt, false),
2372 ];
2373 let rows: Vec<Row> = self
2374 .query_stats
2375 .snapshot()
2376 .into_iter()
2377 .map(|(sql, s)| {
2378 let mean = if s.exec_count == 0 {
2379 0
2380 } else {
2381 s.total_us / s.exec_count
2382 };
2383 Row::new(alloc::vec![
2384 Value::Text(sql),
2385 Value::BigInt(i64::try_from(s.exec_count).unwrap_or(i64::MAX)),
2386 Value::BigInt(i64::try_from(s.total_us).unwrap_or(i64::MAX)),
2387 Value::BigInt(i64::try_from(mean).unwrap_or(i64::MAX)),
2388 Value::BigInt(i64::try_from(s.max_us).unwrap_or(i64::MAX)),
2389 Value::BigInt(i64::try_from(s.last_seen_us).unwrap_or(i64::MAX)),
2390 ])
2391 })
2392 .collect();
2393 QueryResult::Rows { columns, rows }
2394 }
2395
2396 #[must_use]
2401 pub const fn with_activity_provider(mut self, f: ActivityProvider) -> Self {
2402 self.activity_provider = Some(f);
2403 self
2404 }
2405
2406 #[must_use]
2408 pub const fn with_audit_providers(
2409 mut self,
2410 chain: AuditChainProvider,
2411 verify: AuditVerifier,
2412 ) -> Self {
2413 self.audit_chain_provider = Some(chain);
2414 self.audit_verifier = Some(verify);
2415 self
2416 }
2417
2418 #[must_use]
2423 pub const fn with_slow_query_log(mut self, threshold_us: u64, logger: SlowQueryLogger) -> Self {
2424 self.slow_query_threshold_us = Some(threshold_us);
2425 self.slow_query_logger = Some(logger);
2426 self
2427 }
2428
2429 pub fn set_plan_cache_max(&mut self, n: usize) {
2433 self.plan_cache.set_max_entries(n);
2434 }
2435
2436 fn exec_spg_stat_activity(&self) -> QueryResult {
2441 let columns = alloc::vec![
2442 ColumnSchema::new("pid", DataType::Int, false),
2443 ColumnSchema::new("user", DataType::Text, false),
2444 ColumnSchema::new("started_at_us", DataType::BigInt, false),
2445 ColumnSchema::new("current_sql", DataType::Text, false),
2446 ColumnSchema::new("wait_event", DataType::Text, false),
2447 ColumnSchema::new("elapsed_us", DataType::BigInt, false),
2448 ColumnSchema::new("in_transaction", DataType::Bool, false),
2449 ColumnSchema::new("application_name", DataType::Text, false),
2450 ];
2451 let rows: Vec<Row> = self
2452 .activity_provider
2453 .map(|f| f())
2454 .unwrap_or_default()
2455 .into_iter()
2456 .map(|r| {
2457 Row::new(alloc::vec![
2458 Value::Int(i32::try_from(r.pid).unwrap_or(i32::MAX)),
2459 Value::Text(r.user),
2460 Value::BigInt(r.started_at_us),
2461 Value::Text(r.current_sql),
2462 Value::Text(r.wait_event),
2463 Value::BigInt(r.elapsed_us),
2464 Value::Bool(r.in_transaction),
2465 Value::Text(r.application_name),
2466 ])
2467 })
2468 .collect();
2469 QueryResult::Rows { columns, rows }
2470 }
2471
2472 fn exec_spg_table_ddl(&self) -> QueryResult {
2476 let columns = alloc::vec![
2477 ColumnSchema::new("table_name", DataType::Text, false),
2478 ColumnSchema::new("ddl", DataType::Text, false),
2479 ];
2480 let rows: Vec<Row> = self
2481 .catalog
2482 .table_names()
2483 .into_iter()
2484 .filter(|n| !is_internal_table_name(n))
2485 .filter_map(|name| {
2486 let table = self.catalog.get(&name)?;
2487 let ddl = render_create_table(&name, &table.schema().columns);
2488 Some(Row::new(alloc::vec![Value::Text(name), Value::Text(ddl),]))
2489 })
2490 .collect();
2491 QueryResult::Rows { columns, rows }
2492 }
2493
2494 fn exec_spg_role_ddl(&self) -> QueryResult {
2498 let columns = alloc::vec![
2499 ColumnSchema::new("role_name", DataType::Text, false),
2500 ColumnSchema::new("ddl", DataType::Text, false),
2501 ];
2502 let rows: Vec<Row> = self
2503 .users
2504 .iter()
2505 .map(|(name, rec)| {
2506 let ddl = alloc::format!(
2507 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}'",
2508 rec.role.as_str(),
2509 );
2510 Row::new(alloc::vec![
2511 Value::Text(String::from(name)),
2512 Value::Text(ddl)
2513 ])
2514 })
2515 .collect();
2516 QueryResult::Rows { columns, rows }
2517 }
2518
2519 fn exec_spg_database_ddl(&self) -> QueryResult {
2525 let columns = alloc::vec![ColumnSchema::new("ddl", DataType::Text, false)];
2526 let mut out = String::new();
2527 for (name, rec) in self.users.iter() {
2528 out.push_str(&alloc::format!(
2529 "CREATE USER {name} WITH PASSWORD '<redacted>' ROLE '{}';\n",
2530 rec.role.as_str(),
2531 ));
2532 }
2533 for name in self.catalog.table_names() {
2534 if is_internal_table_name(&name) {
2535 continue;
2536 }
2537 if let Some(table) = self.catalog.get(&name) {
2538 out.push_str(&render_create_table(&name, &table.schema().columns));
2539 out.push_str(";\n");
2540 }
2541 }
2542 QueryResult::Rows {
2543 columns,
2544 rows: alloc::vec![Row::new(alloc::vec![Value::Text(out)])],
2545 }
2546 }
2547
2548 fn exec_spg_audit_chain(&self) -> QueryResult {
2552 let columns = alloc::vec![
2553 ColumnSchema::new("seq", DataType::BigInt, false),
2554 ColumnSchema::new("ts_ms", DataType::BigInt, false),
2555 ColumnSchema::new("prev_hash", DataType::Text, false),
2556 ColumnSchema::new("entry_hash", DataType::Text, false),
2557 ColumnSchema::new("sql", DataType::Text, false),
2558 ];
2559 let rows: Vec<Row> = self
2560 .audit_chain_provider
2561 .map(|f| f())
2562 .unwrap_or_default()
2563 .into_iter()
2564 .map(|r| {
2565 Row::new(alloc::vec![
2566 Value::BigInt(r.seq),
2567 Value::BigInt(r.ts_ms),
2568 Value::Text(r.prev_hash_hex),
2569 Value::Text(r.entry_hash_hex),
2570 Value::Text(r.sql),
2571 ])
2572 })
2573 .collect();
2574 QueryResult::Rows { columns, rows }
2575 }
2576
2577 fn exec_spg_audit_verify(&self) -> QueryResult {
2583 let columns = alloc::vec![
2584 ColumnSchema::new("verified_count", DataType::BigInt, false),
2585 ColumnSchema::new("broken_at_seq", DataType::BigInt, false),
2586 ];
2587 let (verified, broken) = self.audit_verifier.map(|f| f()).unwrap_or((0, -1));
2588 let row = Row::new(alloc::vec![Value::BigInt(verified), Value::BigInt(broken),]);
2589 QueryResult::Rows {
2590 columns,
2591 rows: alloc::vec![row],
2592 }
2593 }
2594
2595 pub fn query_stats(&self) -> &query_stats::QueryStats {
2597 &self.query_stats
2598 }
2599
2600 pub fn query_stats_mut(&mut self) -> &mut query_stats::QueryStats {
2602 &mut self.query_stats
2603 }
2604
2605 pub const fn statistics(&self) -> &statistics::Statistics {
2609 &self.statistics
2610 }
2611
2612 pub fn tables_needing_analyze(&self) -> Vec<String> {
2625 const MIN_ROWS: u64 = 100;
2626 let mut out = Vec::new();
2627 for name in self.catalog.table_names() {
2628 if is_internal_table_name(&name) {
2629 continue;
2630 }
2631 let Some(table) = self.catalog.get(&name) else {
2632 continue;
2633 };
2634 let row_count = table.rows().len() as u64;
2635 let modified = self.statistics.modified_since_last_analyze(&name);
2636 let base = row_count.max(MIN_ROWS);
2641 let threshold = base.saturating_add(9) / 10;
2642 if modified >= threshold {
2643 out.push(name);
2644 }
2645 }
2646 out
2647 }
2648
2649 fn exec_analyze(&mut self, target: Option<&str>) -> Result<QueryResult, EngineError> {
2660 let names: Vec<String> = if let Some(name) = target {
2661 if self.catalog.get(name).is_none() {
2663 return Err(EngineError::Storage(StorageError::TableNotFound {
2664 name: name.to_string(),
2665 }));
2666 }
2667 alloc::vec![name.to_string()]
2668 } else {
2669 self.catalog
2670 .table_names()
2671 .into_iter()
2672 .filter(|n| !is_internal_table_name(n))
2673 .collect()
2674 };
2675 let mut analysed = 0usize;
2676 for table_name in &names {
2677 self.analyze_one_table(table_name)?;
2678 analysed += 1;
2679 }
2680 if analysed > 0 {
2686 self.statistics.bump_version();
2687 if target.is_some() {
2688 for t in &names {
2689 self.plan_cache.evict_referencing(t);
2690 }
2691 } else {
2692 self.plan_cache.clear();
2693 }
2694 }
2695 Ok(QueryResult::CommandOk {
2696 affected: analysed,
2697 modified_catalog: true,
2698 })
2699 }
2700
2701 fn set_session_param(&mut self, name: String, value: spg_sql::ast::SetValue) {
2714 let normalised = match value {
2715 spg_sql::ast::SetValue::String(s) => s,
2716 spg_sql::ast::SetValue::Ident(s) => s,
2717 spg_sql::ast::SetValue::Number(s) => s,
2718 spg_sql::ast::SetValue::Default => String::new(),
2719 };
2720 let key = name.to_ascii_lowercase();
2721 let value_off = matches!(
2732 normalised.to_ascii_lowercase().as_str(),
2733 "0" | "off" | "false"
2734 );
2735 let value_on = matches!(
2736 normalised.to_ascii_lowercase().as_str(),
2737 "1" | "on" | "true"
2738 );
2739 if key == "foreign_key_checks"
2740 || key == "session_replication_role" && normalised.eq_ignore_ascii_case("replica")
2741 {
2742 if value_off || key == "session_replication_role" {
2743 self.foreign_key_checks = false;
2744 } else if value_on
2745 || (key == "session_replication_role" && normalised.eq_ignore_ascii_case("origin"))
2746 {
2747 self.foreign_key_checks = true;
2748 let _ = self.drain_pending_foreign_keys();
2752 }
2753 }
2754 self.session_params.insert(key, normalised);
2755 }
2756
2757 fn drain_pending_foreign_keys(&mut self) -> Result<(), EngineError> {
2764 let pending = core::mem::take(&mut self.pending_foreign_keys);
2765 for (child, fk) in pending {
2766 let cols_snapshot = match self.active_catalog().get(&child) {
2770 Some(t) => t.schema().columns.clone(),
2771 None => continue,
2772 };
2773 let storage_fk =
2774 resolve_foreign_key(&child, &cols_snapshot, fk, self.active_catalog())?;
2775 let table = self
2776 .active_catalog_mut()
2777 .get_mut(&child)
2778 .expect("checked above");
2779 table.schema_mut().foreign_keys.push(storage_fk);
2780 }
2781 Ok(())
2782 }
2783
2784 #[must_use]
2788 pub fn session_param(&self, name: &str) -> Option<&str> {
2789 self.session_params
2790 .get(&name.to_ascii_lowercase())
2791 .map(String::as_str)
2792 }
2793
2794 fn ev_ctx<'a>(
2799 &'a self,
2800 columns: &'a [ColumnSchema],
2801 alias: Option<&'a str>,
2802 ) -> EvalContext<'a> {
2803 EvalContext::new(columns, alias)
2804 .with_default_text_search_config(self.session_param("default_text_search_config"))
2805 }
2806
2807 fn exec_compact_cold_segments(&mut self) -> Result<QueryResult, EngineError> {
2811 let target = COMPACTION_TARGET_DEFAULT_BYTES;
2812 let reports = self.compact_cold_segments_with_target(target)?;
2813 let columns = alloc::vec![
2814 ColumnSchema::new("table_name", DataType::Text, false),
2815 ColumnSchema::new("index_name", DataType::Text, false),
2816 ColumnSchema::new("sources_merged", DataType::BigInt, false),
2817 ColumnSchema::new("merged_segment_id", DataType::BigInt, false),
2818 ColumnSchema::new("merged_rows", DataType::BigInt, false),
2819 ColumnSchema::new("deleted_rows_pruned", DataType::BigInt, false),
2820 ColumnSchema::new("bytes_reclaimed_estimate", DataType::BigInt, false),
2821 ];
2822 let rows: Vec<Row> = reports
2823 .into_iter()
2824 .map(|(tname, iname, report)| {
2825 Row::new(alloc::vec![
2826 Value::Text(tname),
2827 Value::Text(iname),
2828 Value::BigInt(i64::try_from(report.sources.len()).unwrap_or(i64::MAX)),
2829 Value::BigInt(i64::from(report.merged_segment_id.unwrap_or(0))),
2830 Value::BigInt(i64::try_from(report.merged_rows).unwrap_or(i64::MAX)),
2831 Value::BigInt(i64::try_from(report.deleted_rows_pruned).unwrap_or(i64::MAX),),
2832 Value::BigInt(
2833 i64::try_from(report.bytes_reclaimed_estimate).unwrap_or(i64::MAX),
2834 ),
2835 ])
2836 })
2837 .collect();
2838 Ok(QueryResult::Rows { columns, rows })
2839 }
2840
2841 fn analyze_one_table(&mut self, table_name: &str) -> Result<(), EngineError> {
2846 let table = self.catalog.get(table_name).ok_or_else(|| {
2847 EngineError::Storage(StorageError::TableNotFound {
2848 name: table_name.to_string(),
2849 })
2850 })?;
2851 let schema = table.schema().clone();
2852 let row_count = table.rows().len();
2853 self.statistics.clear_table(table_name);
2858 for (col_pos, col_schema) in schema.columns.iter().enumerate() {
2859 if matches!(col_schema.ty, DataType::Vector { .. }) {
2862 continue;
2863 }
2864 let mut non_null_values: Vec<Value> = Vec::with_capacity(row_count);
2865 let mut nulls: u64 = 0;
2866 for row in table.rows() {
2867 match row.values.get(col_pos) {
2868 Some(Value::Null) | None => nulls += 1,
2869 Some(v) => non_null_values.push(v.clone()),
2870 }
2871 }
2872 non_null_values.sort_by(|a, b| sort_values_for_histogram(a, b));
2877 let non_null: Vec<String> = non_null_values.iter().map(canonical_value_repr).collect();
2878 let null_frac = if row_count == 0 {
2879 0.0
2880 } else {
2881 #[allow(clippy::cast_precision_loss)]
2882 let f = nulls as f32 / row_count as f32;
2883 f
2884 };
2885 let n_distinct = statistics::estimate_n_distinct(&non_null);
2886 let histogram_bounds = statistics::build_histogram(&non_null);
2887 self.statistics.set(
2888 table_name.to_string(),
2889 col_schema.name.clone(),
2890 statistics::ColumnStats {
2891 null_frac,
2892 n_distinct,
2893 histogram_bounds,
2894 },
2895 );
2896 }
2897 self.statistics.reset_modified(table_name);
2898 let cold_count = {
2904 let table = self
2905 .active_catalog()
2906 .get(table_name)
2907 .expect("table still present");
2908 table.count_cold_locators()
2909 };
2910 let table_mut = self
2911 .active_catalog_mut()
2912 .get_mut(table_name)
2913 .expect("table still present");
2914 table_mut.set_cold_row_count(cold_count);
2915 Ok(())
2916 }
2917
2918 fn exec_show_publications(&self) -> QueryResult {
2930 let columns = alloc::vec![
2931 ColumnSchema::new("name", DataType::Text, false),
2932 ColumnSchema::new("scope", DataType::Text, false),
2933 ColumnSchema::new("table_count", DataType::Int, true),
2934 ];
2935 let rows: Vec<Row> = self
2936 .publications
2937 .iter()
2938 .map(|(name, scope)| {
2939 let (scope_str, count_val) = match scope {
2940 spg_sql::ast::PublicationScope::AllTables => {
2941 ("FOR ALL TABLES".to_string(), Value::Null)
2942 }
2943 spg_sql::ast::PublicationScope::ForTables(ts) => (
2944 alloc::format!("FOR TABLE {}", ts.join(", ")),
2945 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2946 ),
2947 spg_sql::ast::PublicationScope::AllTablesExcept(ts) => (
2948 alloc::format!("FOR ALL TABLES EXCEPT {}", ts.join(", ")),
2949 Value::Int(i32::try_from(ts.len()).unwrap_or(i32::MAX)),
2950 ),
2951 };
2952 Row::new(alloc::vec![
2953 Value::Text(name.clone()),
2954 Value::Text(scope_str),
2955 count_val,
2956 ])
2957 })
2958 .collect();
2959 QueryResult::Rows { columns, rows }
2960 }
2961
2962 fn exec_show_users(&self) -> QueryResult {
2964 let columns = alloc::vec![
2965 ColumnSchema::new("name", DataType::Text, false),
2966 ColumnSchema::new("role", DataType::Text, false),
2967 ];
2968 let rows: Vec<Row> = self
2969 .users
2970 .iter()
2971 .map(|(name, rec)| {
2972 Row::new(alloc::vec![
2973 Value::Text(name.to_string()),
2974 Value::Text(rec.role.as_str().to_string()),
2975 ])
2976 })
2977 .collect();
2978 QueryResult::Rows { columns, rows }
2979 }
2980
2981 fn exec_create_user(&mut self, s: &CreateUserStatement) -> Result<QueryResult, EngineError> {
2982 if self.in_transaction() {
2983 return Err(EngineError::Unsupported(
2984 "CREATE USER is not allowed inside a transaction".into(),
2985 ));
2986 }
2987 let role = users::Role::parse(&s.role).ok_or_else(|| {
2988 EngineError::Unsupported(alloc::format!("invalid role: {:?}", s.role))
2989 })?;
2990 let salt = self.salt_fn.map_or_else(
2994 || {
2995 let mut s_bytes = [0u8; 16];
2996 let digest = spg_crypto::hash(s.name.as_bytes());
2997 s_bytes.copy_from_slice(&digest[..16]);
2998 s_bytes
2999 },
3000 |f| f(),
3001 );
3002 self.users
3003 .create(&s.name, &s.password, role, salt)
3004 .map_err(|e| EngineError::Unsupported(alloc::format!("CREATE USER: {e}")))?;
3005 Ok(QueryResult::CommandOk {
3006 affected: 1,
3007 modified_catalog: true,
3008 })
3009 }
3010
3011 fn exec_drop_user(&mut self, name: &str) -> Result<QueryResult, EngineError> {
3012 if self.in_transaction() {
3013 return Err(EngineError::Unsupported(
3014 "DROP USER is not allowed inside a transaction".into(),
3015 ));
3016 }
3017 self.users
3018 .drop(name)
3019 .map_err(|e| EngineError::Unsupported(alloc::format!("DROP USER: {e}")))?;
3020 Ok(QueryResult::CommandOk {
3021 affected: 1,
3022 modified_catalog: true,
3023 })
3024 }
3025
3026 fn exec_create_function(
3032 &mut self,
3033 s: spg_sql::ast::CreateFunctionStatement,
3034 ) -> Result<QueryResult, EngineError> {
3035 let args_repr = render_function_args(&s.args);
3036 let returns = match &s.returns {
3037 spg_sql::ast::FunctionReturn::Trigger => alloc::string::String::from("TRIGGER"),
3038 spg_sql::ast::FunctionReturn::Void => alloc::string::String::from("VOID"),
3039 spg_sql::ast::FunctionReturn::Type(t) => alloc::format!("{t}"),
3040 spg_sql::ast::FunctionReturn::Other(s) => s.clone(),
3041 };
3042 let body_text = match &s.body {
3043 spg_sql::ast::FunctionBody::PlPgSql(b) => alloc::format!("{b}"),
3044 spg_sql::ast::FunctionBody::Raw(s) => s.clone(),
3045 };
3046 let def = spg_storage::FunctionDef {
3047 name: s.name.clone(),
3048 args_repr,
3049 returns,
3050 language: s.language.clone(),
3051 body: body_text,
3052 };
3053 self.active_catalog_mut()
3054 .create_function(def, s.or_replace)
3055 .map_err(EngineError::Storage)?;
3056 Ok(QueryResult::CommandOk {
3057 affected: 0,
3058 modified_catalog: true,
3059 })
3060 }
3061
3062 fn exec_create_trigger(
3067 &mut self,
3068 s: spg_sql::ast::CreateTriggerStatement,
3069 ) -> Result<QueryResult, EngineError> {
3070 let timing = match s.timing {
3071 spg_sql::ast::TriggerTiming::Before => "BEFORE",
3072 spg_sql::ast::TriggerTiming::After => "AFTER",
3073 spg_sql::ast::TriggerTiming::InsteadOf => "INSTEAD OF",
3074 };
3075 let events: Vec<alloc::string::String> = s
3076 .events
3077 .iter()
3078 .map(|e| match e {
3079 spg_sql::ast::TriggerEvent::Insert => alloc::string::String::from("INSERT"),
3080 spg_sql::ast::TriggerEvent::Update => alloc::string::String::from("UPDATE"),
3081 spg_sql::ast::TriggerEvent::Delete => alloc::string::String::from("DELETE"),
3082 spg_sql::ast::TriggerEvent::Truncate => alloc::string::String::from("TRUNCATE"),
3083 })
3084 .collect();
3085 let for_each = match s.for_each {
3086 spg_sql::ast::TriggerForEach::Row => "ROW",
3087 spg_sql::ast::TriggerForEach::Statement => "STATEMENT",
3088 };
3089 let def = spg_storage::TriggerDef {
3090 name: s.name.clone(),
3091 table: s.table.clone(),
3092 timing: alloc::string::String::from(timing),
3093 events,
3094 for_each: alloc::string::String::from(for_each),
3095 function: s.function.clone(),
3096 update_columns: s.update_columns.clone(),
3097 enabled: true,
3100 };
3101 self.active_catalog_mut()
3102 .create_trigger(def, s.or_replace)
3103 .map_err(EngineError::Storage)?;
3104 Ok(QueryResult::CommandOk {
3105 affected: 0,
3106 modified_catalog: true,
3107 })
3108 }
3109
3110 fn exec_drop_trigger(
3111 &mut self,
3112 name: &str,
3113 table: &str,
3114 if_exists: bool,
3115 ) -> Result<QueryResult, EngineError> {
3116 let removed = self.active_catalog_mut().drop_trigger(name, table);
3117 if !removed && !if_exists {
3118 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3119 alloc::format!("trigger {name:?} on {table:?} does not exist"),
3120 )));
3121 }
3122 Ok(QueryResult::CommandOk {
3123 affected: usize::from(removed),
3124 modified_catalog: removed,
3125 })
3126 }
3127
3128 fn exec_drop_function(
3129 &mut self,
3130 name: &str,
3131 if_exists: bool,
3132 ) -> Result<QueryResult, EngineError> {
3133 let removed = self.active_catalog_mut().drop_function(name);
3134 if !removed && !if_exists {
3135 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3136 alloc::format!("function {name:?} does not exist"),
3137 )));
3138 }
3139 Ok(QueryResult::CommandOk {
3140 affected: usize::from(removed),
3141 modified_catalog: removed,
3142 })
3143 }
3144
3145 fn exec_create_sequence(
3149 &mut self,
3150 s: spg_sql::ast::CreateSequenceStatement,
3151 ) -> Result<QueryResult, EngineError> {
3152 use spg_sql::ast::{SeqBound, SequenceDataType as AstDt};
3153 use spg_storage::{SequenceDataType, SequenceDef};
3154 let dt = match s.data_type {
3155 None => SequenceDataType::BigInt,
3156 Some(AstDt::SmallInt) => SequenceDataType::SmallInt,
3157 Some(AstDt::Int) => SequenceDataType::Int,
3158 Some(AstDt::BigInt) => SequenceDataType::BigInt,
3159 };
3160 let increment = s.options.increment.unwrap_or(1);
3161 if increment == 0 {
3162 return Err(EngineError::Unsupported(
3163 "INCREMENT must not be zero".into(),
3164 ));
3165 }
3166 let (def_min, def_max) = dt.default_bounds(increment > 0);
3167 let min_value = match s.options.min_value {
3168 None | Some(SeqBound::NoBound) => def_min,
3169 Some(SeqBound::Value(n)) => n,
3170 };
3171 let max_value = match s.options.max_value {
3172 None | Some(SeqBound::NoBound) => def_max,
3173 Some(SeqBound::Value(n)) => n,
3174 };
3175 if min_value > max_value {
3176 return Err(EngineError::Unsupported(alloc::format!(
3177 "MINVALUE ({min_value}) must be <= MAXVALUE ({max_value})"
3178 )));
3179 }
3180 let start = s
3181 .options
3182 .start
3183 .unwrap_or(if increment > 0 { min_value } else { max_value });
3184 if start < min_value || start > max_value {
3185 return Err(EngineError::Unsupported(alloc::format!(
3186 "START WITH ({start}) is outside MINVALUE..MAXVALUE ({min_value}..{max_value})"
3187 )));
3188 }
3189 let cache = s.options.cache.unwrap_or(1);
3190 if cache < 1 {
3191 return Err(EngineError::Unsupported("CACHE must be >= 1".into()));
3192 }
3193 let cycle = s.options.cycle.unwrap_or(false);
3194 let owned_by = match s.options.owned_by {
3195 None | Some(spg_sql::ast::SequenceOwnedBy::None) => None,
3196 Some(spg_sql::ast::SequenceOwnedBy::Column { table, column }) => Some((table, column)),
3197 };
3198 let def = SequenceDef {
3199 name: s.name.clone(),
3200 data_type: dt,
3201 start,
3202 increment,
3203 min_value,
3204 max_value,
3205 cache,
3206 cycle,
3207 owned_by,
3208 last_value: start,
3209 is_called: false,
3210 };
3211 self.active_catalog_mut()
3212 .create_sequence(def, s.if_not_exists)
3213 .map_err(EngineError::Storage)?;
3214 Ok(QueryResult::CommandOk {
3215 affected: 0,
3216 modified_catalog: !self.in_transaction(),
3217 })
3218 }
3219
3220 fn exec_alter_sequence(
3223 &mut self,
3224 s: spg_sql::ast::AlterSequenceStatement,
3225 ) -> Result<QueryResult, EngineError> {
3226 use spg_sql::ast::SeqBound;
3227 let cat = self.active_catalog_mut();
3228 if !cat.sequences().contains_key(&s.name) {
3229 if s.if_exists {
3230 return Ok(QueryResult::CommandOk {
3231 affected: 0,
3232 modified_catalog: false,
3233 });
3234 }
3235 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3236 alloc::format!("sequence {:?} does not exist", s.name),
3237 )));
3238 }
3239 let min_value = match s.options.min_value {
3240 None => None,
3241 Some(SeqBound::NoBound) => None, Some(SeqBound::Value(n)) => Some(n),
3243 };
3244 let max_value = match s.options.max_value {
3245 None => None,
3246 Some(SeqBound::NoBound) => None,
3247 Some(SeqBound::Value(n)) => Some(n),
3248 };
3249 let owned_by = s.options.owned_by.map(|ob| match ob {
3250 spg_sql::ast::SequenceOwnedBy::None => None,
3251 spg_sql::ast::SequenceOwnedBy::Column { table, column } => Some((table, column)),
3252 });
3253 cat.alter_sequence(
3254 &s.name,
3255 s.options.increment,
3256 min_value,
3257 max_value,
3258 s.options.start,
3259 s.options.restart,
3260 s.options.cache,
3261 s.options.cycle,
3262 owned_by,
3263 )
3264 .map_err(EngineError::Storage)?;
3265 Ok(QueryResult::CommandOk {
3266 affected: 0,
3267 modified_catalog: !self.in_transaction(),
3268 })
3269 }
3270
3271 fn pre_resolve_sequence_calls_in_statement(
3276 &mut self,
3277 stmt: &mut Statement,
3278 ) -> Result<(), EngineError> {
3279 match stmt {
3280 Statement::Select(s) => self.pre_resolve_sequence_calls_in_select(s),
3281 Statement::Insert(s) => {
3282 for tuple in &mut s.rows {
3283 for cell in tuple.iter_mut() {
3284 self.resolve_sequence_calls_in_expr(cell)?;
3285 }
3286 }
3287 Ok(())
3288 }
3289 Statement::Update(s) => {
3290 for (_col, expr) in &mut s.assignments {
3291 self.resolve_sequence_calls_in_expr(expr)?;
3292 }
3293 if let Some(w) = &mut s.where_ {
3294 self.resolve_sequence_calls_in_expr(w)?;
3295 }
3296 Ok(())
3297 }
3298 Statement::Delete(s) => {
3299 if let Some(w) = &mut s.where_ {
3300 self.resolve_sequence_calls_in_expr(w)?;
3301 }
3302 Ok(())
3303 }
3304 _ => Ok(()),
3305 }
3306 }
3307
3308 fn pre_resolve_sequence_calls_in_select(
3309 &mut self,
3310 s: &mut spg_sql::ast::SelectStatement,
3311 ) -> Result<(), EngineError> {
3312 for item in &mut s.items {
3313 match item {
3314 spg_sql::ast::SelectItem::Expr { expr, .. } => {
3315 self.resolve_sequence_calls_in_expr(expr)?;
3316 }
3317 spg_sql::ast::SelectItem::Wildcard => {}
3318 }
3319 }
3320 if let Some(w) = &mut s.where_ {
3321 self.resolve_sequence_calls_in_expr(w)?;
3322 }
3323 Ok(())
3324 }
3325
3326 #[allow(clippy::too_many_lines)]
3334 fn resolve_sequence_calls_in_expr(&mut self, expr: &mut Expr) -> Result<(), EngineError> {
3335 match expr {
3336 Expr::Literal(_) | Expr::Column(_) | Expr::Placeholder(_) => Ok(()),
3337 Expr::FunctionCall { name, args } => {
3338 for a in args.iter_mut() {
3342 self.resolve_sequence_calls_in_expr(a)?;
3343 }
3344 let lc = name.to_ascii_lowercase();
3345 if lc == "nextval" || lc == "currval" || lc == "setval" {
3346 let v = self.eval_sequence_call(&lc, args)?;
3347 *expr = Expr::Literal(value_to_literal(v));
3348 }
3349 Ok(())
3350 }
3351 Expr::Binary { lhs, rhs, .. } => {
3352 self.resolve_sequence_calls_in_expr(lhs)?;
3353 self.resolve_sequence_calls_in_expr(rhs)
3354 }
3355 Expr::Unary { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3356 Expr::Cast { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3357 Expr::IsNull { expr, .. } => self.resolve_sequence_calls_in_expr(expr),
3358 Expr::Like { expr, pattern, .. } => {
3359 self.resolve_sequence_calls_in_expr(expr)?;
3360 self.resolve_sequence_calls_in_expr(pattern)
3361 }
3362 Expr::Extract { source, .. } => self.resolve_sequence_calls_in_expr(source),
3363 Expr::Array(items) => {
3364 for it in items.iter_mut() {
3365 self.resolve_sequence_calls_in_expr(it)?;
3366 }
3367 Ok(())
3368 }
3369 _ => Ok(()),
3374 }
3375 }
3376
3377 fn eval_sequence_call(&mut self, op: &str, args: &[Expr]) -> Result<Value, EngineError> {
3381 if args.is_empty() {
3382 return Err(EngineError::Unsupported(alloc::format!(
3383 "{op}() takes at least one argument"
3384 )));
3385 }
3386 let seq_name = match &args[0] {
3387 Expr::Literal(spg_sql::ast::Literal::String(s)) => {
3388 let trimmed = s
3394 .strip_prefix("public.")
3395 .or_else(|| s.strip_prefix("pg_catalog."))
3396 .unwrap_or(s);
3397 trimmed.to_string()
3398 }
3399 Expr::Cast { expr, .. } => {
3404 if let Expr::Literal(spg_sql::ast::Literal::String(s)) = expr.as_ref() {
3405 let trimmed = s
3406 .strip_prefix("public.")
3407 .or_else(|| s.strip_prefix("pg_catalog."))
3408 .unwrap_or(s);
3409 trimmed.to_string()
3410 } else {
3411 return Err(EngineError::Unsupported(alloc::format!(
3412 "{op}() first argument must be a literal sequence name"
3413 )));
3414 }
3415 }
3416 other => {
3417 return Err(EngineError::Unsupported(alloc::format!(
3418 "{op}() first argument must be a literal sequence name, got {other:?}"
3419 )));
3420 }
3421 };
3422 match op {
3423 "nextval" => {
3424 let v = self
3425 .active_catalog_mut()
3426 .sequence_next_value(&seq_name)
3427 .map_err(EngineError::Storage)?;
3428 Ok(Value::BigInt(v))
3429 }
3430 "currval" => {
3431 let v = self
3432 .active_catalog()
3433 .sequence_current_value(&seq_name)
3434 .map_err(EngineError::Storage)?;
3435 Ok(Value::BigInt(v))
3436 }
3437 "setval" => {
3438 if args.len() < 2 || args.len() > 3 {
3439 return Err(EngineError::Unsupported(alloc::format!(
3440 "setval() takes 2 or 3 arguments, got {}",
3441 args.len()
3442 )));
3443 }
3444 let value = match &args[1] {
3445 Expr::Literal(spg_sql::ast::Literal::Integer(n)) => *n,
3446 other => {
3447 return Err(EngineError::Unsupported(alloc::format!(
3448 "setval() value argument must be a literal integer, got {other:?}"
3449 )));
3450 }
3451 };
3452 let is_called = if args.len() == 3 {
3453 match &args[2] {
3454 Expr::Literal(spg_sql::ast::Literal::Bool(b)) => *b,
3455 other => {
3456 return Err(EngineError::Unsupported(alloc::format!(
3457 "setval() is_called argument must be a literal BOOL, got {other:?}"
3458 )));
3459 }
3460 }
3461 } else {
3462 true
3463 };
3464 let v = self
3465 .active_catalog_mut()
3466 .sequence_set_value(&seq_name, value, is_called)
3467 .map_err(EngineError::Storage)?;
3468 Ok(Value::BigInt(v))
3469 }
3470 other => Err(EngineError::Unsupported(alloc::format!(
3471 "unknown sequence op {other:?}"
3472 ))),
3473 }
3474 }
3475
3476 fn expand_views_in_select(
3485 &self,
3486 stmt: &SelectStatement,
3487 ) -> Result<Option<SelectStatement>, EngineError> {
3488 let cat = self.active_catalog();
3489 let mut referenced: Vec<String> = Vec::new();
3490 if let Some(from) = &stmt.from {
3491 collect_view_refs(&from.primary, cat, &mut referenced);
3492 for j in &from.joins {
3493 collect_view_refs(&j.table, cat, &mut referenced);
3494 }
3495 }
3496 referenced.retain(|n| !stmt.ctes.iter().any(|c| c.name == *n));
3499 if referenced.is_empty() {
3500 return Ok(None);
3501 }
3502 let mut new_ctes: Vec<spg_sql::ast::Cte> = Vec::with_capacity(referenced.len());
3503 for name in &referenced {
3504 let view = cat.views().get(name).ok_or_else(|| {
3505 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3506 "view {name:?} disappeared mid-expansion"
3507 )))
3508 })?;
3509 let parsed = spg_sql::parser::parse_statement(&view.body).map_err(|e| {
3510 EngineError::Unsupported(alloc::format!("view {name:?} body re-parse failed: {e}"))
3511 })?;
3512 let Statement::Select(body) = parsed else {
3513 return Err(EngineError::Unsupported(alloc::format!(
3514 "view {name:?} body is not a SELECT (catalog corruption)"
3515 )));
3516 };
3517 new_ctes.push(spg_sql::ast::Cte {
3518 name: name.clone(),
3519 body,
3520 recursive: false,
3521 column_overrides: view.columns.clone(),
3522 });
3523 }
3524 let mut out = stmt.clone();
3525 new_ctes.extend(out.ctes);
3527 out.ctes = new_ctes;
3528 Ok(Some(out))
3529 }
3530
3531 fn exec_create_view(
3535 &mut self,
3536 s: spg_sql::ast::CreateViewStatement,
3537 ) -> Result<QueryResult, EngineError> {
3538 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body));
3542 let def = spg_storage::ViewDef {
3543 name: s.name.clone(),
3544 columns: s.columns,
3545 body: body_repr,
3546 };
3547 self.active_catalog_mut()
3548 .create_view(def, s.or_replace, s.if_not_exists)
3549 .map_err(EngineError::Storage)?;
3550 Ok(QueryResult::CommandOk {
3551 affected: 0,
3552 modified_catalog: !self.in_transaction(),
3553 })
3554 }
3555
3556 fn exec_create_type(
3561 &mut self,
3562 s: spg_sql::ast::CreateTypeStatement,
3563 ) -> Result<QueryResult, EngineError> {
3564 let cat = self.active_catalog();
3567 if cat.get(&s.name).is_some() {
3568 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3569 alloc::format!("type {:?} would shadow an existing table", s.name),
3570 )));
3571 }
3572 if cat.sequences().contains_key(&s.name) {
3573 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3574 alloc::format!("type {:?} would shadow an existing sequence", s.name),
3575 )));
3576 }
3577 if cat.views().contains_key(&s.name) {
3578 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3579 alloc::format!("type {:?} would shadow an existing view", s.name),
3580 )));
3581 }
3582 let def = match s.kind {
3583 spg_sql::ast::TypeKind::Enum { labels } => {
3584 if labels.is_empty() {
3585 return Err(EngineError::Unsupported(
3586 "CREATE TYPE … AS ENUM requires at least one label".into(),
3587 ));
3588 }
3589 for i in 0..labels.len() {
3591 for j in (i + 1)..labels.len() {
3592 if labels[i] == labels[j] {
3593 return Err(EngineError::Unsupported(alloc::format!(
3594 "CREATE TYPE {:?}: duplicate ENUM label {:?}",
3595 s.name,
3596 labels[i]
3597 )));
3598 }
3599 }
3600 }
3601 spg_storage::EnumDef {
3602 name: s.name.clone(),
3603 labels,
3604 }
3605 }
3606 };
3607 self.active_catalog_mut()
3608 .create_enum_type(def)
3609 .map_err(EngineError::Storage)?;
3610 Ok(QueryResult::CommandOk {
3611 affected: 0,
3612 modified_catalog: !self.in_transaction(),
3613 })
3614 }
3615
3616 fn exec_create_domain(
3621 &mut self,
3622 s: spg_sql::ast::CreateDomainStatement,
3623 ) -> Result<QueryResult, EngineError> {
3624 let cat = self.active_catalog();
3625 if cat.domain_types().contains_key(&s.name) {
3626 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3627 alloc::format!("domain {:?} already exists", s.name),
3628 )));
3629 }
3630 if cat.get(&s.name).is_some()
3631 || cat.sequences().contains_key(&s.name)
3632 || cat.views().contains_key(&s.name)
3633 || cat.enum_types().contains_key(&s.name)
3634 {
3635 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3636 alloc::format!("domain {:?} would shadow an existing object", s.name),
3637 )));
3638 }
3639 let base_type = column_type_to_data_type(s.base_type);
3640 let default = s.default.as_ref().map(|e| alloc::format!("{e}"));
3641 let checks = s
3642 .checks
3643 .iter()
3644 .map(|e| alloc::format!("{e}"))
3645 .collect::<Vec<_>>();
3646 let def = spg_storage::DomainDef {
3647 name: s.name.clone(),
3648 base_type,
3649 nullable: !s.not_null,
3650 default,
3651 checks,
3652 };
3653 self.active_catalog_mut()
3654 .create_domain_type(def)
3655 .map_err(EngineError::Storage)?;
3656 Ok(QueryResult::CommandOk {
3657 affected: 0,
3658 modified_catalog: !self.in_transaction(),
3659 })
3660 }
3661
3662 fn exec_drop_domain(
3664 &mut self,
3665 names: &[String],
3666 if_exists: bool,
3667 ) -> Result<QueryResult, EngineError> {
3668 let mut removed = 0usize;
3669 for name in names {
3670 let was_present = self.active_catalog_mut().drop_domain_type(name);
3671 if was_present {
3672 removed += 1;
3673 } else if !if_exists {
3674 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3675 alloc::format!("domain {name:?} does not exist"),
3676 )));
3677 }
3678 }
3679 Ok(QueryResult::CommandOk {
3680 affected: removed,
3681 modified_catalog: removed > 0 && !self.in_transaction(),
3682 })
3683 }
3684
3685 fn exec_create_schema(
3691 &mut self,
3692 name: String,
3693 if_not_exists: bool,
3694 ) -> Result<QueryResult, EngineError> {
3695 self.active_catalog_mut()
3696 .create_schema(name, if_not_exists)
3697 .map_err(EngineError::Storage)?;
3698 Ok(QueryResult::CommandOk {
3699 affected: 0,
3700 modified_catalog: !self.in_transaction(),
3701 })
3702 }
3703
3704 fn exec_drop_schema(
3708 &mut self,
3709 names: &[String],
3710 if_exists: bool,
3711 ) -> Result<QueryResult, EngineError> {
3712 let mut removed = 0usize;
3713 for name in names {
3714 let was_present = self
3715 .active_catalog_mut()
3716 .drop_schema(name)
3717 .map_err(EngineError::Storage)?;
3718 if was_present {
3719 removed += 1;
3720 } else if !if_exists {
3721 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3722 alloc::format!("schema {name:?} does not exist"),
3723 )));
3724 }
3725 }
3726 Ok(QueryResult::CommandOk {
3727 affected: removed,
3728 modified_catalog: removed > 0 && !self.in_transaction(),
3729 })
3730 }
3731
3732 fn exec_drop_type(
3737 &mut self,
3738 names: &[String],
3739 if_exists: bool,
3740 ) -> Result<QueryResult, EngineError> {
3741 let mut removed = 0usize;
3742 for name in names {
3743 let was_present = self.active_catalog_mut().drop_enum_type(name);
3744 if was_present {
3745 removed += 1;
3746 } else if !if_exists {
3747 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3748 alloc::format!("type {name:?} does not exist"),
3749 )));
3750 }
3751 }
3752 Ok(QueryResult::CommandOk {
3753 affected: removed,
3754 modified_catalog: removed > 0 && !self.in_transaction(),
3755 })
3756 }
3757
3758 fn exec_create_materialized_view(
3763 &mut self,
3764 s: spg_sql::ast::CreateMaterializedViewStatement,
3765 ) -> Result<QueryResult, EngineError> {
3766 let cat = self.active_catalog();
3768 if cat.materialized_views().contains_key(&s.name) || cat.get(&s.name).is_some() {
3769 if s.if_not_exists {
3770 return Ok(QueryResult::CommandOk {
3771 affected: 0,
3772 modified_catalog: false,
3773 });
3774 }
3775 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3776 alloc::format!("materialized view {:?} already exists", s.name),
3777 )));
3778 }
3779 if cat.views().contains_key(&s.name) {
3780 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3781 alloc::format!(
3782 "materialized view {:?} would shadow an existing view",
3783 s.name
3784 ),
3785 )));
3786 }
3787 if cat.sequences().contains_key(&s.name) {
3788 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3789 alloc::format!(
3790 "materialized view {:?} would shadow an existing sequence",
3791 s.name
3792 ),
3793 )));
3794 }
3795 let body_repr = alloc::format!("{}", spg_sql::ast::Statement::Select(s.body.clone()));
3797 let result = self.exec_select_cancel(&s.body, CancelToken::none())?;
3802 let (mut cols, rows) = match result {
3803 QueryResult::Rows { columns, rows } => (columns, rows),
3804 other => {
3805 return Err(EngineError::Unsupported(alloc::format!(
3806 "CREATE MATERIALIZED VIEW body did not return rows: {other:?}"
3807 )));
3808 }
3809 };
3810 if !s.columns.is_empty() {
3812 if s.columns.len() != cols.len() {
3813 return Err(EngineError::Unsupported(alloc::format!(
3814 "CREATE MATERIALIZED VIEW {:?}: column list has {} names but body returns {}",
3815 s.name,
3816 s.columns.len(),
3817 cols.len()
3818 )));
3819 }
3820 for (c, name) in cols.iter_mut().zip(s.columns.iter()) {
3821 c.name.clone_from(name);
3822 }
3823 }
3824 cols = infer_column_types(&cols, &rows);
3827 let schema = spg_storage::TableSchema::new(s.name.clone(), cols);
3828 let cat = self.active_catalog_mut();
3829 cat.create_table(schema).map_err(EngineError::Storage)?;
3830 if s.with_data {
3831 let table = cat
3832 .get_mut(&s.name)
3833 .expect("just-created materialized-view backing table must exist");
3834 for row in rows {
3835 table.insert(row).map_err(EngineError::Storage)?;
3836 }
3837 }
3838 cat.register_materialized_view(s.name.clone(), body_repr);
3839 Ok(QueryResult::CommandOk {
3840 affected: 0,
3841 modified_catalog: !self.in_transaction(),
3842 })
3843 }
3844
3845 fn exec_refresh_materialized_view(
3849 &mut self,
3850 name: &str,
3851 with_data: bool,
3852 ) -> Result<QueryResult, EngineError> {
3853 let source = self
3854 .active_catalog()
3855 .materialized_views()
3856 .get(name)
3857 .cloned()
3858 .ok_or_else(|| {
3859 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3860 "materialized view {name:?} does not exist"
3861 )))
3862 })?;
3863 {
3866 let cat = self.active_catalog_mut();
3867 let table = cat.get_mut(name).ok_or_else(|| {
3868 EngineError::Storage(spg_storage::StorageError::Corrupt(alloc::format!(
3869 "materialized view {name:?} backing table missing"
3870 )))
3871 })?;
3872 table.truncate();
3873 }
3874 if !with_data {
3875 return Ok(QueryResult::CommandOk {
3876 affected: 0,
3877 modified_catalog: !self.in_transaction(),
3878 });
3879 }
3880 let parsed = spg_sql::parser::parse_statement(&source).map_err(|e| {
3881 EngineError::Unsupported(alloc::format!(
3882 "materialized view {name:?} body re-parse failed: {e}"
3883 ))
3884 })?;
3885 let Statement::Select(body) = parsed else {
3886 return Err(EngineError::Unsupported(alloc::format!(
3887 "materialized view {name:?} body is not a SELECT (catalog corruption)"
3888 )));
3889 };
3890 let rows = match self.exec_select_cancel(&body, CancelToken::none())? {
3891 QueryResult::Rows { rows, .. } => rows,
3892 other => {
3893 return Err(EngineError::Unsupported(alloc::format!(
3894 "REFRESH MATERIALIZED VIEW {name:?} body did not return rows: {other:?}"
3895 )));
3896 }
3897 };
3898 let cat = self.active_catalog_mut();
3899 let table = cat.get_mut(name).expect("backing table verified above");
3900 let affected = rows.len();
3901 for row in rows {
3902 table.insert(row).map_err(EngineError::Storage)?;
3903 }
3904 Ok(QueryResult::CommandOk {
3905 affected,
3906 modified_catalog: !self.in_transaction(),
3907 })
3908 }
3909
3910 fn exec_drop_materialized_view(
3913 &mut self,
3914 names: &[String],
3915 if_exists: bool,
3916 ) -> Result<QueryResult, EngineError> {
3917 let mut removed = 0usize;
3918 for name in names {
3919 let was_present = self
3920 .active_catalog_mut()
3921 .drop_materialized_view_source(name);
3922 if was_present {
3923 self.active_catalog_mut().drop_table(name);
3925 removed += 1;
3926 } else if !if_exists {
3927 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3928 alloc::format!("materialized view {name:?} does not exist"),
3929 )));
3930 }
3931 }
3932 Ok(QueryResult::CommandOk {
3933 affected: removed,
3934 modified_catalog: removed > 0 && !self.in_transaction(),
3935 })
3936 }
3937
3938 fn exec_drop_view(
3940 &mut self,
3941 names: &[String],
3942 if_exists: bool,
3943 ) -> Result<QueryResult, EngineError> {
3944 let mut removed = 0usize;
3945 for name in names {
3946 let was_present = self.active_catalog_mut().drop_view(name);
3947 if !was_present && !if_exists {
3948 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3949 alloc::format!("view {name:?} does not exist"),
3950 )));
3951 }
3952 if was_present {
3953 removed += 1;
3954 }
3955 }
3956 Ok(QueryResult::CommandOk {
3957 affected: removed,
3958 modified_catalog: removed > 0 && !self.in_transaction(),
3959 })
3960 }
3961
3962 fn exec_drop_sequence(
3964 &mut self,
3965 names: &[String],
3966 if_exists: bool,
3967 ) -> Result<QueryResult, EngineError> {
3968 let mut removed = 0usize;
3969 for name in names {
3970 let was_present = self.active_catalog_mut().drop_sequence(name);
3971 if !was_present && !if_exists {
3972 return Err(EngineError::Storage(spg_storage::StorageError::Corrupt(
3973 alloc::format!("sequence {name:?} does not exist"),
3974 )));
3975 }
3976 if was_present {
3977 removed += 1;
3978 }
3979 }
3980 Ok(QueryResult::CommandOk {
3981 affected: removed,
3982 modified_catalog: removed > 0 && !self.in_transaction(),
3983 })
3984 }
3985
3986 fn exec_update_cancel(
3993 &mut self,
3994 stmt: &spg_sql::ast::UpdateStatement,
3995 cancel: CancelToken<'_>,
3996 ) -> Result<QueryResult, EngineError> {
3997 let before_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "BEFORE");
4006 let after_update_triggers = self.snapshot_update_row_triggers(&stmt.table, "AFTER");
4007 let trigger_session_cfg: Option<String> = self
4008 .session_params
4009 .get("default_text_search_config")
4010 .cloned();
4011 if let Some(w) = &stmt.where_ {
4019 let schema_cols = self
4020 .active_catalog()
4021 .get(&stmt.table)
4022 .ok_or_else(|| {
4023 EngineError::Storage(StorageError::TableNotFound {
4024 name: stmt.table.clone(),
4025 })
4026 })?
4027 .schema()
4028 .columns
4029 .clone();
4030 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4031 && let Some(idx_name) = self
4032 .active_catalog()
4033 .get(&stmt.table)
4034 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4035 {
4036 let _ = self
4040 .active_catalog_mut()
4041 .promote_cold_row(&stmt.table, &idx_name, &key);
4042 }
4043 }
4044
4045 let ts_cfg: Option<String> = self
4048 .session_param("default_text_search_config")
4049 .map(String::from);
4050 let clock_for_on_update = self.clock;
4054 let table = self
4055 .active_catalog_mut()
4056 .get_mut(&stmt.table)
4057 .ok_or_else(|| {
4058 EngineError::Storage(StorageError::TableNotFound {
4059 name: stmt.table.clone(),
4060 })
4061 })?;
4062 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4063 let mut targets: Vec<(usize, &Expr)> = Vec::with_capacity(stmt.assignments.len());
4067 for (col, expr) in &stmt.assignments {
4068 let pos = schema_cols
4069 .iter()
4070 .position(|c| c.name == *col)
4071 .ok_or_else(|| {
4072 EngineError::Eval(EvalError::ColumnNotFound { name: col.clone() })
4073 })?;
4074 targets.push((pos, expr));
4075 }
4076 let mut on_update_overrides: Vec<(usize, String)> = Vec::new();
4084 for (i, col) in schema_cols.iter().enumerate() {
4085 if targets.iter().any(|(p, _)| *p == i) {
4086 continue;
4087 }
4088 if let Some(src) = &col.on_update_runtime {
4089 on_update_overrides.push((i, src.clone()));
4090 }
4091 }
4092 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4093 .with_default_text_search_config(ts_cfg.as_deref());
4094 let mut planned: Vec<(usize, Vec<Value>)> = Vec::new();
4100 for (i, row) in table.rows().iter().enumerate() {
4101 if i.is_multiple_of(256) {
4105 cancel.check()?;
4106 }
4107 if let Some(w) = &stmt.where_ {
4108 let cond = eval::eval_expr(w, row, &ctx)?;
4109 if !matches!(cond, Value::Bool(true)) {
4110 continue;
4111 }
4112 }
4113 let mut new_vals = row.values.clone();
4114 for (pos, expr) in &targets {
4115 let v = eval::eval_expr(expr, row, &ctx)?;
4116 let coerced = coerce_value(v, schema_cols[*pos].ty, &schema_cols[*pos].name, *pos)?;
4117 check_unsigned_range(&coerced, &schema_cols[*pos], *pos)?;
4118 new_vals[*pos] = coerced;
4119 }
4120 for (pos, src) in &on_update_overrides {
4123 let v = eval_runtime_default_free(src, schema_cols[*pos].ty, clock_for_on_update)?;
4124 new_vals[*pos] = v;
4125 }
4126 planned.push((i, new_vals));
4127 }
4128 let plan_with_old: Vec<(usize, Vec<Value>, Vec<Value>)> = planned
4132 .iter()
4133 .map(|(pos, new_vals)| (*pos, table.rows()[*pos].values.clone(), new_vals.clone()))
4134 .collect();
4135 let self_fks = table.schema().foreign_keys.clone();
4136 let _ = table;
4141 if !self_fks.is_empty() {
4145 let new_rows: Vec<Vec<Value>> = planned
4146 .iter()
4147 .map(|(_pos, new_vals)| new_vals.clone())
4148 .collect();
4149 enforce_fk_inserts(self.active_catalog(), &stmt.table, &self_fks, &new_rows)?;
4150 }
4151 {
4155 let new_rows: Vec<Vec<Value>> = planned
4156 .iter()
4157 .map(|(_pos, new_vals)| new_vals.clone())
4158 .collect();
4159 enforce_check_constraints(self.active_catalog(), &stmt.table, &new_rows)?;
4160 }
4161 let child_plan =
4165 plan_fk_parent_updates(self.active_catalog(), &stmt.table, &plan_with_old)?;
4166 for step in &child_plan {
4168 apply_fk_child_step(self.active_catalog_mut(), step)?;
4169 }
4170 let table = self
4172 .active_catalog_mut()
4173 .get_mut(&stmt.table)
4174 .ok_or_else(|| {
4175 EngineError::Storage(StorageError::TableNotFound {
4176 name: stmt.table.clone(),
4177 })
4178 })?;
4179 let mut applied_after_before: Vec<(usize, Row, Row)> = Vec::with_capacity(planned.len());
4189 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4191 for (pos, new_vals) in &planned {
4192 let old_row = table.rows()[*pos].clone();
4193 let mut new_row = Row::new(new_vals.clone());
4194 let mut skip = false;
4195 for (fd, filter) in &before_update_triggers {
4196 if !filter.is_empty()
4201 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4202 {
4203 continue;
4204 }
4205 let (outcome, deferred) = triggers::fire_row_trigger(
4206 fd,
4207 Some(new_row.clone()),
4208 Some(&old_row),
4209 &stmt.table,
4210 &schema_cols,
4211 &[],
4212 trigger_session_cfg.as_deref(),
4213 false,
4214 )
4215 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4216 deferred_embedded.extend(deferred);
4217 match outcome {
4218 triggers::TriggerOutcome::Row(r) => new_row = r,
4219 triggers::TriggerOutcome::Skip => {
4220 skip = true;
4221 break;
4222 }
4223 }
4224 }
4225 if !skip {
4226 applied_after_before.push((*pos, new_row, old_row));
4227 }
4228 }
4229 let updated_for_returning: Vec<Vec<Value>> = if stmt.returning.is_some() {
4232 applied_after_before
4233 .iter()
4234 .map(|(_pos, new_row, _old)| new_row.values.clone())
4235 .collect()
4236 } else {
4237 Vec::new()
4238 };
4239 let affected = applied_after_before.len();
4240 for (pos, new_row, old_row) in applied_after_before {
4244 table.update_row(pos, new_row.values.clone())?;
4245 for (fd, filter) in &after_update_triggers {
4246 if !filter.is_empty()
4247 && !any_column_changed(filter, &schema_cols, &old_row, &new_row)
4248 {
4249 continue;
4250 }
4251 let (_outcome, deferred) = triggers::fire_row_trigger(
4252 fd,
4253 Some(new_row.clone()),
4254 Some(&old_row),
4255 &stmt.table,
4256 &schema_cols,
4257 &[],
4258 trigger_session_cfg.as_deref(),
4259 true,
4260 )
4261 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
4262 deferred_embedded.extend(deferred);
4263 }
4264 }
4265 let _ = table;
4266 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4268 if !self.in_transaction() && affected > 0 {
4270 self.statistics
4271 .record_modifications(&stmt.table, affected as u64);
4272 }
4273 if let Some(items) = &stmt.returning {
4275 return self.build_returning_rows(&stmt.table, items, updated_for_returning);
4276 }
4277 Ok(QueryResult::CommandOk {
4278 affected,
4279 modified_catalog: !self.in_transaction(),
4280 })
4281 }
4282
4283 fn exec_merge_cancel(
4314 &mut self,
4315 stmt: &spg_sql::ast::MergeStatement,
4316 cancel: CancelToken<'_>,
4317 ) -> Result<QueryResult, EngineError> {
4318 let target_alias = stmt
4319 .target_alias
4320 .clone()
4321 .unwrap_or_else(|| stmt.target.clone());
4322 let source_alias = stmt
4323 .source_alias
4324 .clone()
4325 .unwrap_or_else(|| stmt.source.clone());
4326 let (target_cols, target_rows_snapshot) = {
4327 let t = self.active_catalog().get(&stmt.target).ok_or_else(|| {
4328 EngineError::Storage(StorageError::TableNotFound {
4329 name: stmt.target.clone(),
4330 })
4331 })?;
4332 (
4333 t.schema().columns.clone(),
4334 t.rows().iter().cloned().collect::<Vec<Row>>(),
4335 )
4336 };
4337 let (source_cols, source_rows) = {
4338 let s = self.active_catalog().get(&stmt.source).ok_or_else(|| {
4339 EngineError::Storage(StorageError::TableNotFound {
4340 name: stmt.source.clone(),
4341 })
4342 })?;
4343 (
4344 s.schema().columns.clone(),
4345 s.rows().iter().cloned().collect::<Vec<Row>>(),
4346 )
4347 };
4348 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
4350 for col in &target_cols {
4351 combined_schema.push(ColumnSchema::new(
4352 alloc::format!("{target_alias}.{}", col.name),
4353 col.ty,
4354 col.nullable,
4355 ));
4356 }
4357 for col in &source_cols {
4358 combined_schema.push(ColumnSchema::new(
4359 alloc::format!("{source_alias}.{}", col.name),
4360 col.ty,
4361 col.nullable,
4362 ));
4363 }
4364 let combined_ctx = EvalContext::new(&combined_schema, None);
4365 let mut source_only_schema: Vec<ColumnSchema> = Vec::new();
4369 for col in &target_cols {
4370 source_only_schema.push(ColumnSchema::new(
4371 alloc::format!("{target_alias}.{}", col.name),
4372 col.ty,
4373 col.nullable,
4374 ));
4375 }
4376 for col in &source_cols {
4377 source_only_schema.push(ColumnSchema::new(
4378 alloc::format!("{source_alias}.{}", col.name),
4379 col.ty,
4380 col.nullable,
4381 ));
4382 }
4383 let source_only_ctx = EvalContext::new(&source_only_schema, None);
4384 let target_arity = target_cols.len();
4385 let source_arity = source_cols.len();
4386
4387 let mut delete_indices: Vec<usize> = Vec::new();
4390 let mut updates: Vec<(usize, Vec<Value>)> = Vec::new();
4391 let mut inserts: Vec<Vec<Value>> = Vec::new();
4392 let mut affected: usize = 0;
4393
4394 for (src_idx, src_row) in source_rows.iter().enumerate() {
4395 if src_idx.is_multiple_of(256) {
4396 cancel.check()?;
4397 }
4398 let mut matched_targets: Vec<usize> = Vec::new();
4400 for (t_idx, t_row) in target_rows_snapshot.iter().enumerate() {
4401 let mut combined_vals = t_row.values.clone();
4402 combined_vals.extend(src_row.values.iter().cloned());
4403 let combined_row = Row::new(combined_vals);
4404 let cond = eval::eval_expr(&stmt.on, &combined_row, &combined_ctx)?;
4405 if matches!(cond, Value::Bool(true)) {
4406 matched_targets.push(t_idx);
4407 }
4408 }
4409 let is_matched = !matched_targets.is_empty();
4410 let fired_clause = stmt.clauses.iter().find(|c| {
4416 let kind_ok = match c.matched {
4417 spg_sql::ast::MergeMatched::Matched => is_matched,
4418 spg_sql::ast::MergeMatched::NotMatched => !is_matched,
4419 };
4420 if !kind_ok {
4421 return false;
4422 }
4423 let Some(cond_expr) = &c.condition else {
4424 return true;
4425 };
4426 let row = if is_matched {
4427 let t = &target_rows_snapshot[matched_targets[0]];
4428 let mut vals = t.values.clone();
4429 vals.extend(src_row.values.iter().cloned());
4430 Row::new(vals)
4431 } else {
4432 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4433 vals.extend(src_row.values.iter().cloned());
4434 Row::new(vals)
4435 };
4436 let ctx_ref = if is_matched {
4437 &combined_ctx
4438 } else {
4439 &source_only_ctx
4440 };
4441 matches!(
4442 eval::eval_expr(cond_expr, &row, ctx_ref),
4443 Ok(Value::Bool(true))
4444 )
4445 });
4446 let Some(clause) = fired_clause else { continue };
4447 match &clause.action {
4448 spg_sql::ast::MergeAction::DoNothing => {}
4449 spg_sql::ast::MergeAction::Delete => {
4450 for &t_idx in &matched_targets {
4451 if !delete_indices.contains(&t_idx) {
4452 delete_indices.push(t_idx);
4453 affected += 1;
4454 }
4455 }
4456 }
4457 spg_sql::ast::MergeAction::Update { assignments } => {
4458 let mut planned_sets: Vec<(usize, &Expr)> =
4460 Vec::with_capacity(assignments.len());
4461 for (col, expr) in assignments {
4462 let pos =
4463 target_cols
4464 .iter()
4465 .position(|c| c.name == *col)
4466 .ok_or_else(|| {
4467 EngineError::Eval(EvalError::ColumnNotFound {
4468 name: col.clone(),
4469 })
4470 })?;
4471 planned_sets.push((pos, expr));
4472 }
4473 for &t_idx in &matched_targets {
4474 let t_row = &target_rows_snapshot[t_idx];
4475 let mut new_values = t_row.values.clone();
4476 let mut combined_vals = t_row.values.clone();
4477 combined_vals.extend(src_row.values.iter().cloned());
4478 let combined_row = Row::new(combined_vals);
4479 for (pos, expr) in &planned_sets {
4480 let raw = eval::eval_expr(expr, &combined_row, &combined_ctx)?;
4481 let coerced = coerce_value(
4482 raw,
4483 target_cols[*pos].ty,
4484 &target_cols[*pos].name,
4485 *pos,
4486 )?;
4487 new_values[*pos] = coerced;
4488 }
4489 updates.push((t_idx, new_values));
4490 affected += 1;
4491 }
4492 }
4493 spg_sql::ast::MergeAction::Insert { columns, values } => {
4494 let mut vals: Vec<Value> = (0..target_arity).map(|_| Value::Null).collect();
4496 vals.extend(src_row.values.iter().cloned());
4497 let synth_row = Row::new(vals);
4498 let mut new_row_values: Vec<Value> =
4499 (0..target_arity).map(|_| Value::Null).collect();
4500 for (col, expr) in columns.iter().zip(values.iter()) {
4501 let pos =
4502 target_cols
4503 .iter()
4504 .position(|c| c.name == *col)
4505 .ok_or_else(|| {
4506 EngineError::Eval(EvalError::ColumnNotFound {
4507 name: col.clone(),
4508 })
4509 })?;
4510 let raw = eval::eval_expr(expr, &synth_row, &source_only_ctx)?;
4511 let coerced =
4512 coerce_value(raw, target_cols[pos].ty, &target_cols[pos].name, pos)?;
4513 new_row_values[pos] = coerced;
4514 }
4515 inserts.push(new_row_values);
4516 affected += 1;
4517 }
4518 }
4519 }
4520 let _ = source_arity; let table = self
4524 .active_catalog_mut()
4525 .get_mut(&stmt.target)
4526 .ok_or_else(|| {
4527 EngineError::Storage(StorageError::TableNotFound {
4528 name: stmt.target.clone(),
4529 })
4530 })?;
4531 for (idx, new_vals) in &updates {
4535 table
4536 .update_row(*idx, new_vals.clone())
4537 .map_err(EngineError::Storage)?;
4538 }
4539 if !delete_indices.is_empty() {
4540 table.delete_rows(&delete_indices);
4541 }
4542 for vals in inserts {
4543 table.insert(Row::new(vals)).map_err(EngineError::Storage)?;
4544 }
4545 Ok(QueryResult::CommandOk {
4546 affected,
4547 modified_catalog: affected > 0,
4548 })
4549 }
4550
4551 fn exec_delete_cancel(
4552 &mut self,
4553 stmt: &spg_sql::ast::DeleteStatement,
4554 cancel: CancelToken<'_>,
4555 ) -> Result<QueryResult, EngineError> {
4556 let before_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "BEFORE");
4560 let after_delete_triggers = self.snapshot_row_triggers(&stmt.table, "DELETE", "AFTER");
4561 let trigger_session_cfg: Option<String> = self
4562 .session_params
4563 .get("default_text_search_config")
4564 .cloned();
4565 let mut cold_shadow_count: usize = 0;
4573 if let Some(w) = &stmt.where_ {
4574 let schema_cols = self
4575 .active_catalog()
4576 .get(&stmt.table)
4577 .ok_or_else(|| {
4578 EngineError::Storage(StorageError::TableNotFound {
4579 name: stmt.table.clone(),
4580 })
4581 })?
4582 .schema()
4583 .columns
4584 .clone();
4585 if let Some((col_pos, key)) = try_pk_predicate(w, &schema_cols, stmt.table.as_str())
4586 && let Some(idx_name) = self
4587 .active_catalog()
4588 .get(&stmt.table)
4589 .and_then(|t| t.index_on(col_pos).map(|i| i.name.clone()))
4590 {
4591 cold_shadow_count = self
4592 .active_catalog_mut()
4593 .shadow_cold_row(&stmt.table, &idx_name, &key)
4594 .unwrap_or(0);
4595 }
4596 }
4597
4598 let ts_cfg: Option<String> = self
4604 .session_param("default_text_search_config")
4605 .map(String::from);
4606 let table = self
4607 .active_catalog_mut()
4608 .get_mut(&stmt.table)
4609 .ok_or_else(|| {
4610 EngineError::Storage(StorageError::TableNotFound {
4611 name: stmt.table.clone(),
4612 })
4613 })?;
4614 let schema_cols: Vec<ColumnSchema> = table.schema().columns.clone();
4615 let ctx = EvalContext::new(&schema_cols, Some(stmt.table.as_str()))
4616 .with_default_text_search_config(ts_cfg.as_deref());
4617 let mut positions: Vec<usize> = Vec::new();
4618 let mut to_delete_rows: Vec<Vec<Value>> = Vec::new();
4622 for (i, row) in table.rows().iter().enumerate() {
4623 if i.is_multiple_of(256) {
4624 cancel.check()?;
4625 }
4626 let keep = if let Some(w) = &stmt.where_ {
4627 let cond = eval::eval_expr(w, row, &ctx)?;
4628 !matches!(cond, Value::Bool(true))
4629 } else {
4630 false
4631 };
4632 if !keep {
4633 positions.push(i);
4634 to_delete_rows.push(row.values.clone());
4635 }
4636 }
4637 let _ = table;
4644 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
4652 if !before_delete_triggers.is_empty() {
4653 let mut filtered_positions: Vec<usize> = Vec::with_capacity(positions.len());
4654 let mut filtered_old_rows: Vec<Vec<Value>> = Vec::with_capacity(to_delete_rows.len());
4655 for (pos, old_vals) in positions.iter().zip(to_delete_rows.iter()) {
4656 let old_row = Row::new(old_vals.clone());
4657 let mut cancel_this = false;
4658 for fd in &before_delete_triggers {
4659 let (outcome, deferred) = triggers::fire_row_trigger(
4660 fd,
4661 None,
4662 Some(&old_row),
4663 &stmt.table,
4664 &schema_cols,
4665 &[],
4666 trigger_session_cfg.as_deref(),
4667 false,
4668 )
4669 .map_err(|e| {
4670 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4671 })?;
4672 deferred_embedded.extend(deferred);
4673 if matches!(outcome, triggers::TriggerOutcome::Skip) {
4674 cancel_this = true;
4675 break;
4676 }
4677 }
4678 if !cancel_this {
4679 filtered_positions.push(*pos);
4680 filtered_old_rows.push(old_vals.clone());
4681 }
4682 }
4683 positions = filtered_positions;
4684 to_delete_rows = filtered_old_rows;
4685 }
4686 let cascade_plan = plan_fk_parent_deletions(
4687 self.active_catalog(),
4688 &stmt.table,
4689 &positions,
4690 &to_delete_rows,
4691 )?;
4692 for step in &cascade_plan {
4699 apply_fk_child_step(self.active_catalog_mut(), step)?;
4700 }
4701 let table = self
4703 .active_catalog_mut()
4704 .get_mut(&stmt.table)
4705 .ok_or_else(|| {
4706 EngineError::Storage(StorageError::TableNotFound {
4707 name: stmt.table.clone(),
4708 })
4709 })?;
4710 let affected = table.delete_rows(&positions) + cold_shadow_count;
4711 let _ = table;
4712 if !after_delete_triggers.is_empty() {
4717 for old_vals in &to_delete_rows {
4718 let old_row = Row::new(old_vals.clone());
4719 for fd in &after_delete_triggers {
4720 let (_outcome, deferred) = triggers::fire_row_trigger(
4721 fd,
4722 None,
4723 Some(&old_row),
4724 &stmt.table,
4725 &schema_cols,
4726 &[],
4727 trigger_session_cfg.as_deref(),
4728 true,
4729 )
4730 .map_err(|e| {
4731 EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}")))
4732 })?;
4733 deferred_embedded.extend(deferred);
4734 }
4735 }
4736 }
4737 self.execute_deferred_trigger_stmts(deferred_embedded, cancel)?;
4739 if !self.in_transaction() && affected > 0 {
4741 self.statistics
4742 .record_modifications(&stmt.table, affected as u64);
4743 }
4744 if let Some(items) = &stmt.returning {
4750 return self.build_returning_rows(&stmt.table, items, to_delete_rows);
4751 }
4752 Ok(QueryResult::CommandOk {
4753 affected,
4754 modified_catalog: !self.in_transaction(),
4755 })
4756 }
4757
4758 #[allow(clippy::format_push_string)]
4768 fn exec_explain(
4769 &self,
4770 e: &spg_sql::ast::ExplainStatement,
4771 cancel: CancelToken<'_>,
4772 ) -> Result<QueryResult, EngineError> {
4773 let mut lines = Vec::<String>::new();
4774 explain_select(&e.inner, self, 0, &mut lines);
4775 if e.suggest {
4776 let suggestions = build_index_suggestions(&e.inner, self);
4785 for s in suggestions {
4786 lines.push(s);
4787 }
4788 } else if e.analyze {
4789 let started = self.clock.map(|f| f());
4806 let exec = self.exec_select_cancel(&e.inner, cancel)?;
4807 let elapsed_micros = match (self.clock, started) {
4808 (Some(f), Some(s)) => Some(f().saturating_sub(s)),
4809 _ => None,
4810 };
4811 let row_count = if let QueryResult::Rows { rows, .. } = &exec {
4812 rows.len()
4813 } else {
4814 0
4815 };
4816 annotate_explain_lines(&mut lines, row_count, self);
4817 let mut total = alloc::format!("Total: rows={row_count}");
4818 if let Some(us) = elapsed_micros {
4819 total.push_str(&alloc::format!(" elapsed={us}us"));
4820 }
4821 lines.push(total);
4822 }
4823 let columns = alloc::vec![ColumnSchema::new("QUERY PLAN", DataType::Text, false)];
4824 let rows: Vec<Row> = lines
4825 .into_iter()
4826 .map(|l| Row::new(alloc::vec![Value::Text(l)]))
4827 .collect();
4828 Ok(QueryResult::Rows { columns, rows })
4829 }
4830
4831 fn exec_show_tables(&self) -> QueryResult {
4832 let columns = alloc::vec![ColumnSchema::new("name", DataType::Text, false)];
4833 let rows: Vec<Row> = self
4834 .active_catalog()
4835 .table_names()
4836 .into_iter()
4837 .map(|n| Row::new(alloc::vec![Value::Text(n)]))
4838 .collect();
4839 QueryResult::Rows { columns, rows }
4840 }
4841
4842 fn exec_show_create_table(&self, name: &str) -> Result<QueryResult, EngineError> {
4847 let t = self.active_catalog().get(name).ok_or_else(|| {
4848 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4849 })?;
4850 let cols: Vec<String> = t
4851 .schema()
4852 .columns
4853 .iter()
4854 .map(|c| {
4855 let ty = render_data_type(c.ty);
4856 let nullable = if c.nullable { "" } else { " NOT NULL" };
4857 alloc::format!(" `{}` {}{}", c.name, ty, nullable)
4858 })
4859 .collect();
4860 let mut body = cols.join(",\n");
4861 for uc in &t.schema().uniqueness_constraints {
4863 let col_names: Vec<String> = uc
4864 .columns
4865 .iter()
4866 .map(|&p| {
4867 t.schema().columns.get(p).map_or_else(
4868 || alloc::format!("col{p}"),
4869 |c| alloc::format!("`{}`", c.name),
4870 )
4871 })
4872 .collect();
4873 let kw = if uc.is_primary_key {
4874 "PRIMARY KEY"
4875 } else {
4876 "UNIQUE KEY"
4877 };
4878 body.push_str(",\n ");
4879 body.push_str(&alloc::format!("{kw} ({})", col_names.join(", ")));
4880 }
4881 for fk in &t.schema().foreign_keys {
4883 let local: Vec<String> = fk
4884 .local_columns
4885 .iter()
4886 .map(|&p| {
4887 t.schema().columns.get(p).map_or_else(
4888 || alloc::format!("col{p}"),
4889 |c| alloc::format!("`{}`", c.name),
4890 )
4891 })
4892 .collect();
4893 let parent_cols: Vec<String> =
4894 if let Some(parent) = self.active_catalog().get(&fk.parent_table) {
4895 fk.parent_columns
4896 .iter()
4897 .map(|&p| {
4898 parent.schema().columns.get(p).map_or_else(
4899 || alloc::format!("col{p}"),
4900 |c| alloc::format!("`{}`", c.name),
4901 )
4902 })
4903 .collect()
4904 } else {
4905 fk.parent_columns
4906 .iter()
4907 .map(|p| alloc::format!("col{p}"))
4908 .collect()
4909 };
4910 body.push_str(",\n ");
4911 body.push_str(&alloc::format!(
4912 "FOREIGN KEY ({}) REFERENCES `{}` ({})",
4913 local.join(", "),
4914 fk.parent_table,
4915 parent_cols.join(", ")
4916 ));
4917 }
4918 let ddl = alloc::format!(
4919 "CREATE TABLE `{}` (\n{}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
4920 name,
4921 body
4922 );
4923 let columns = alloc::vec![
4924 ColumnSchema::new("Table", DataType::Text, false),
4925 ColumnSchema::new("Create Table", DataType::Text, false),
4926 ];
4927 let rows = alloc::vec![Row::new(alloc::vec![
4928 Value::Text(name.into()),
4929 Value::Text(ddl),
4930 ])];
4931 Ok(QueryResult::Rows { columns, rows })
4932 }
4933
4934 fn exec_show_indexes(&self, name: &str) -> Result<QueryResult, EngineError> {
4940 let t = self.active_catalog().get(name).ok_or_else(|| {
4941 EngineError::Storage(StorageError::TableNotFound { name: name.into() })
4942 })?;
4943 let columns = alloc::vec![
4944 ColumnSchema::new("Table", DataType::Text, false),
4945 ColumnSchema::new("Non_unique", DataType::Int, false),
4946 ColumnSchema::new("Key_name", DataType::Text, false),
4947 ColumnSchema::new("Seq_in_index", DataType::Int, false),
4948 ColumnSchema::new("Column_name", DataType::Text, false),
4949 ColumnSchema::new("Null", DataType::Text, false),
4950 ColumnSchema::new("Index_type", DataType::Text, false),
4951 ];
4952 let mut rows: Vec<Row> = Vec::new();
4953 for idx in t.indices() {
4954 let col = t
4955 .schema()
4956 .columns
4957 .get(idx.column_position)
4958 .map_or("?".into(), |c| c.name.clone());
4959 let nullable = t
4960 .schema()
4961 .columns
4962 .get(idx.column_position)
4963 .map_or(true, |c| c.nullable);
4964 rows.push(Row::new(alloc::vec![
4965 Value::Text(name.into()),
4966 Value::Int(i32::from(!idx.is_unique)),
4967 Value::Text(idx.name.clone()),
4968 Value::Int(1),
4969 Value::Text(col),
4970 Value::Text(if nullable {
4971 "YES".into()
4972 } else {
4973 String::new()
4974 }),
4975 Value::Text("BTREE".into()),
4976 ]));
4977 }
4978 Ok(QueryResult::Rows { columns, rows })
4979 }
4980
4981 fn exec_show_status(&self) -> QueryResult {
4985 let columns = alloc::vec![
4986 ColumnSchema::new("Variable_name", DataType::Text, false),
4987 ColumnSchema::new("Value", DataType::Text, false),
4988 ];
4989 let pairs: &[(&str, &str)] = &[
4990 ("Uptime", "0"),
4991 ("Threads_connected", "1"),
4992 ("Threads_running", "1"),
4993 ("Questions", "0"),
4994 ("Slow_queries", "0"),
4995 ("Opened_tables", "0"),
4996 ("Innodb_buffer_pool_pages_total", "0"),
4997 ];
4998 let rows: Vec<Row> = pairs
4999 .iter()
5000 .map(|(k, v)| {
5001 Row::new(alloc::vec![
5002 Value::Text((*k).into()),
5003 Value::Text((*v).into())
5004 ])
5005 })
5006 .collect();
5007 QueryResult::Rows { columns, rows }
5008 }
5009
5010 fn exec_show_variables(&self) -> QueryResult {
5013 let columns = alloc::vec![
5014 ColumnSchema::new("Variable_name", DataType::Text, false),
5015 ColumnSchema::new("Value", DataType::Text, false),
5016 ];
5017 let mut rows: Vec<Row> = Vec::new();
5018 let canonical: &[(&str, &str)] = &[
5019 ("version", "8.0.35-spg"),
5020 ("version_comment", "SPG dual-stack engine"),
5021 ("character_set_server", "utf8mb4"),
5022 ("collation_server", "utf8mb4_0900_ai_ci"),
5023 ("max_allowed_packet", "67108864"),
5024 ("autocommit", "ON"),
5025 ("sql_mode", "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"),
5026 ("time_zone", "SYSTEM"),
5027 ("transaction_isolation", "REPEATABLE-READ"),
5028 ];
5029 for &(k, v) in canonical {
5030 rows.push(Row::new(alloc::vec![
5031 Value::Text(k.into()),
5032 Value::Text(v.into()),
5033 ]));
5034 }
5035 for (k, v) in &self.session_params {
5037 if !canonical.iter().any(|(n, _)| (*n).eq_ignore_ascii_case(k)) {
5038 rows.push(Row::new(alloc::vec![
5039 Value::Text(k.clone()),
5040 Value::Text(v.clone()),
5041 ]));
5042 }
5043 }
5044 QueryResult::Rows { columns, rows }
5045 }
5046
5047 fn exec_show_processlist(&self) -> QueryResult {
5052 let columns = alloc::vec![
5053 ColumnSchema::new("Id", DataType::Int, false),
5054 ColumnSchema::new("User", DataType::Text, false),
5055 ColumnSchema::new("Host", DataType::Text, false),
5056 ColumnSchema::new("db", DataType::Text, true),
5057 ColumnSchema::new("Command", DataType::Text, false),
5058 ColumnSchema::new("Time", DataType::Int, false),
5059 ColumnSchema::new("State", DataType::Text, true),
5060 ColumnSchema::new("Info", DataType::Text, true),
5061 ];
5062 let rows = alloc::vec![Row::new(alloc::vec![
5063 Value::Int(1),
5064 Value::Text("postgres".into()),
5065 Value::Text("localhost".into()),
5066 Value::Text("postgres".into()),
5067 Value::Text("Query".into()),
5068 Value::Int(0),
5069 Value::Text("executing".into()),
5070 Value::Text("SHOW PROCESSLIST".into()),
5071 ])];
5072 QueryResult::Rows { columns, rows }
5073 }
5074
5075 fn exec_show_databases(&self) -> QueryResult {
5082 let columns = alloc::vec![ColumnSchema::new("Database", DataType::Text, false)];
5083 let names = [
5084 "information_schema",
5085 "mysql",
5086 "performance_schema",
5087 "sys",
5088 "postgres",
5089 ];
5090 let rows: Vec<Row> = names
5091 .iter()
5092 .map(|n| Row::new(alloc::vec![Value::Text((*n).into())]))
5093 .collect();
5094 QueryResult::Rows { columns, rows }
5095 }
5096
5097 fn exec_show_columns(&self, table_name: &str) -> Result<QueryResult, EngineError> {
5100 let table =
5101 self.active_catalog()
5102 .get(table_name)
5103 .ok_or_else(|| StorageError::TableNotFound {
5104 name: table_name.into(),
5105 })?;
5106 let columns = alloc::vec![
5107 ColumnSchema::new("name", DataType::Text, false),
5108 ColumnSchema::new("type", DataType::Text, false),
5109 ColumnSchema::new("nullable", DataType::Bool, false),
5110 ];
5111 let rows: Vec<Row> = table
5112 .schema()
5113 .columns
5114 .iter()
5115 .map(|c| {
5116 Row::new(alloc::vec![
5117 Value::Text(c.name.clone()),
5118 Value::Text(alloc::format!("{}", c.ty)),
5119 Value::Bool(c.nullable),
5120 ])
5121 })
5122 .collect();
5123 Ok(QueryResult::Rows { columns, rows })
5124 }
5125
5126 fn exec_begin(&mut self) -> Result<QueryResult, EngineError> {
5127 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5128 if self.tx_catalogs.contains_key(&tx_id) {
5129 return Err(EngineError::TransactionAlreadyOpen);
5130 }
5131 self.tx_catalogs.insert(
5132 tx_id,
5133 TxState {
5134 catalog: self.catalog.clone(),
5135 savepoints: Vec::new(),
5136 },
5137 );
5138 Ok(QueryResult::CommandOk {
5139 affected: 0,
5140 modified_catalog: false,
5141 })
5142 }
5143
5144 fn exec_commit(&mut self) -> Result<QueryResult, EngineError> {
5145 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5146 let state = self
5147 .tx_catalogs
5148 .remove(&tx_id)
5149 .ok_or(EngineError::NoActiveTransaction)?;
5150 self.catalog = state.catalog;
5151 Ok(QueryResult::CommandOk {
5155 affected: 0,
5156 modified_catalog: true,
5157 })
5158 }
5159
5160 fn exec_rollback(&mut self) -> Result<QueryResult, EngineError> {
5161 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5162 if self.tx_catalogs.remove(&tx_id).is_none() {
5163 return Err(EngineError::NoActiveTransaction);
5164 }
5165 Ok(QueryResult::CommandOk {
5167 affected: 0,
5168 modified_catalog: false,
5169 })
5170 }
5171
5172 fn exec_savepoint(&mut self, name: String) -> Result<QueryResult, EngineError> {
5173 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5174 let state = self
5175 .tx_catalogs
5176 .get_mut(&tx_id)
5177 .ok_or(EngineError::NoActiveTransaction)?;
5178 state.savepoints.retain(|(n, _)| n != &name);
5182 let snapshot = state.catalog.clone();
5183 state.savepoints.push((name, snapshot));
5184 Ok(QueryResult::CommandOk {
5185 affected: 0,
5186 modified_catalog: false,
5187 })
5188 }
5189
5190 fn exec_rollback_to_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5191 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5192 let state = self
5193 .tx_catalogs
5194 .get_mut(&tx_id)
5195 .ok_or(EngineError::NoActiveTransaction)?;
5196 let pos = state
5197 .savepoints
5198 .iter()
5199 .rposition(|(n, _)| n == name)
5200 .ok_or_else(|| {
5201 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5202 })?;
5203 let snapshot = state.savepoints[pos].1.clone();
5207 state.savepoints.truncate(pos + 1);
5208 state.catalog = snapshot;
5209 Ok(QueryResult::CommandOk {
5210 affected: 0,
5211 modified_catalog: false,
5212 })
5213 }
5214
5215 fn exec_release_savepoint(&mut self, name: &str) -> Result<QueryResult, EngineError> {
5216 let tx_id = self.current_tx.ok_or(EngineError::NoActiveTransaction)?;
5217 let state = self
5218 .tx_catalogs
5219 .get_mut(&tx_id)
5220 .ok_or(EngineError::NoActiveTransaction)?;
5221 let pos = state
5222 .savepoints
5223 .iter()
5224 .rposition(|(n, _)| n == name)
5225 .ok_or_else(|| {
5226 EngineError::Unsupported(alloc::format!("savepoint not found: {name}"))
5227 })?;
5228 state.savepoints.truncate(pos);
5231 Ok(QueryResult::CommandOk {
5232 affected: 0,
5233 modified_catalog: false,
5234 })
5235 }
5236
5237 fn exec_alter_table(
5248 &mut self,
5249 s: spg_sql::ast::AlterTableStatement,
5250 ) -> Result<QueryResult, EngineError> {
5251 let table_name = s.name.clone();
5256 for target in s.targets {
5257 self.exec_alter_table_subaction(&table_name, target)?;
5258 }
5259 Ok(QueryResult::CommandOk {
5260 affected: 0,
5261 modified_catalog: !self.in_transaction(),
5262 })
5263 }
5264
5265 fn exec_alter_table_subaction(
5266 &mut self,
5267 table_name_outer: &str,
5268 target: spg_sql::ast::AlterTableTarget,
5269 ) -> Result<(), EngineError> {
5270 struct S<'a> {
5273 name: &'a str,
5274 }
5275 let s = S {
5276 name: table_name_outer,
5277 };
5278 match target {
5279 spg_sql::ast::AlterTableTarget::SetHotTierBytes(n) => {
5280 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5281 EngineError::Storage(StorageError::TableNotFound {
5282 name: s.name.into(),
5283 })
5284 })?;
5285 table.schema_mut().hot_tier_bytes = Some(n);
5286 }
5287 spg_sql::ast::AlterTableTarget::AddForeignKey(fk) => {
5288 let cols_snapshot = self
5293 .active_catalog()
5294 .get(s.name)
5295 .ok_or_else(|| {
5296 EngineError::Storage(StorageError::TableNotFound {
5297 name: s.name.into(),
5298 })
5299 })?
5300 .schema()
5301 .columns
5302 .clone();
5303 let storage_fk =
5304 resolve_foreign_key(s.name, &cols_snapshot, fk, self.active_catalog())?;
5305 let existing_rows: Vec<Vec<Value>> = self
5308 .active_catalog()
5309 .get(s.name)
5310 .expect("checked above")
5311 .rows()
5312 .iter()
5313 .map(|r| r.values.clone())
5314 .collect();
5315 enforce_fk_inserts(
5316 self.active_catalog(),
5317 s.name,
5318 core::slice::from_ref(&storage_fk),
5319 &existing_rows,
5320 )?;
5321 let table = self
5323 .active_catalog_mut()
5324 .get_mut(s.name)
5325 .expect("checked above");
5326 if let Some(name) = &storage_fk.name
5327 && table
5328 .schema()
5329 .foreign_keys
5330 .iter()
5331 .any(|f| f.name.as_ref() == Some(name))
5332 {
5333 return Err(EngineError::Unsupported(alloc::format!(
5334 "ALTER TABLE ADD CONSTRAINT: a constraint named {name:?} already exists"
5335 )));
5336 }
5337 table.schema_mut().foreign_keys.push(storage_fk);
5338 }
5339 spg_sql::ast::AlterTableTarget::DropForeignKey { name, if_exists } => {
5340 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5341 EngineError::Storage(StorageError::TableNotFound {
5342 name: s.name.into(),
5343 })
5344 })?;
5345 let fks = &mut table.schema_mut().foreign_keys;
5346 let before = fks.len();
5347 fks.retain(|f| f.name.as_ref() != Some(&name));
5348 if fks.len() == before && !if_exists {
5349 return Err(EngineError::Unsupported(alloc::format!(
5350 "ALTER TABLE DROP CONSTRAINT: no FK named {name:?} on {:?}",
5351 s.name
5352 )));
5353 }
5354 }
5356 spg_sql::ast::AlterTableTarget::AddColumn {
5357 column,
5358 if_not_exists,
5359 } => {
5360 let clock = self.clock;
5365 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5366 EngineError::Storage(StorageError::TableNotFound {
5367 name: s.name.into(),
5368 })
5369 })?;
5370 if table
5371 .schema()
5372 .columns
5373 .iter()
5374 .any(|c| c.name.eq_ignore_ascii_case(&column.name))
5375 {
5376 if if_not_exists {
5377 return Ok(());
5378 }
5379 return Err(EngineError::Unsupported(alloc::format!(
5380 "ALTER TABLE ADD COLUMN: column {:?} already exists on {:?}",
5381 column.name,
5382 s.name
5383 )));
5384 }
5385 let col_name = column.name.clone();
5386 let nullable = column.nullable;
5387 let has_default = column.default.is_some() || column.auto_increment;
5388 let col_schema = column_def_to_schema(column)?;
5389 let row_count = table.row_count();
5390 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
5397 resolve_column_default_free(&col_schema, clock)?
5398 } else if nullable || row_count == 0 {
5399 Value::Null
5400 } else {
5401 return Err(EngineError::Unsupported(alloc::format!(
5402 "ALTER TABLE ADD COLUMN {col_name:?}: NOT NULL column requires DEFAULT \
5403 when the table has existing rows"
5404 )));
5405 };
5406 table.add_column(col_schema, fill_value);
5407 }
5408 spg_sql::ast::AlterTableTarget::AlterColumnType {
5409 column,
5410 new_type,
5411 using,
5412 } => {
5413 let new_data_type = column_type_to_data_type(new_type);
5419 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5420 EngineError::Storage(StorageError::TableNotFound {
5421 name: s.name.into(),
5422 })
5423 })?;
5424 let col_pos = table
5425 .schema()
5426 .columns
5427 .iter()
5428 .position(|c| c.name.eq_ignore_ascii_case(&column))
5429 .ok_or_else(|| {
5430 EngineError::Unsupported(alloc::format!(
5431 "ALTER COLUMN TYPE: column {column:?} not found on {:?}",
5432 s.name
5433 ))
5434 })?;
5435 let schema_cols = table.schema().columns.clone();
5436 let ctx = eval::EvalContext::new(&schema_cols, None);
5437 let mut new_values: alloc::vec::Vec<Value> =
5438 alloc::vec::Vec::with_capacity(table.row_count());
5439 for row in table.rows().iter() {
5440 let raw = match &using {
5441 Some(expr) => eval::eval_expr(expr, row, &ctx).map_err(|e| {
5442 EngineError::Unsupported(alloc::format!(
5443 "ALTER COLUMN TYPE: USING expression failed: {e:?}"
5444 ))
5445 })?,
5446 None => row.values.get(col_pos).cloned().unwrap_or(Value::Null),
5447 };
5448 let coerced = coerce_value(raw, new_data_type, &column, col_pos)?;
5449 new_values.push(coerced);
5450 }
5451 table.schema_mut().columns[col_pos].ty = new_data_type;
5452 for (i, v) in new_values.into_iter().enumerate() {
5453 let mut row_values = table
5454 .rows()
5455 .get(i)
5456 .expect("bounds-checked above")
5457 .values
5458 .clone();
5459 row_values[col_pos] = v;
5460 table.update_row(i, row_values)?;
5461 }
5462 }
5463 spg_sql::ast::AlterTableTarget::AddTableConstraint(tc) => {
5464 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5470 EngineError::Storage(StorageError::TableNotFound {
5471 name: s.name.into(),
5472 })
5473 })?;
5474 let is_pk = matches!(tc, spg_sql::ast::TableConstraint::PrimaryKey { .. });
5475 match tc {
5476 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. }
5477 | spg_sql::ast::TableConstraint::Unique { columns, .. } => {
5478 let positions: Vec<usize> = columns
5479 .iter()
5480 .map(|c| {
5481 table
5482 .schema()
5483 .columns
5484 .iter()
5485 .position(|sc| sc.name.eq_ignore_ascii_case(c))
5486 .ok_or_else(|| {
5487 EngineError::Unsupported(alloc::format!(
5488 "ALTER TABLE ADD CONSTRAINT: column {c:?} not found on {:?}",
5489 s.name
5490 ))
5491 })
5492 })
5493 .collect::<Result<Vec<_>, _>>()?;
5494 let already = table
5498 .schema()
5499 .uniqueness_constraints
5500 .iter()
5501 .any(|u| u.columns == positions);
5502 if !already {
5503 table.schema_mut().uniqueness_constraints.push(
5504 spg_storage::UniquenessConstraint {
5505 is_primary_key: is_pk,
5506 columns: positions.clone(),
5507 nulls_not_distinct: false,
5508 },
5509 );
5510 if is_pk {
5512 for p in &positions {
5513 if let Some(c) = table.schema_mut().columns.get_mut(*p) {
5514 c.nullable = false;
5515 }
5516 }
5517 }
5518 let leading = &columns[0];
5521 let already_idx = table.indices().iter().any(|idx| {
5522 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5523 && table.schema().columns[idx.column_position].name == *leading
5524 });
5525 if !already_idx {
5526 let suffix = if is_pk { "pkey" } else { "key" };
5527 let idx_name = alloc::format!("{}_{leading}_{suffix}", s.name);
5528 let _ = table.add_index(idx_name, leading);
5529 }
5530 }
5531 }
5532 spg_sql::ast::TableConstraint::Check { expr, .. } => {
5533 table.schema_mut().checks.push(alloc::format!("{expr}"));
5534 }
5535 spg_sql::ast::TableConstraint::Index { name, columns } => {
5536 let leading = &columns[0];
5542 let already_idx = table.indices().iter().any(|idx| {
5543 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
5544 && table.schema().columns[idx.column_position].name == *leading
5545 });
5546 if !already_idx {
5547 let idx_name = name
5548 .clone()
5549 .unwrap_or_else(|| alloc::format!("{}_{leading}_idx", s.name));
5550 let _ = table.add_index(idx_name, leading);
5551 }
5552 }
5553 spg_sql::ast::TableConstraint::FulltextIndex { name, columns } => {
5554 for (k, col) in columns.iter().enumerate() {
5562 let already_idx = table.indices().iter().any(|idx| {
5563 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
5564 && table.schema().columns[idx.column_position].name == *col
5565 });
5566 if already_idx {
5567 continue;
5568 }
5569 let idx_name = match (&name, columns.len(), k) {
5570 (Some(n), 1, _) => n.clone(),
5571 (Some(n), _, k) => alloc::format!("{n}_{k}"),
5572 (None, _, _) => {
5573 alloc::format!("{}_{col}_ftidx", s.name)
5574 }
5575 };
5576 let _ = table.add_gin_fulltext_index(idx_name, col);
5577 }
5578 }
5579 }
5580 }
5581 spg_sql::ast::AlterTableTarget::DropColumn {
5582 column,
5583 if_exists,
5584 cascade,
5585 } => {
5586 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5593 EngineError::Storage(StorageError::TableNotFound {
5594 name: s.name.into(),
5595 })
5596 })?;
5597 let col_pos = match table
5598 .schema()
5599 .columns
5600 .iter()
5601 .position(|c| c.name.eq_ignore_ascii_case(&column))
5602 {
5603 Some(p) => p,
5604 None => {
5605 if if_exists {
5606 return Ok(());
5607 }
5608 return Err(EngineError::Unsupported(alloc::format!(
5609 "ALTER TABLE DROP COLUMN: column {column:?} not found on {:?}",
5610 s.name
5611 )));
5612 }
5613 };
5614 let dependent_fks: Vec<usize> = table
5617 .schema()
5618 .foreign_keys
5619 .iter()
5620 .enumerate()
5621 .filter_map(|(i, fk)| {
5622 if fk.local_columns.contains(&col_pos) {
5623 Some(i)
5624 } else {
5625 None
5626 }
5627 })
5628 .collect();
5629 if !dependent_fks.is_empty() && !cascade {
5630 return Err(EngineError::Unsupported(alloc::format!(
5631 "ALTER TABLE DROP COLUMN {column:?}: column has FK dependents; \
5632 use DROP COLUMN ... CASCADE to remove them"
5633 )));
5634 }
5635 if cascade {
5637 let mut sorted = dependent_fks.clone();
5639 sorted.sort();
5640 sorted.reverse();
5641 let fks = &mut table.schema_mut().foreign_keys;
5642 for i in sorted {
5643 fks.remove(i);
5644 }
5645 }
5646 table.drop_column(col_pos);
5649 }
5650 spg_sql::ast::AlterTableTarget::SetTriggerEnabled { which, enabled } => {
5651 let table_name = s.name.to_string();
5659 let trigs = self.active_catalog_mut().triggers_mut();
5660 let mut touched = false;
5661 for t in trigs.iter_mut() {
5662 if !t.table.eq_ignore_ascii_case(&table_name) {
5663 continue;
5664 }
5665 match &which {
5666 spg_sql::ast::TriggerSelector::All => {
5667 t.enabled = enabled;
5668 touched = true;
5669 }
5670 spg_sql::ast::TriggerSelector::Named(name) => {
5671 if t.name.eq_ignore_ascii_case(name) {
5672 t.enabled = enabled;
5673 touched = true;
5674 }
5675 }
5676 }
5677 }
5678 if !touched {
5684 if let spg_sql::ast::TriggerSelector::Named(name) = &which {
5685 return Err(EngineError::Unsupported(alloc::format!(
5686 "ALTER TABLE {table_name:?} {} TRIGGER {name:?}: no such trigger on table",
5687 if enabled { "ENABLE" } else { "DISABLE" },
5688 )));
5689 }
5690 }
5691 }
5692 spg_sql::ast::AlterTableTarget::RenameTable { new } => {
5693 let old = s.name.to_string();
5700 self.active_catalog_mut()
5701 .rename_table(&old, &new)
5702 .map_err(EngineError::Storage)?;
5703 }
5704 spg_sql::ast::AlterTableTarget::RenameColumn { old, new } => {
5705 let table = self.active_catalog_mut().get_mut(s.name).ok_or_else(|| {
5719 EngineError::Storage(StorageError::TableNotFound {
5720 name: s.name.into(),
5721 })
5722 })?;
5723 let col_pos = table
5724 .schema()
5725 .columns
5726 .iter()
5727 .position(|c| c.name.eq_ignore_ascii_case(&old))
5728 .ok_or_else(|| {
5729 EngineError::Unsupported(alloc::format!(
5730 "ALTER TABLE RENAME COLUMN: column {old:?} not found on {:?}",
5731 s.name
5732 ))
5733 })?;
5734 if table
5736 .schema()
5737 .columns
5738 .iter()
5739 .enumerate()
5740 .any(|(i, c)| i != col_pos && c.name.eq_ignore_ascii_case(&new))
5741 {
5742 return Err(EngineError::Unsupported(alloc::format!(
5743 "ALTER TABLE RENAME COLUMN: column {new:?} already exists on {:?}",
5744 s.name
5745 )));
5746 }
5747 if old.eq_ignore_ascii_case(&new) {
5751 return Ok(());
5752 }
5753 table.rename_column(col_pos, &new);
5754 let n_cols = table.schema().columns.len();
5760 for i in 0..n_cols {
5761 let rt = table.schema().columns[i].runtime_default.clone();
5762 if let Some(src) = rt {
5763 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5764 table.schema_mut().columns[i].runtime_default = Some(rewritten);
5765 }
5766 }
5767 let checks = table.schema().checks.clone();
5769 let mut new_checks = Vec::with_capacity(checks.len());
5770 for chk in checks {
5771 new_checks.push(rewrite_column_in_source(&chk, &old, &new)?);
5772 }
5773 table.schema_mut().checks = new_checks;
5774 let n_idx = table.indices().len();
5776 for i in 0..n_idx {
5777 let pred = table.indices()[i].partial_predicate.clone();
5778 if let Some(src) = pred {
5779 let rewritten = rewrite_column_in_source(&src, &old, &new)?;
5780 table.set_partial_predicate(i, Some(rewritten));
5784 }
5785 }
5786 let table_name = s.name.to_string();
5789 for trig in self.active_catalog_mut().triggers_mut() {
5790 if !trig.table.eq_ignore_ascii_case(&table_name) {
5791 continue;
5792 }
5793 for c in &mut trig.update_columns {
5794 if c.eq_ignore_ascii_case(&old) {
5795 *c = new.clone();
5796 }
5797 }
5798 }
5799 }
5800 }
5801 Ok(())
5802 }
5803
5804 fn exec_alter_index(
5805 &mut self,
5806 stmt: spg_sql::ast::AlterIndexStatement,
5807 ) -> Result<QueryResult, EngineError> {
5808 let spg_sql::ast::AlterIndexStatement {
5812 name: idx_name,
5813 target,
5814 } = stmt;
5815 if let spg_sql::ast::AlterIndexTarget::Rename { new, if_exists } = target {
5819 let renamed = self.active_catalog_mut().rename_index(&idx_name, &new);
5820 return match renamed {
5821 Ok(()) => Ok(QueryResult::CommandOk {
5822 affected: 0,
5823 modified_catalog: !self.in_transaction(),
5824 }),
5825 Err(StorageError::IndexNotFound { .. }) if if_exists => {
5826 Ok(QueryResult::CommandOk {
5827 affected: 0,
5828 modified_catalog: false,
5829 })
5830 }
5831 Err(e) => Err(EngineError::Storage(e)),
5832 };
5833 }
5834 let spg_sql::ast::AlterIndexTarget::Rebuild { encoding } = target else {
5835 unreachable!("Rename branch returned above");
5836 };
5837 let target = encoding.map(|e| match e {
5838 SqlVecEncoding::F32 => VecEncoding::F32,
5839 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
5840 SqlVecEncoding::F16 => VecEncoding::F16,
5841 });
5842 let table_name = {
5847 let cat = self.active_catalog();
5848 let mut found: Option<String> = None;
5849 for tname in cat.table_names() {
5850 if let Some(t) = cat.get(&tname)
5851 && t.indices().iter().any(|i| i.name == idx_name)
5852 {
5853 found = Some(tname);
5854 break;
5855 }
5856 }
5857 found.ok_or_else(|| {
5858 EngineError::Storage(StorageError::IndexNotFound {
5859 name: idx_name.clone(),
5860 })
5861 })?
5862 };
5863 let table = self
5864 .active_catalog_mut()
5865 .get_mut(&table_name)
5866 .expect("table found above");
5867 table.rebuild_nsw_index(&idx_name, target)?;
5868 self.plan_cache.evict_referencing(&table_name);
5871 Ok(QueryResult::CommandOk {
5872 affected: 0,
5873 modified_catalog: !self.in_transaction(),
5874 })
5875 }
5876
5877 fn exec_create_index(
5878 &mut self,
5879 stmt: CreateIndexStatement,
5880 ) -> Result<QueryResult, EngineError> {
5881 let table = self
5882 .active_catalog_mut()
5883 .get_mut(&stmt.table)
5884 .ok_or_else(|| {
5885 EngineError::Storage(StorageError::TableNotFound {
5886 name: stmt.table.clone(),
5887 })
5888 })?;
5889 if stmt.if_not_exists && table.indices().iter().any(|i| i.name == stmt.name) {
5891 return Ok(QueryResult::CommandOk {
5892 affected: 0,
5893 modified_catalog: false,
5894 });
5895 }
5896 let _ = &stmt.extra_columns; let table_name = stmt.table.clone();
5903 let included_positions: Vec<usize> = if stmt.included_columns.is_empty() {
5907 Vec::new()
5908 } else {
5909 let schema = table.schema();
5910 stmt.included_columns
5911 .iter()
5912 .map(|c| {
5913 schema.column_position(c).ok_or_else(|| {
5914 EngineError::Storage(StorageError::ColumnNotFound { column: c.clone() })
5915 })
5916 })
5917 .collect::<Result<Vec<_>, _>>()?
5918 };
5919 match stmt.method {
5920 IndexMethod::BTree => table.add_index(stmt.name.clone(), &stmt.column)?,
5921 IndexMethod::Hnsw => {
5922 if !included_positions.is_empty() {
5923 return Err(EngineError::Unsupported(
5924 "INCLUDE columns are not supported on HNSW indexes".into(),
5925 ));
5926 }
5927 table.add_nsw_index(stmt.name.clone(), &stmt.column, spg_storage::NSW_DEFAULT_M)?;
5928 }
5929 IndexMethod::Brin => {
5931 if !included_positions.is_empty() {
5932 return Err(EngineError::Unsupported(
5933 "INCLUDE columns are not supported on BRIN indexes".into(),
5934 ));
5935 }
5936 table.add_brin_index(stmt.name.clone(), &stmt.column)?;
5937 }
5938 IndexMethod::Gin => {
5946 if !included_positions.is_empty() {
5947 return Err(EngineError::Unsupported(
5948 "INCLUDE columns are not supported on GIN indexes".into(),
5949 ));
5950 }
5951 let col_pos = table
5952 .schema()
5953 .column_position(&stmt.column)
5954 .ok_or_else(|| {
5955 EngineError::Storage(StorageError::ColumnNotFound {
5956 column: stmt.column.clone(),
5957 })
5958 })?;
5959 let col_ty = table.schema().columns[col_pos].ty;
5960 let is_trgm = stmt
5966 .opclass
5967 .as_deref()
5968 .is_some_and(|op| op.eq_ignore_ascii_case("gin_trgm_ops"));
5969 if is_trgm
5970 && matches!(
5971 col_ty,
5972 spg_storage::DataType::Text | spg_storage::DataType::Varchar(_)
5973 )
5974 {
5975 table
5976 .add_gin_trgm_index(stmt.name.clone(), &stmt.column)
5977 .map_err(EngineError::Storage)?;
5978 } else if col_ty == spg_storage::DataType::TsVector {
5979 table
5980 .add_gin_index(stmt.name.clone(), &stmt.column)
5981 .map_err(EngineError::Storage)?;
5982 } else {
5983 table.add_index(stmt.name.clone(), &stmt.column)?;
5989 }
5990 }
5991 }
5992 if !included_positions.is_empty()
5993 && let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name)
5994 {
5995 idx.included_columns = included_positions;
5996 }
5997 if let Some(pred_expr) = &stmt.partial_predicate {
6005 let canonical = pred_expr.to_string();
6006 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6018 idx.partial_predicate = Some(canonical);
6019 }
6020 }
6021 if let Some(key_expr) = &stmt.expression {
6029 if matches!(
6030 stmt.method,
6031 IndexMethod::Hnsw | IndexMethod::Brin | IndexMethod::Gin
6032 ) {
6033 return Err(EngineError::Unsupported(
6034 "Expression keys are not supported on HNSW or BRIN indexes".into(),
6035 ));
6036 }
6037 let canonical = key_expr.to_string();
6038 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6039 idx.expression = Some(canonical);
6040 }
6041 }
6042 if stmt.is_unique {
6051 let mut extra_positions: alloc::vec::Vec<usize> = alloc::vec::Vec::new();
6052 for col_name in &stmt.extra_columns {
6053 let pos = table
6054 .schema()
6055 .columns
6056 .iter()
6057 .position(|c| c.name.eq_ignore_ascii_case(col_name))
6058 .ok_or_else(|| {
6059 EngineError::Unsupported(alloc::format!(
6060 "UNIQUE INDEX {:?}: extra column {col_name:?} not in table {:?}",
6061 stmt.name,
6062 stmt.table
6063 ))
6064 })?;
6065 extra_positions.push(pos);
6066 }
6067 if let Some(idx) = table.indices_mut().iter_mut().find(|i| i.name == stmt.name) {
6068 idx.is_unique = true;
6069 idx.extra_column_positions = extra_positions;
6070 }
6071 let snapshot_indices = table.indices().to_vec();
6076 let snapshot_rows: alloc::vec::Vec<spg_storage::Row> =
6077 table.rows().iter().cloned().collect();
6078 let snapshot_schema = table.schema().clone();
6079 let idx_ref = snapshot_indices
6080 .iter()
6081 .find(|i| i.name == stmt.name)
6082 .expect("just-added index");
6083 check_existing_unique_violation(idx_ref, &snapshot_schema, &snapshot_rows)?;
6084 }
6085 self.plan_cache.evict_referencing(&table_name);
6088 Ok(QueryResult::CommandOk {
6089 affected: 0,
6090 modified_catalog: !self.in_transaction(),
6091 })
6092 }
6093
6094 fn reconcile_table_if_not_exists(
6103 &mut self,
6104 stmt: CreateTableStatement,
6105 ) -> Result<QueryResult, EngineError> {
6106 let table_name = stmt.name.clone();
6107 let clock = self.clock;
6108 let existing_col_names: alloc::collections::BTreeSet<String> = self
6109 .active_catalog()
6110 .get(&table_name)
6111 .expect("checked above")
6112 .schema()
6113 .columns
6114 .iter()
6115 .map(|c| c.name.to_ascii_lowercase())
6116 .collect();
6117 let row_count = self
6118 .active_catalog()
6119 .get(&table_name)
6120 .expect("checked above")
6121 .row_count();
6122 let new_columns: alloc::vec::Vec<spg_sql::ast::ColumnDef> = stmt
6124 .columns
6125 .iter()
6126 .filter(|c| !existing_col_names.contains(&c.name.to_ascii_lowercase()))
6127 .cloned()
6128 .collect();
6129 for col_def in new_columns {
6130 let col_name = col_def.name.clone();
6131 let nullable = col_def.nullable;
6132 let has_default = col_def.default.is_some() || col_def.auto_increment;
6133 let col_schema = column_def_to_schema(col_def)?;
6134 let fill_value: Value = if has_default || col_schema.runtime_default.is_some() {
6135 resolve_column_default_free(&col_schema, clock)?
6136 } else if nullable || row_count == 0 {
6137 Value::Null
6138 } else {
6139 return Err(EngineError::Unsupported(alloc::format!(
6140 "CREATE TABLE IF NOT EXISTS {table_name:?}: reconciling \
6141 column {col_name:?} requires DEFAULT (existing rows would violate NOT NULL)"
6142 )));
6143 };
6144 let table = self
6145 .active_catalog_mut()
6146 .get_mut(&table_name)
6147 .expect("checked above");
6148 table.add_column(col_schema, fill_value);
6149 }
6150 let table_cols_now = self
6154 .active_catalog()
6155 .get(&table_name)
6156 .expect("checked above")
6157 .schema()
6158 .columns
6159 .clone();
6160 for fk in stmt.foreign_keys {
6161 let all_resolved = fk.columns.iter().all(|c| {
6165 table_cols_now
6166 .iter()
6167 .any(|sc| sc.name.eq_ignore_ascii_case(c))
6168 });
6169 if !all_resolved {
6170 continue;
6171 }
6172 let already_present = {
6173 let table = self
6174 .active_catalog()
6175 .get(&table_name)
6176 .expect("checked above");
6177 table.schema().foreign_keys.iter().any(|f| {
6178 f.parent_table.eq_ignore_ascii_case(&fk.parent_table)
6179 && f.local_columns.len() == fk.columns.len()
6180 })
6181 };
6182 if already_present {
6183 continue;
6184 }
6185 let storage_fk =
6186 resolve_foreign_key(&table_name, &table_cols_now, fk, self.active_catalog())?;
6187 let table = self
6188 .active_catalog_mut()
6189 .get_mut(&table_name)
6190 .expect("checked above");
6191 table.schema_mut().foreign_keys.push(storage_fk);
6192 }
6193 Ok(QueryResult::CommandOk {
6194 affected: 0,
6195 modified_catalog: !self.in_transaction(),
6196 })
6197 }
6198
6199 fn exec_drop_table(
6201 &mut self,
6202 names: Vec<String>,
6203 if_exists: bool,
6204 ) -> Result<QueryResult, EngineError> {
6205 for name in names {
6206 let dropped = self.active_catalog_mut().drop_table(&name);
6207 if !dropped && !if_exists {
6208 return Err(EngineError::Storage(StorageError::TableNotFound { name }));
6209 }
6210 }
6211 Ok(QueryResult::CommandOk {
6212 affected: 0,
6213 modified_catalog: !self.in_transaction(),
6214 })
6215 }
6216
6217 fn exec_drop_index(
6219 &mut self,
6220 name: String,
6221 if_exists: bool,
6222 ) -> Result<QueryResult, EngineError> {
6223 let dropped = self.active_catalog_mut().drop_named_index(&name);
6224 if !dropped && !if_exists {
6225 return Err(EngineError::Storage(StorageError::IndexNotFound { name }));
6226 }
6227 Ok(QueryResult::CommandOk {
6228 affected: 0,
6229 modified_catalog: !self.in_transaction(),
6230 })
6231 }
6232
6233 fn exec_create_table(
6234 &mut self,
6235 stmt: CreateTableStatement,
6236 ) -> Result<QueryResult, EngineError> {
6237 if stmt.if_not_exists && self.active_catalog().get(&stmt.name).is_some() {
6238 return Ok(QueryResult::CommandOk {
6257 affected: 0,
6258 modified_catalog: false,
6259 });
6260 }
6261 let table_name = stmt.name.clone();
6262 let inline_pk_columns: Vec<String> = stmt
6266 .columns
6267 .iter()
6268 .filter(|c| c.is_primary_key)
6269 .map(|c| c.name.clone())
6270 .collect();
6271 let cols = stmt
6277 .columns
6278 .into_iter()
6279 .map(column_def_to_schema)
6280 .collect::<Result<Vec<_>, _>>()?;
6281 let mut cols = cols;
6290 for col in cols.iter_mut() {
6291 let Some(name) = col.user_enum_type.take() else {
6292 continue;
6293 };
6294 let cat = self.active_catalog();
6295 if cat.enum_types().contains_key(&name) {
6296 col.user_enum_type = Some(name);
6297 continue;
6298 }
6299 if let Some(dom) = cat.domain_types().get(&name) {
6300 col.ty = dom.base_type;
6301 col.user_domain_type = Some(name);
6302 if !dom.nullable {
6303 col.nullable = false;
6304 }
6305 continue;
6306 }
6307 return Err(EngineError::Unsupported(alloc::format!(
6308 "column {:?}: unknown column type {:?} (not a built-in, ENUM, or DOMAIN)",
6309 col.name,
6310 name
6311 )));
6312 }
6313 for tc in &stmt.table_constraints {
6314 if let spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } = tc {
6315 for col_name in columns {
6316 if let Some(col) = cols.iter_mut().find(|c| c.name == *col_name) {
6317 col.nullable = false;
6318 }
6319 }
6320 }
6321 }
6322 let mut fks: Vec<spg_storage::ForeignKeyConstraint> =
6329 Vec::with_capacity(stmt.foreign_keys.len());
6330 for fk in stmt.foreign_keys {
6331 let needs_parent = !fk.parent_table.eq_ignore_ascii_case(&table_name);
6338 if !self.foreign_key_checks
6339 && needs_parent
6340 && self.active_catalog().get(&fk.parent_table).is_none()
6341 {
6342 self.pending_foreign_keys.push((table_name.clone(), fk));
6343 continue;
6344 }
6345 fks.push(resolve_foreign_key(
6346 &table_name,
6347 &cols,
6348 fk,
6349 self.active_catalog(),
6350 )?);
6351 }
6352 let mut schema = TableSchema::new(table_name.clone(), cols);
6353 schema.foreign_keys = fks;
6354 let mut uc_storage: Vec<spg_storage::UniquenessConstraint> = Vec::new();
6358 let mut check_exprs: Vec<String> = Vec::new();
6359 for tc in &stmt.table_constraints {
6360 let (is_pk, names, nnd) = match tc {
6361 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6362 (true, columns.clone(), false)
6363 }
6364 spg_sql::ast::TableConstraint::Unique {
6365 columns,
6366 nulls_not_distinct,
6367 ..
6368 } => (false, columns.clone(), *nulls_not_distinct),
6369 spg_sql::ast::TableConstraint::Check { expr, .. } => {
6370 check_exprs.push(alloc::format!("{expr}"));
6373 continue;
6374 }
6375 spg_sql::ast::TableConstraint::Index { .. } => continue,
6381 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6385 };
6386 let mut positions = Vec::with_capacity(names.len());
6387 for n in &names {
6388 let pos = schema
6389 .columns
6390 .iter()
6391 .position(|c| c.name == *n)
6392 .ok_or_else(|| {
6393 EngineError::Unsupported(alloc::format!(
6394 "table constraint references unknown column {n:?}"
6395 ))
6396 })?;
6397 positions.push(pos);
6398 }
6399 uc_storage.push(spg_storage::UniquenessConstraint {
6400 is_primary_key: is_pk,
6401 columns: positions,
6402 nulls_not_distinct: nnd,
6403 });
6404 }
6405 schema.uniqueness_constraints = uc_storage.clone();
6406 schema.checks = check_exprs;
6407 self.active_catalog_mut().create_table(schema)?;
6408 let table = self
6412 .active_catalog_mut()
6413 .get_mut(&table_name)
6414 .expect("just created");
6415 for (i, col_name) in inline_pk_columns.iter().enumerate() {
6416 let idx_name = if inline_pk_columns.len() == 1 {
6417 alloc::format!("{table_name}_pkey")
6418 } else {
6419 alloc::format!("{table_name}_pkey_{i}")
6420 };
6421 if let Err(e) = table.add_index(idx_name, col_name) {
6422 return Err(EngineError::Storage(e));
6423 }
6424 }
6425 for (i, tc) in stmt.table_constraints.iter().enumerate() {
6426 if let spg_sql::ast::TableConstraint::FulltextIndex { name, columns } = tc {
6431 for (k, col) in columns.iter().enumerate() {
6432 let already = table.indices().iter().any(|idx| {
6433 matches!(idx.kind, spg_storage::IndexKind::GinFulltext(_))
6434 && table.schema().columns[idx.column_position].name == *col
6435 });
6436 if already {
6437 continue;
6438 }
6439 let idx_name = match (name.as_ref(), columns.len(), k) {
6440 (Some(n), 1, _) => n.clone(),
6441 (Some(n), _, k) => alloc::format!("{n}_{k}"),
6442 (None, _, _) => {
6443 alloc::format!("{table_name}_{col}_ftidx")
6444 }
6445 };
6446 if let Err(e) = table.add_gin_fulltext_index(idx_name, col) {
6447 return Err(EngineError::Storage(e));
6448 }
6449 }
6450 continue;
6451 }
6452 let (suffix, names, explicit_name): (&str, &Vec<String>, Option<&String>) = match tc {
6456 spg_sql::ast::TableConstraint::PrimaryKey { columns, .. } => {
6457 ("pkey", columns, None)
6458 }
6459 spg_sql::ast::TableConstraint::Unique { columns, .. } => ("key", columns, None),
6460 spg_sql::ast::TableConstraint::Index { name, columns } => {
6461 ("idx", columns, name.as_ref())
6462 }
6463 spg_sql::ast::TableConstraint::Check { .. } => continue,
6464 spg_sql::ast::TableConstraint::FulltextIndex { .. } => continue,
6466 };
6467 let leading = &names[0];
6468 let already = table.indices().iter().any(|idx| {
6471 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
6472 && table.schema().columns[idx.column_position].name == *leading
6473 });
6474 if already {
6475 continue;
6476 }
6477 let idx_name = if let Some(n) = explicit_name {
6478 n.clone()
6479 } else if names.len() == 1 {
6480 alloc::format!("{table_name}_{leading}_{suffix}")
6481 } else {
6482 alloc::format!("{table_name}_{leading}_{suffix}_{i}")
6483 };
6484 if let Err(e) = table.add_index(idx_name, leading) {
6485 return Err(EngineError::Storage(e));
6486 }
6487 }
6488 Ok(QueryResult::CommandOk {
6489 affected: 0,
6490 modified_catalog: !self.in_transaction(),
6491 })
6492 }
6493
6494 fn exec_insert(&mut self, mut stmt: InsertStatement) -> Result<QueryResult, EngineError> {
6495 for tuple in &mut stmt.rows {
6503 for cell in tuple.iter_mut() {
6504 self.resolve_sequence_calls_in_expr(cell)?;
6505 }
6506 }
6507 if let Some(select) = stmt.select_source.clone() {
6512 let select_result = self.exec_select_cancel(&select, CancelToken::none())?;
6513 let rows = match select_result {
6514 QueryResult::Rows { rows, .. } => rows,
6515 other => {
6516 return Err(EngineError::Unsupported(alloc::format!(
6517 "INSERT … SELECT: inner statement produced {other:?} instead of a row set"
6518 )));
6519 }
6520 };
6521 let mut materialised: Vec<Vec<Expr>> = Vec::with_capacity(rows.len());
6522 for row in rows {
6523 let mut tuple: Vec<Expr> = Vec::with_capacity(row.values.len());
6524 for v in row.values {
6525 tuple.push(value_to_literal_expr_permissive(v)?);
6526 }
6527 materialised.push(tuple);
6528 }
6529 let recurse = InsertStatement {
6530 table: stmt.table,
6531 columns: stmt.columns,
6532 rows: materialised,
6533 select_source: None,
6534 on_conflict: stmt.on_conflict,
6535 returning: stmt.returning,
6536 };
6537 return self.exec_insert(recurse);
6538 }
6539 let clock = self.clock;
6543 let before_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "BEFORE");
6549 let after_insert_triggers = self.snapshot_row_triggers(&stmt.table, "INSERT", "AFTER");
6550 let trigger_session_cfg: Option<alloc::string::String> = self
6551 .session_params
6552 .get("default_text_search_config")
6553 .cloned();
6554 let pre_borrow_column_meta: Vec<ColumnSchema> = {
6560 let preview_table = self.active_catalog().get(&stmt.table).ok_or_else(|| {
6561 EngineError::Storage(StorageError::TableNotFound {
6562 name: stmt.table.clone(),
6563 })
6564 })?;
6565 preview_table.schema().columns.clone()
6566 };
6567 let enum_label_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6568 pre_borrow_column_meta
6569 .iter()
6570 .enumerate()
6571 .filter_map(|(i, col)| {
6572 if let Some(inline) = &col.inline_enum_variants {
6577 return Some((i, inline.clone()));
6578 }
6579 col.user_enum_type.as_ref().and_then(|ename| {
6580 self.active_catalog()
6581 .enum_types()
6582 .get(ename)
6583 .map(|e| (i, e.labels.clone()))
6584 })
6585 })
6586 .collect();
6587 let set_variant_lookup: alloc::collections::BTreeMap<usize, Vec<String>> =
6592 pre_borrow_column_meta
6593 .iter()
6594 .enumerate()
6595 .filter_map(|(i, col)| col.inline_set_variants.as_ref().map(|vs| (i, vs.clone())))
6596 .collect();
6597 let table = self
6598 .active_catalog_mut()
6599 .get_mut(&stmt.table)
6600 .ok_or_else(|| {
6601 EngineError::Storage(StorageError::TableNotFound {
6602 name: stmt.table.clone(),
6603 })
6604 })?;
6605 let column_meta: Vec<ColumnSchema> = table.schema().columns.clone();
6611 let schema_cols_len = column_meta.len();
6612 let tuple_pos: Option<Vec<Option<usize>>> = match &stmt.columns {
6616 None => None, Some(cols) => {
6618 let mut map = alloc::vec![None; schema_cols_len];
6619 for (j, name) in cols.iter().enumerate() {
6620 let idx = column_meta
6621 .iter()
6622 .position(|c| c.name == *name)
6623 .ok_or_else(|| {
6624 EngineError::Eval(EvalError::ColumnNotFound { name: name.clone() })
6625 })?;
6626 if map[idx].is_some() {
6627 return Err(EngineError::Storage(StorageError::ArityMismatch {
6628 expected: schema_cols_len,
6629 actual: cols.len(),
6630 }));
6631 }
6632 map[idx] = Some(j);
6633 }
6634 for (i, col) in column_meta.iter().enumerate() {
6638 if map[i].is_none()
6639 && !col.nullable
6640 && col.default.is_none()
6641 && col.runtime_default.is_none()
6642 && !col.auto_increment
6643 {
6644 return Err(EngineError::Storage(StorageError::NullInNotNull {
6645 column: col.name.clone(),
6646 }));
6647 }
6648 }
6649 Some(map)
6650 }
6651 };
6652 let expected_tuple_len = stmt.columns.as_ref().map_or(schema_cols_len, Vec::len);
6653 let fks = table.schema().foreign_keys.clone();
6659 let mut affected = 0usize;
6660 let mut all_values: Vec<Vec<Value>> = Vec::with_capacity(stmt.rows.len());
6663 for tuple in stmt.rows {
6664 if tuple.len() != expected_tuple_len {
6665 return Err(EngineError::Storage(StorageError::ArityMismatch {
6666 expected: expected_tuple_len,
6667 actual: tuple.len(),
6668 }));
6669 }
6670 let values: Vec<Value> = if let Some(map) = &tuple_pos {
6674 let raw_tuple: Vec<Value> = tuple
6676 .into_iter()
6677 .map(literal_expr_to_value)
6678 .collect::<Result<_, _>>()?;
6679 let mut out = Vec::with_capacity(schema_cols_len);
6680 for (i, col) in column_meta.iter().enumerate() {
6681 let mut raw = match map[i] {
6682 Some(j) => raw_tuple[j].clone(),
6683 None => resolve_column_default_free(col, clock)?,
6684 };
6685 if col.auto_increment && raw.is_null() {
6686 let next = table.next_auto_value(i).ok_or_else(|| {
6687 EngineError::Unsupported(alloc::format!(
6688 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6689 col.name
6690 ))
6691 })?;
6692 raw = Value::BigInt(next);
6693 }
6694 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6695 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6696 let coerced =
6697 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6698 check_unsigned_range(&coerced, col, i)?;
6699 out.push(coerced);
6700 }
6701 out
6702 } else {
6703 let mut out = Vec::with_capacity(schema_cols_len);
6705 for (i, (col, expr)) in column_meta.iter().zip(tuple).enumerate() {
6706 let mut raw = literal_expr_to_value(expr)?;
6707 if col.auto_increment && raw.is_null() {
6708 let next = table.next_auto_value(i).ok_or_else(|| {
6709 EngineError::Unsupported(alloc::format!(
6710 "AUTO_INCREMENT applies to integer columns only (column `{}`)",
6711 col.name
6712 ))
6713 })?;
6714 raw = Value::BigInt(next);
6715 }
6716 let coerced = coerce_value(raw, col.ty, &col.name, i)?;
6717 enforce_enum_label(&enum_label_lookup, i, &col.name, &coerced)?;
6718 let coerced =
6719 canonicalize_set_value(&set_variant_lookup, i, &col.name, coerced)?;
6720 check_unsigned_range(&coerced, col, i)?;
6721 out.push(coerced);
6722 }
6723 out
6724 };
6725 all_values.push(values);
6726 }
6727 let uniqueness = table.schema().uniqueness_constraints.clone();
6732 let _ = table;
6733 if !fks.is_empty() {
6734 enforce_fk_inserts(self.active_catalog(), &stmt.table, &fks, &all_values)?;
6735 }
6736 enforce_check_constraints(self.active_catalog(), &stmt.table, &all_values)?;
6738 enforce_uniqueness_inserts(self.active_catalog(), &stmt.table, &uniqueness, &all_values)?;
6740 enforce_unique_index_inserts(self.active_catalog(), &stmt.table, &all_values)?;
6747 let mut pending_updates: Vec<(usize, Vec<Value>)> = Vec::new();
6754 let mut skipped_count = 0usize;
6755 if let Some(clause) = &stmt.on_conflict {
6756 let conflict_cols = resolve_on_conflict_columns(
6757 self.active_catalog(),
6758 &stmt.table,
6759 clause.target_columns.as_slice(),
6760 )?;
6761 let mut kept: Vec<Vec<Value>> = Vec::with_capacity(all_values.len());
6762 let mut seen_keys: Vec<Vec<Value>> = Vec::new();
6763 for values in all_values {
6764 let key_tuple: Vec<&Value> = conflict_cols.iter().map(|&c| &values[c]).collect();
6765 let has_null_key = key_tuple.iter().any(|v| matches!(v, Value::Null));
6768 let collides_with_table = !has_null_key
6769 && on_conflict_keys_exist(
6770 self.active_catalog(),
6771 &stmt.table,
6772 &conflict_cols,
6773 &key_tuple,
6774 );
6775 let key_tuple_owned: Vec<Value> = key_tuple.iter().map(|v| (*v).clone()).collect();
6776 let collides_with_batch =
6777 !has_null_key && seen_keys.iter().any(|k| k == &key_tuple_owned);
6778 let collides = collides_with_table || collides_with_batch;
6779 match (&clause.action, collides) {
6780 (_, false) => {
6781 seen_keys.push(key_tuple_owned);
6782 kept.push(values);
6783 }
6784 (spg_sql::ast::OnConflictAction::Nothing, true) => {
6785 skipped_count += 1;
6786 }
6787 (
6788 spg_sql::ast::OnConflictAction::Update {
6789 assignments,
6790 where_,
6791 },
6792 true,
6793 ) => {
6794 if !collides_with_table {
6795 skipped_count += 1;
6796 continue;
6797 }
6798 let target_pos = lookup_row_position_by_keys(
6799 self.active_catalog(),
6800 &stmt.table,
6801 &conflict_cols,
6802 &key_tuple,
6803 )
6804 .ok_or_else(|| {
6805 EngineError::Unsupported(
6806 "ON CONFLICT DO UPDATE: conflict detected but row \
6807 position could not be resolved (cold-tier row?)"
6808 .into(),
6809 )
6810 })?;
6811 let updated = apply_on_conflict_assignments(
6812 self.active_catalog(),
6813 &stmt.table,
6814 target_pos,
6815 &values,
6816 assignments,
6817 where_.as_ref(),
6818 )?;
6819 if let Some(new_row) = updated {
6820 pending_updates.push((target_pos, new_row));
6821 } else {
6822 skipped_count += 1;
6823 }
6824 }
6825 }
6826 }
6827 all_values = kept;
6828 }
6829 let table = self
6831 .active_catalog_mut()
6832 .get_mut(&stmt.table)
6833 .ok_or_else(|| {
6834 EngineError::Storage(StorageError::TableNotFound {
6835 name: stmt.table.clone(),
6836 })
6837 })?;
6838 let mut returning_rows: Vec<Vec<Value>> = Vec::new();
6842 let mut deferred_embedded: Vec<triggers::DeferredEmbeddedStmt> = Vec::new();
6846 'rowloop: for values in all_values {
6847 let mut row = Row::new(values);
6848 for fd in &before_insert_triggers {
6853 let (outcome, deferred) = triggers::fire_row_trigger(
6854 fd,
6855 Some(row.clone()),
6856 None,
6857 &stmt.table,
6858 &column_meta,
6859 &[],
6860 trigger_session_cfg.as_deref(),
6861 false,
6862 )
6863 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6864 deferred_embedded.extend(deferred);
6865 match outcome {
6866 triggers::TriggerOutcome::Row(r) => row = r,
6867 triggers::TriggerOutcome::Skip => continue 'rowloop,
6868 }
6869 }
6870 if stmt.returning.is_some() {
6871 returning_rows.push(row.values.clone());
6872 }
6873 let inserted = row.clone();
6876 table.insert(row)?;
6877 affected += 1;
6878 for fd in &after_insert_triggers {
6882 let (_outcome, deferred) = triggers::fire_row_trigger(
6883 fd,
6884 Some(inserted.clone()),
6885 None,
6886 &stmt.table,
6887 &column_meta,
6888 &[],
6889 trigger_session_cfg.as_deref(),
6890 true,
6891 )
6892 .map_err(|e| EngineError::Storage(StorageError::Corrupt(alloc::format!("{e}"))))?;
6893 deferred_embedded.extend(deferred);
6894 }
6895 }
6896 for (pos, new_row) in pending_updates {
6900 if stmt.returning.is_some() {
6901 returning_rows.push(new_row.clone());
6902 }
6903 table.update_row(pos, new_row)?;
6904 affected += 1;
6905 }
6906 let _ = skipped_count;
6907 let _ = table;
6913 self.execute_deferred_trigger_stmts(deferred_embedded, CancelToken::none())?;
6914 if let Some(items) = &stmt.returning {
6918 return self.build_returning_rows(&stmt.table, items, returning_rows);
6919 }
6920 if !self.in_transaction() && affected > 0 {
6925 self.statistics
6926 .record_modifications(&stmt.table, affected as u64);
6927 }
6928 Ok(QueryResult::CommandOk {
6929 affected,
6930 modified_catalog: !self.in_transaction(),
6931 })
6932 }
6933
6934 fn exec_select_as_of_segment(
6947 &self,
6948 stmt: &SelectStatement,
6949 from: &spg_sql::ast::FromClause,
6950 segment_id: u32,
6951 ) -> Result<QueryResult, EngineError> {
6952 if !from.joins.is_empty()
6955 || stmt.group_by.is_some()
6956 || stmt.having.is_some()
6957 || !stmt.unions.is_empty()
6958 || !stmt.order_by.is_empty()
6959 || stmt.offset.is_some()
6960 || stmt.distinct
6961 || aggregate::uses_aggregate(stmt)
6962 {
6963 return Err(EngineError::Unsupported(
6964 "AS OF SEGMENT supports SELECT projection + WHERE + LIMIT only \
6965 (joins / aggregates / ORDER BY are STABILITY § \"Out of v6.10\")"
6966 .into(),
6967 ));
6968 }
6969 let table = self
6970 .active_catalog()
6971 .get(&from.primary.name)
6972 .ok_or_else(|| StorageError::TableNotFound {
6973 name: from.primary.name.clone(),
6974 })?;
6975 let schema = table.schema().clone();
6976 let schema_cols = &schema.columns;
6977 let alias = from
6978 .primary
6979 .alias
6980 .as_deref()
6981 .unwrap_or(from.primary.name.as_str());
6982 let ctx = EvalContext::new(schema_cols, Some(alias));
6983 let seg = self
6984 .active_catalog()
6985 .cold_segment(segment_id)
6986 .ok_or_else(|| {
6987 EngineError::Unsupported(alloc::format!(
6988 "AS OF SEGMENT: cold segment {segment_id} not registered"
6989 ))
6990 })?;
6991 let mut out_rows: Vec<Row> = Vec::new();
6992 let mut limit_remaining: Option<usize> =
6993 stmt.limit_literal().and_then(|n| usize::try_from(n).ok());
6994 for (_key, body) in seg.scan() {
6995 let (row, _consumed) =
6996 spg_storage::decode_row_body_dense(&body, &schema).map_err(EngineError::Storage)?;
6997 if let Some(where_expr) = &stmt.where_ {
6998 let cond = self.eval_expr_simple(where_expr, &row, &ctx)?;
6999 if !matches!(cond, Value::Bool(true)) {
7000 continue;
7001 }
7002 }
7003 let projected = self.project_row_simple(&row, &stmt.items, schema_cols, alias)?;
7005 out_rows.push(projected);
7006 if let Some(rem) = limit_remaining.as_mut() {
7007 if *rem == 0 {
7008 out_rows.pop();
7009 break;
7010 }
7011 *rem -= 1;
7012 }
7013 }
7014 let columns = self.derive_output_columns(&stmt.items, schema_cols, alias);
7016 Ok(QueryResult::Rows {
7017 columns,
7018 rows: out_rows,
7019 })
7020 }
7021
7022 fn eval_expr_simple(
7027 &self,
7028 expr: &Expr,
7029 row: &Row,
7030 ctx: &EvalContext,
7031 ) -> Result<Value, EngineError> {
7032 let cancel = CancelToken::none();
7033 self.eval_expr_with_correlated(expr, row, ctx, cancel, None)
7034 }
7035
7036 fn build_returning_rows(
7043 &self,
7044 table_name: &str,
7045 items: &[SelectItem],
7046 mutated_rows: Vec<Vec<Value>>,
7047 ) -> Result<QueryResult, EngineError> {
7048 let table = self.active_catalog().get(table_name).ok_or_else(|| {
7049 EngineError::Storage(StorageError::TableNotFound {
7050 name: table_name.into(),
7051 })
7052 })?;
7053 let schema_cols = table.schema().columns.clone();
7054 let columns = self.derive_output_columns(items, &schema_cols, table_name);
7055 let mut out_rows: Vec<Row> = Vec::with_capacity(mutated_rows.len());
7056 for values in mutated_rows {
7057 let row = Row::new(values);
7058 let projected = self.project_row_simple(&row, items, &schema_cols, table_name)?;
7059 out_rows.push(projected);
7060 }
7061 Ok(QueryResult::Rows {
7062 columns,
7063 rows: out_rows,
7064 })
7065 }
7066
7067 fn project_row_simple(
7071 &self,
7072 row: &Row,
7073 items: &[SelectItem],
7074 schema_cols: &[ColumnSchema],
7075 alias: &str,
7076 ) -> Result<Row, EngineError> {
7077 let ctx = EvalContext::new(schema_cols, Some(alias));
7078 let cancel = CancelToken::none();
7079 let mut out_vals = Vec::new();
7080 for item in items {
7081 match item {
7082 SelectItem::Wildcard => {
7083 out_vals.extend(row.values.iter().cloned());
7084 }
7085 SelectItem::Expr { expr, .. } => {
7086 let v = self.eval_expr_with_correlated(expr, row, &ctx, cancel, None)?;
7087 out_vals.push(v);
7088 }
7089 }
7090 }
7091 Ok(Row::new(out_vals))
7092 }
7093
7094 fn derive_output_columns(
7099 &self,
7100 items: &[SelectItem],
7101 schema_cols: &[ColumnSchema],
7102 _alias: &str,
7103 ) -> Vec<ColumnSchema> {
7104 let mut out = Vec::new();
7105 for item in items {
7106 match item {
7107 SelectItem::Wildcard => {
7108 out.extend(schema_cols.iter().cloned());
7109 }
7110 SelectItem::Expr { alias, .. } => {
7111 let name = alias.clone().unwrap_or_else(|| "?column?".to_string());
7112 out.push(ColumnSchema::new(name, DataType::Text, true));
7115 }
7116 }
7117 }
7118 out
7119 }
7120
7121 fn exec_select_cancel(
7122 &self,
7123 stmt: &SelectStatement,
7124 cancel: CancelToken<'_>,
7125 ) -> Result<QueryResult, EngineError> {
7126 cancel.check()?;
7127 if !self.active_catalog().views().is_empty() {
7134 if let Some(rewritten) = self.expand_views_in_select(stmt)? {
7135 return self.exec_select_cancel(&rewritten, cancel);
7136 }
7137 }
7138 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7147 return self.exec_select_with_meta_views(stmt, cancel);
7148 }
7149 if let Some(from) = &stmt.from
7158 && let Some(seg_id) = from.primary.as_of_segment
7159 {
7160 return self.exec_select_as_of_segment(stmt, from, seg_id);
7161 }
7162 if let Some(from) = &stmt.from
7166 && from.joins.is_empty()
7167 && stmt.where_.is_none()
7168 && stmt.group_by.is_none()
7169 && stmt.having.is_none()
7170 && stmt.unions.is_empty()
7171 && stmt.order_by.is_empty()
7172 && stmt.limit.is_none()
7173 && stmt.offset.is_none()
7174 && !stmt.distinct
7175 && stmt.items.iter().all(|i| matches!(i, SelectItem::Wildcard))
7176 {
7177 let lower = from.primary.name.to_ascii_lowercase();
7178 match lower.as_str() {
7179 "spg_statistic" => return Ok(self.exec_spg_statistic()),
7180 "spg_stat_replication" => return Ok(self.exec_spg_stat_replication()),
7182 "spg_stat_segment" => return Ok(self.exec_spg_stat_segment()),
7183 "spg_stat_query" => return Ok(self.exec_spg_stat_query()),
7184 "spg_stat_activity" => return Ok(self.exec_spg_stat_activity()),
7185 "spg_audit_chain" => return Ok(self.exec_spg_audit_chain()),
7186 "spg_audit_verify" => return Ok(self.exec_spg_audit_verify()),
7187 "spg_table_ddl" => return Ok(self.exec_spg_table_ddl()),
7188 "spg_role_ddl" => return Ok(self.exec_spg_role_ddl()),
7189 "spg_database_ddl" => return Ok(self.exec_spg_database_ddl()),
7190 _ => {}
7191 }
7192 }
7193 if !stmt.ctes.is_empty() {
7201 return self.exec_with_ctes(stmt, cancel);
7202 }
7203 let mut stmt_owned;
7210 let stmt_ref: &SelectStatement = if expr_tree_has_subquery(stmt) {
7211 stmt_owned = stmt.clone();
7212 self.resolve_select_subqueries(&mut stmt_owned, cancel)?;
7213 &stmt_owned
7214 } else {
7215 stmt
7216 };
7217 if stmt_ref.unions.is_empty() {
7218 return self.exec_bare_select_cancel(stmt_ref, cancel);
7219 }
7220 let mut head = stmt_ref.clone();
7225 head.unions = Vec::new();
7226 head.order_by = Vec::new();
7227 head.limit = None;
7228 let QueryResult::Rows { columns, mut rows } =
7229 self.exec_bare_select_cancel(&head, cancel)?
7230 else {
7231 unreachable!("bare SELECT cannot return CommandOk")
7232 };
7233 for (kind, peer) in &stmt_ref.unions {
7234 let QueryResult::Rows {
7235 columns: peer_cols,
7236 rows: peer_rows,
7237 } = self.exec_bare_select_cancel(peer, cancel)?
7238 else {
7239 unreachable!("bare SELECT cannot return CommandOk")
7240 };
7241 if peer_cols.len() != columns.len() {
7242 return Err(EngineError::Unsupported(alloc::format!(
7243 "UNION arity mismatch: head has {} columns, peer has {}",
7244 columns.len(),
7245 peer_cols.len()
7246 )));
7247 }
7248 rows.extend(peer_rows);
7249 if matches!(kind, UnionKind::Distinct) {
7250 rows = dedup_rows(rows);
7251 }
7252 }
7253 if !stmt.order_by.is_empty() {
7256 let synth_ctx = EvalContext::new(&columns, None);
7257 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7258 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(rows.len());
7259 for r in rows {
7260 let keys = build_order_keys(&stmt.order_by, &r, &synth_ctx)?;
7261 tagged.push((keys, r));
7262 }
7263 sort_by_keys(&mut tagged, &descs);
7264 rows = tagged.into_iter().map(|(_, r)| r).collect();
7265 }
7266 apply_offset_and_limit(&mut rows, stmt.offset_literal(), stmt.limit_literal());
7267 Ok(QueryResult::Rows { columns, rows })
7268 }
7269
7270 #[allow(clippy::too_many_lines)]
7271 #[allow(clippy::too_many_lines)] fn exec_select_unnest(
7279 &self,
7280 stmt: &SelectStatement,
7281 primary: &TableRef,
7282 cancel: CancelToken<'_>,
7283 ) -> Result<QueryResult, EngineError> {
7284 let expr = primary
7285 .unnest_expr
7286 .as_deref()
7287 .expect("caller guards unnest_expr.is_some()");
7288 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7291 let ctx = EvalContext::new(&empty_schema, None);
7292 let dummy_row = Row::new(alloc::vec::Vec::new());
7293 let (elem_dtype, rows): (DataType, alloc::vec::Vec<Row>) =
7296 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7297 Value::Null => (DataType::Text, alloc::vec::Vec::new()),
7298 Value::TextArray(items) => {
7299 let rows = items
7300 .into_iter()
7301 .map(|item| {
7302 Row::new(alloc::vec![match item {
7303 Some(s) => Value::Text(s),
7304 None => Value::Null,
7305 }])
7306 })
7307 .collect();
7308 (DataType::Text, rows)
7309 }
7310 Value::IntArray(items) => {
7311 let rows = items
7312 .into_iter()
7313 .map(|item| {
7314 Row::new(alloc::vec![match item {
7315 Some(n) => Value::Int(n),
7316 None => Value::Null,
7317 }])
7318 })
7319 .collect();
7320 (DataType::Int, rows)
7321 }
7322 Value::BigIntArray(items) => {
7323 let rows = items
7324 .into_iter()
7325 .map(|item| {
7326 Row::new(alloc::vec![match item {
7327 Some(n) => Value::BigInt(n),
7328 None => Value::Null,
7329 }])
7330 })
7331 .collect();
7332 (DataType::BigInt, rows)
7333 }
7334 other => {
7335 return Err(EngineError::Unsupported(alloc::format!(
7336 "unnest() expects an array argument, got {:?}",
7337 other.data_type()
7338 )));
7339 }
7340 };
7341 let alias = primary
7342 .alias
7343 .clone()
7344 .unwrap_or_else(|| "unnest".to_string());
7345 let col_name = primary
7351 .unnest_column_aliases
7352 .first()
7353 .cloned()
7354 .unwrap_or_else(|| alias.clone());
7355 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7356 let schema_cols = alloc::vec![col_schema.clone()];
7357 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7358 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7360 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7361 for row in rows {
7362 cancel.check()?;
7363 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7364 if matches!(v, Value::Bool(true)) {
7365 out.push(row);
7366 }
7367 }
7368 out
7369 } else {
7370 rows
7371 };
7372 if aggregate::uses_aggregate(stmt) {
7378 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7379 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7380 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7381 return Ok(QueryResult::Rows {
7382 columns: agg.columns,
7383 rows: agg.rows,
7384 });
7385 }
7386 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7388 let mut projected_rows: alloc::vec::Vec<Row> =
7389 alloc::vec::Vec::with_capacity(filtered.len());
7390 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7399 if let Some(srf_idx) = srf_position {
7400 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7401 .expect("checked by is_top_level_unnest above");
7402 for row in &filtered {
7403 let arr_val =
7404 eval::eval_expr(srf_arg, row, &scan_ctx).map_err(EngineError::Eval)?;
7405 let elements = array_value_to_elements(&arr_val)?;
7406 for elem in elements {
7410 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7411 for (i, p) in projection.iter().enumerate() {
7412 if i == srf_idx {
7413 vals.push(elem.clone());
7414 } else {
7415 vals.push(
7416 eval::eval_expr(&p.expr, row, &scan_ctx)
7417 .map_err(EngineError::Eval)?,
7418 );
7419 }
7420 }
7421 projected_rows.push(Row::new(vals));
7422 }
7423 }
7424 } else {
7425 for row in &filtered {
7426 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7427 for p in &projection {
7428 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7429 }
7430 projected_rows.push(Row::new(vals));
7431 }
7432 }
7433 let columns: alloc::vec::Vec<ColumnSchema> = projection
7436 .iter()
7437 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7438 .collect();
7439 if !stmt.order_by.is_empty() {
7442 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7443 .iter()
7444 .enumerate()
7445 .map(|(i, r)| -> Result<_, EngineError> {
7446 let keys: Result<Vec<Value>, EngineError> = stmt
7447 .order_by
7448 .iter()
7449 .map(|ob| {
7450 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7451 })
7452 .collect();
7453 Ok((i, keys?))
7454 })
7455 .collect::<Result<_, _>>()?;
7456 indexed.sort_by(|a, b| {
7457 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7458 let mut cmp = value_cmp(ka, kb);
7459 if stmt.order_by[idx].desc {
7460 cmp = cmp.reverse();
7461 }
7462 if cmp != core::cmp::Ordering::Equal {
7463 return cmp;
7464 }
7465 }
7466 core::cmp::Ordering::Equal
7467 });
7468 projected_rows = indexed
7469 .into_iter()
7470 .map(|(i, _)| projected_rows[i].clone())
7471 .collect();
7472 }
7473 if let Some(offset) = stmt.offset_literal() {
7475 let off = (offset as usize).min(projected_rows.len());
7476 projected_rows.drain(..off);
7477 }
7478 if let Some(limit) = stmt.limit_literal() {
7479 projected_rows.truncate(limit as usize);
7480 }
7481 Ok(QueryResult::Rows {
7482 columns,
7483 rows: projected_rows,
7484 })
7485 }
7486
7487 fn exec_select_generate_series(
7498 &self,
7499 stmt: &SelectStatement,
7500 primary: &TableRef,
7501 cancel: CancelToken<'_>,
7502 ) -> Result<QueryResult, EngineError> {
7503 let args = primary
7504 .generate_series_args
7505 .as_ref()
7506 .expect("caller guards generate_series_args.is_some()");
7507 let empty_schema: alloc::vec::Vec<ColumnSchema> = alloc::vec::Vec::new();
7508 let ctx = EvalContext::new(&empty_schema, None);
7509 let dummy_row = Row::new(alloc::vec::Vec::new());
7510 let mut arg_values: alloc::vec::Vec<Value> = alloc::vec::Vec::with_capacity(args.len());
7511 for a in args {
7512 arg_values.push(eval::eval_expr(a, &dummy_row, &ctx).map_err(EngineError::Eval)?);
7513 }
7514 let (elem_dtype, rows) = match arg_values.as_slice() {
7518 [Value::Timestamp(start), Value::Timestamp(stop), step] => {
7519 let interval_step = match step {
7520 Value::Interval { .. } => step.clone(),
7521 other => {
7522 return Err(EngineError::Unsupported(alloc::format!(
7523 "generate_series(timestamp, timestamp, …): \
7524 step must be INTERVAL, got {:?}",
7525 other.data_type()
7526 )));
7527 }
7528 };
7529 let rows = generate_series_timestamps(*start, *stop, interval_step, &cancel)?;
7530 (DataType::Timestamp, rows)
7531 }
7532 [start, stop, step]
7533 if value_is_integer(start) && value_is_integer(stop) && value_is_integer(step) =>
7534 {
7535 let s = value_to_i64(start);
7536 let e = value_to_i64(stop);
7537 let st = value_to_i64(step);
7538 let rows = generate_series_integers(s, e, st, &cancel)?;
7539 (DataType::BigInt, rows)
7540 }
7541 [start, stop] if value_is_integer(start) && value_is_integer(stop) => {
7542 let s = value_to_i64(start);
7543 let e = value_to_i64(stop);
7544 let rows = generate_series_integers(s, e, 1, &cancel)?;
7545 (DataType::BigInt, rows)
7546 }
7547 _ => {
7548 return Err(EngineError::Unsupported(alloc::format!(
7549 "generate_series(): v7.17 supports integer or (timestamp, timestamp, interval) \
7550 argument shapes; got {:?}",
7551 arg_values
7552 .iter()
7553 .map(|v| v.data_type())
7554 .collect::<alloc::vec::Vec<_>>()
7555 )));
7556 }
7557 };
7558 let alias = primary
7559 .alias
7560 .clone()
7561 .unwrap_or_else(|| "generate_series".to_string());
7562 let col_name = alias.clone();
7563 let col_schema = ColumnSchema::new(col_name, elem_dtype, true);
7564 let schema_cols = alloc::vec![col_schema.clone()];
7565 let scan_ctx = EvalContext::new(&schema_cols, Some(&alias));
7566 let filtered: alloc::vec::Vec<Row> = if let Some(w) = &stmt.where_ {
7568 let mut out = alloc::vec::Vec::with_capacity(rows.len());
7569 for row in rows {
7570 cancel.check()?;
7571 let v = eval::eval_expr(w, &row, &scan_ctx).map_err(EngineError::Eval)?;
7572 if matches!(v, Value::Bool(true)) {
7573 out.push(row);
7574 }
7575 }
7576 out
7577 } else {
7578 rows
7579 };
7580 if aggregate::uses_aggregate(stmt) {
7590 let filtered_refs: alloc::vec::Vec<&Row> = filtered.iter().collect();
7591 let mut agg = aggregate::run(stmt, &filtered_refs, &schema_cols, Some(&alias))?;
7592 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7593 return Ok(QueryResult::Rows {
7594 columns: agg.columns,
7595 rows: agg.rows,
7596 });
7597 }
7598 let projection = build_projection(&stmt.items, &schema_cols, &alias)?;
7600 let mut projected_rows: alloc::vec::Vec<Row> =
7601 alloc::vec::Vec::with_capacity(filtered.len());
7602 for row in &filtered {
7603 let mut vals = alloc::vec::Vec::with_capacity(projection.len());
7604 for p in &projection {
7605 vals.push(eval::eval_expr(&p.expr, row, &scan_ctx).map_err(EngineError::Eval)?);
7606 }
7607 projected_rows.push(Row::new(vals));
7608 }
7609 let columns: alloc::vec::Vec<ColumnSchema> = projection
7610 .iter()
7611 .map(|p| ColumnSchema::new(p.output_name.clone(), p.ty, p.nullable))
7612 .collect();
7613 if !stmt.order_by.is_empty() {
7615 let mut indexed: alloc::vec::Vec<(usize, Vec<Value>)> = filtered
7616 .iter()
7617 .enumerate()
7618 .map(|(i, r)| -> Result<_, EngineError> {
7619 let keys: Result<Vec<Value>, EngineError> = stmt
7620 .order_by
7621 .iter()
7622 .map(|ob| {
7623 eval::eval_expr(&ob.expr, r, &scan_ctx).map_err(EngineError::Eval)
7624 })
7625 .collect();
7626 Ok((i, keys?))
7627 })
7628 .collect::<Result<_, _>>()?;
7629 indexed.sort_by(|a, b| {
7630 for (idx, (ka, kb)) in a.1.iter().zip(b.1.iter()).enumerate() {
7631 let mut cmp = value_cmp(ka, kb);
7632 if stmt.order_by[idx].desc {
7633 cmp = cmp.reverse();
7634 }
7635 if cmp != core::cmp::Ordering::Equal {
7636 return cmp;
7637 }
7638 }
7639 core::cmp::Ordering::Equal
7640 });
7641 projected_rows = indexed
7642 .into_iter()
7643 .map(|(i, _)| projected_rows[i].clone())
7644 .collect();
7645 }
7646 if let Some(offset) = stmt.offset_literal() {
7647 let off = (offset as usize).min(projected_rows.len());
7648 projected_rows.drain(..off);
7649 }
7650 if let Some(limit) = stmt.limit_literal() {
7651 projected_rows.truncate(limit as usize);
7652 }
7653 Ok(QueryResult::Rows {
7654 columns,
7655 rows: projected_rows,
7656 })
7657 }
7658
7659 fn exec_bare_select_cancel(
7660 &self,
7661 stmt: &SelectStatement,
7662 cancel: CancelToken<'_>,
7663 ) -> Result<QueryResult, EngineError> {
7664 check_with_ties_requires_order_by(stmt)?;
7669 if !self.meta_views_materialised && select_references_meta_view(stmt) {
7677 return self.exec_select_with_meta_views(stmt, cancel);
7678 }
7679 if select_has_window(stmt) {
7684 return self.exec_select_with_window(stmt, cancel);
7685 }
7686 let Some(from) = &stmt.from else {
7691 let empty_schema: Vec<ColumnSchema> = Vec::new();
7692 let ctx = self.ev_ctx(&empty_schema, None);
7693 let projection = build_projection(&stmt.items, &empty_schema, "")?;
7694 let dummy_row = Row::new(Vec::new());
7695 let mut values = Vec::with_capacity(projection.len());
7696 for p in &projection {
7697 values.push(eval::eval_expr(&p.expr, &dummy_row, &ctx)?);
7698 }
7699 let columns: Vec<ColumnSchema> = projection
7700 .into_iter()
7701 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7702 .collect();
7703 return Ok(QueryResult::Rows {
7704 columns,
7705 rows: alloc::vec![Row::new(values)],
7706 });
7707 };
7708 if !from.joins.is_empty() {
7712 return self.exec_joined_select(stmt, from);
7713 }
7714 if from.primary.unnest_expr.is_some() {
7721 return self.exec_select_unnest(stmt, &from.primary, cancel);
7722 }
7723 if from.primary.generate_series_args.is_some() {
7729 return self.exec_select_generate_series(stmt, &from.primary, cancel);
7730 }
7731 let primary = &from.primary;
7732 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
7733 StorageError::TableNotFound {
7734 name: primary.name.clone(),
7735 }
7736 })?;
7737 let schema_cols = &table.schema().columns;
7738 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
7741 let ctx = self.ev_ctx(schema_cols, Some(alias));
7742
7743 if let Some(nsw_rows) = try_nsw_knn(stmt, table, schema_cols, alias) {
7748 return materialise_in_order(stmt, table, schema_cols, alias, &nsw_rows);
7749 }
7750
7751 let indexed_rows: Option<Vec<Cow<'_, Row>>> = stmt.where_.as_ref().and_then(|w| {
7759 try_index_seek(w, schema_cols, self.active_catalog(), table, alias)
7762 .or_else(|| {
7763 try_gin_seek(w, schema_cols, self.active_catalog(), table, alias, &ctx)
7769 })
7770 .or_else(|| {
7771 try_trgm_seek(w, schema_cols, table, alias)
7777 })
7778 });
7779
7780 if aggregate::uses_aggregate(stmt) {
7783 let mut filtered: Vec<&Row> = Vec::new();
7784 let mut memo = memoize::MemoizeCache::new();
7788 if let Some(rows) = &indexed_rows {
7789 for cow in rows {
7790 let row = cow.as_ref();
7791 if let Some(where_expr) = &stmt.where_ {
7792 let cond = self.eval_expr_with_correlated(
7793 where_expr,
7794 row,
7795 &ctx,
7796 cancel,
7797 Some(&mut memo),
7798 )?;
7799 if !matches!(cond, Value::Bool(true)) {
7800 continue;
7801 }
7802 }
7803 filtered.push(row);
7804 }
7805 } else {
7806 for i in 0..table.row_count() {
7807 let row = &table.rows()[i];
7808 if let Some(where_expr) = &stmt.where_ {
7809 let cond = self.eval_expr_with_correlated(
7810 where_expr,
7811 row,
7812 &ctx,
7813 cancel,
7814 Some(&mut memo),
7815 )?;
7816 if !matches!(cond, Value::Bool(true)) {
7817 continue;
7818 }
7819 }
7820 filtered.push(row);
7821 }
7822 }
7823 let mut agg = aggregate::run(stmt, &filtered, schema_cols, Some(alias))?;
7824 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
7825 return Ok(QueryResult::Rows {
7826 columns: agg.columns,
7827 rows: agg.rows,
7828 });
7829 }
7830
7831 let projection = build_projection(&stmt.items, schema_cols, alias)?;
7832 let srf_position = projection.iter().position(|p| is_top_level_unnest(&p.expr));
7840
7841 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
7844 let mut memo = memoize::MemoizeCache::new();
7846 let mut process_row = |row: &Row, loop_idx: usize| -> Result<(), EngineError> {
7849 if loop_idx.is_multiple_of(256) {
7850 cancel.check()?;
7851 }
7852 if let Some(where_expr) = &stmt.where_ {
7853 let cond =
7854 self.eval_expr_with_correlated(where_expr, row, &ctx, cancel, Some(&mut memo))?;
7855 if !matches!(cond, Value::Bool(true)) {
7856 return Ok(());
7857 }
7858 }
7859 let order_keys = if stmt.order_by.is_empty() {
7860 Vec::new()
7861 } else {
7862 build_order_keys(&stmt.order_by, row, &ctx)?
7863 };
7864 if let Some(srf_idx) = srf_position {
7865 let srf_arg = top_level_unnest_arg(&projection[srf_idx].expr)
7866 .expect("checked by is_top_level_unnest above");
7867 let arr_val = eval::eval_expr(srf_arg, row, &ctx)?;
7868 let elements = array_value_to_elements(&arr_val)?;
7869 for elem in elements {
7870 let mut values = Vec::with_capacity(projection.len());
7871 for (i, p) in projection.iter().enumerate() {
7872 if i == srf_idx {
7873 values.push(elem.clone());
7874 } else {
7875 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7876 }
7877 }
7878 tagged.push((order_keys.clone(), Row::new(values)));
7879 }
7880 } else {
7881 let mut values = Vec::with_capacity(projection.len());
7882 for p in &projection {
7883 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
7884 }
7885 tagged.push((order_keys, Row::new(values)));
7886 }
7887 Ok(())
7888 };
7889 if let Some(rows) = &indexed_rows {
7890 for (loop_idx, cow) in rows.iter().enumerate() {
7891 process_row(cow.as_ref(), loop_idx)?;
7892 }
7893 } else {
7894 for i in 0..table.row_count() {
7895 process_row(&table.rows()[i], i)?;
7896 }
7897 }
7898
7899 if !stmt.order_by.is_empty() {
7900 let keep = if stmt.distinct || stmt.limit_with_ties {
7908 None
7909 } else {
7910 stmt.limit_literal()
7911 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
7912 };
7913 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
7914 partial_sort_tagged(&mut tagged, keep, &descs);
7915 }
7916
7917 let output_rows: Vec<Row> = if stmt.limit_with_ties && !stmt.distinct {
7927 apply_offset_and_limit_tagged(
7928 &mut tagged,
7929 stmt.offset_literal(),
7930 stmt.limit_literal(),
7931 true,
7932 );
7933 tagged.into_iter().map(|(_, r)| r).collect()
7934 } else {
7935 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
7936 if stmt.distinct {
7937 output_rows = dedup_rows(output_rows);
7938 }
7939 apply_offset_and_limit(
7940 &mut output_rows,
7941 stmt.offset_literal(),
7942 stmt.limit_literal(),
7943 );
7944 output_rows
7945 };
7946
7947 let columns: Vec<ColumnSchema> = projection
7948 .into_iter()
7949 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
7950 .collect();
7951
7952 Ok(QueryResult::Rows {
7953 columns,
7954 rows: output_rows,
7955 })
7956 }
7957
7958 #[allow(clippy::too_many_lines)]
7965 fn materialise_table_ref(
7973 &self,
7974 tref: &TableRef,
7975 ) -> Result<(Vec<Row>, Vec<ColumnSchema>), EngineError> {
7976 if let Some(expr) = tref.unnest_expr.as_deref() {
7977 let empty_schema: Vec<ColumnSchema> = Vec::new();
7978 let ctx = EvalContext::new(&empty_schema, None);
7979 let dummy_row = Row::new(Vec::new());
7980 let (elem_dtype, rows) =
7981 match eval::eval_expr(expr, &dummy_row, &ctx).map_err(EngineError::Eval)? {
7982 Value::Null => (DataType::Text, Vec::new()),
7983 Value::TextArray(items) => (
7984 DataType::Text,
7985 items
7986 .into_iter()
7987 .map(|item| {
7988 Row::new(alloc::vec![match item {
7989 Some(s) => Value::Text(s),
7990 None => Value::Null,
7991 }])
7992 })
7993 .collect(),
7994 ),
7995 Value::IntArray(items) => (
7996 DataType::Int,
7997 items
7998 .into_iter()
7999 .map(|item| {
8000 Row::new(alloc::vec![match item {
8001 Some(n) => Value::Int(n),
8002 None => Value::Null,
8003 }])
8004 })
8005 .collect(),
8006 ),
8007 Value::BigIntArray(items) => (
8008 DataType::BigInt,
8009 items
8010 .into_iter()
8011 .map(|item| {
8012 Row::new(alloc::vec![match item {
8013 Some(n) => Value::BigInt(n),
8014 None => Value::Null,
8015 }])
8016 })
8017 .collect(),
8018 ),
8019 other => {
8020 return Err(EngineError::Unsupported(alloc::format!(
8021 "unnest() expects an array argument, got {:?}",
8022 other.data_type()
8023 )));
8024 }
8025 };
8026 let alias = tref.alias.clone().unwrap_or_else(|| "unnest".to_string());
8027 let col_name = tref.unnest_column_aliases.first().cloned().unwrap_or(alias);
8028 return Ok((
8029 rows,
8030 alloc::vec![ColumnSchema::new(col_name, elem_dtype, true)],
8031 ));
8032 }
8033 let table =
8034 self.active_catalog()
8035 .get(&tref.name)
8036 .ok_or_else(|| StorageError::TableNotFound {
8037 name: tref.name.clone(),
8038 })?;
8039 let rows: Vec<Row> = table.rows().iter().cloned().collect();
8040 let cols = table.schema().columns.clone();
8041 Ok((rows, cols))
8042 }
8043
8044 fn build_joined_filtered_rows(
8056 &self,
8057 from: &FromClause,
8058 where_: Option<&Expr>,
8059 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
8060 let (primary_rows, primary_cols) = self.materialise_table_ref(&from.primary)?;
8061 let primary_alias = from
8062 .primary
8063 .alias
8064 .as_deref()
8065 .unwrap_or(from.primary.name.as_str())
8066 .to_string();
8067 #[allow(clippy::type_complexity)]
8074 let mut joined: Vec<JoinedPeer<'_>> = Vec::new();
8075 for j in &from.joins {
8076 let a = j
8077 .table
8078 .alias
8079 .as_deref()
8080 .unwrap_or(j.table.name.as_str())
8081 .to_string();
8082 if let Some(inner_box) = &j.table.lateral_subquery {
8083 let schema = self.lateral_probe_schema(inner_box)?;
8088 joined.push(JoinedPeer {
8089 eager_rows: None,
8090 cols: schema,
8091 alias: a,
8092 kind: j.kind,
8093 on: j.on.as_ref(),
8094 lateral: Some(inner_box.as_ref()),
8095 });
8096 } else {
8097 let (rows, cols) = self.materialise_table_ref(&j.table)?;
8098 joined.push(JoinedPeer {
8099 eager_rows: Some(rows),
8100 cols,
8101 alias: a,
8102 kind: j.kind,
8103 on: j.on.as_ref(),
8104 lateral: None,
8105 });
8106 }
8107 }
8108 let mut combined_schema: Vec<ColumnSchema> = Vec::new();
8109 for col in &primary_cols {
8110 combined_schema.push(ColumnSchema::new(
8111 alloc::format!("{primary_alias}.{}", col.name),
8112 col.ty,
8113 col.nullable,
8114 ));
8115 }
8116 for peer in &joined {
8117 for col in &peer.cols {
8118 combined_schema.push(ColumnSchema::new(
8119 alloc::format!("{}.{}", peer.alias, col.name),
8120 col.ty,
8121 col.nullable,
8122 ));
8123 }
8124 }
8125 let ctx = EvalContext::new(&combined_schema, None);
8126 let mut working: Vec<Row> = primary_rows;
8127 let mut consumed_cols = primary_cols.len();
8130 for peer in &joined {
8131 let right_arity = peer.cols.len();
8132 let mut next: Vec<Row> = Vec::new();
8133 for left in &working {
8134 let mut left_matched = false;
8135 let per_left_rrows: alloc::borrow::Cow<'_, [Row]> = match peer.lateral {
8136 Some(inner) => {
8137 let outer_schema = &combined_schema[..consumed_cols];
8141 let rows = self.materialise_lateral_for_outer(inner, outer_schema, left)?;
8142 alloc::borrow::Cow::Owned(rows)
8143 }
8144 None => {
8145 let r = peer.eager_rows.as_ref().expect("non-lateral peer eager");
8146 alloc::borrow::Cow::Borrowed(r.as_slice())
8147 }
8148 };
8149 for right in per_left_rrows.as_ref() {
8150 let mut combined_vals = left.values.clone();
8151 combined_vals.extend(right.values.iter().cloned());
8152 let combined = Row::new(combined_vals);
8153 let keep = if let Some(on_expr) = peer.on {
8154 let cond = eval::eval_expr(on_expr, &combined, &ctx)?;
8155 matches!(cond, Value::Bool(true))
8156 } else {
8157 true
8158 };
8159 if keep {
8160 next.push(combined);
8161 left_matched = true;
8162 }
8163 }
8164 if !left_matched && matches!(peer.kind, JoinKind::Left) {
8165 let mut combined_vals = left.values.clone();
8166 for _ in 0..right_arity {
8167 combined_vals.push(Value::Null);
8168 }
8169 next.push(Row::new(combined_vals));
8170 }
8171 }
8172 working = next;
8173 consumed_cols += right_arity;
8174 debug_assert!(consumed_cols <= combined_schema.len());
8175 }
8176 let mut filtered: Vec<Row> = Vec::new();
8177 for row in working {
8178 if let Some(where_expr) = where_ {
8179 let cond = eval::eval_expr(where_expr, &row, &ctx)?;
8180 if !matches!(cond, Value::Bool(true)) {
8181 continue;
8182 }
8183 }
8184 filtered.push(row);
8185 }
8186 Ok((combined_schema, filtered))
8187 }
8188
8189 fn lateral_probe_schema(
8195 &self,
8196 inner: &SelectStatement,
8197 ) -> Result<Vec<ColumnSchema>, EngineError> {
8198 match self.execute_readonly_select_for_lateral_probe(inner) {
8208 Ok(QueryResult::Rows { columns, .. }) => Ok(columns),
8209 _ => {
8215 let mut out: Vec<ColumnSchema> = Vec::new();
8216 for (i, item) in inner.items.iter().enumerate() {
8217 let name = match item {
8218 SelectItem::Expr { alias: Some(a), .. } => a.clone(),
8219 SelectItem::Expr { expr, .. } => synth_lateral_col_name(expr, i),
8220 SelectItem::Wildcard => alloc::format!("col{i}"),
8221 };
8222 out.push(ColumnSchema::new(name, DataType::Text, true));
8223 }
8224 Ok(out)
8225 }
8226 }
8227 }
8228
8229 fn execute_readonly_select_for_lateral_probe(
8235 &self,
8236 inner: &SelectStatement,
8237 ) -> Result<QueryResult, EngineError> {
8238 self.exec_bare_select_cancel(inner, CancelToken::none())
8239 }
8240
8241 fn materialise_lateral_for_outer(
8247 &self,
8248 inner: &SelectStatement,
8249 outer_schema: &[ColumnSchema],
8250 outer_row: &Row,
8251 ) -> Result<Vec<Row>, EngineError> {
8252 let mut substituted = inner.clone();
8253 substitute_outer_columns_multi(&mut substituted, outer_row, outer_schema);
8254 let result = self.exec_bare_select_cancel(&substituted, CancelToken::none())?;
8255 match result {
8256 QueryResult::Rows { rows, .. } => Ok(rows),
8257 _ => Err(EngineError::Unsupported(
8258 "LATERAL subquery must be a SELECT (cannot be a write statement)".into(),
8259 )),
8260 }
8261 }
8262
8263 fn exec_joined_select(
8264 &self,
8265 stmt: &SelectStatement,
8266 from: &FromClause,
8267 ) -> Result<QueryResult, EngineError> {
8268 let (combined_schema, filtered) =
8276 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
8277 let ctx = EvalContext::new(&combined_schema, None);
8278 if aggregate::uses_aggregate(stmt) {
8281 let refs: Vec<&Row> = filtered.iter().collect();
8282 let mut agg = aggregate::run(stmt, &refs, &combined_schema, None)?;
8283 apply_offset_and_limit(&mut agg.rows, stmt.offset_literal(), stmt.limit_literal());
8284 return Ok(QueryResult::Rows {
8285 columns: agg.columns,
8286 rows: agg.rows,
8287 });
8288 }
8289
8290 let projection = build_projection(&stmt.items, &combined_schema, "")?;
8291 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::new();
8292 for row in &filtered {
8293 let mut values = Vec::with_capacity(projection.len());
8294 for p in &projection {
8295 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8296 }
8297 let order_keys = if stmt.order_by.is_empty() {
8298 Vec::new()
8299 } else {
8300 build_order_keys(&stmt.order_by, row, &ctx)?
8301 };
8302 tagged.push((order_keys, Row::new(values)));
8303 }
8304 if !stmt.order_by.is_empty() {
8305 let keep = if stmt.distinct {
8306 None
8307 } else {
8308 stmt.limit_literal()
8309 .map(|l| l as usize + stmt.offset_literal().map_or(0, |o| o as usize))
8310 };
8311 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
8312 partial_sort_tagged(&mut tagged, keep, &descs);
8313 }
8314 let mut output_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
8315 if stmt.distinct {
8316 output_rows = dedup_rows(output_rows);
8317 }
8318 apply_offset_and_limit(
8319 &mut output_rows,
8320 stmt.offset_literal(),
8321 stmt.limit_literal(),
8322 );
8323 let columns: Vec<ColumnSchema> = projection
8324 .into_iter()
8325 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8326 .collect();
8327 Ok(QueryResult::Rows {
8328 columns,
8329 rows: output_rows,
8330 })
8331 }
8332}
8333
8334#[derive(Debug, Clone)]
8337struct ProjectedItem {
8338 expr: Expr,
8339 output_name: String,
8340 ty: DataType,
8341 nullable: bool,
8342}
8343
8344fn dedup_rows(rows: Vec<Row>) -> Vec<Row> {
8350 let mut out: Vec<Row> = Vec::with_capacity(rows.len());
8351 for r in rows {
8352 if !out.iter().any(|seen| seen == &r) {
8353 out.push(r);
8354 }
8355 }
8356 out
8357}
8358
8359fn value_to_order_key(v: &Value) -> Result<f64, EngineError> {
8363 match v {
8364 Value::Null => Ok(f64::INFINITY),
8365 Value::SmallInt(n) => Ok(f64::from(*n)),
8366 Value::Int(n) => Ok(f64::from(*n)),
8367 Value::Date(d) => Ok(f64::from(*d)),
8368 #[allow(clippy::cast_precision_loss)]
8369 Value::Timestamp(t) => Ok(*t as f64),
8370 #[allow(clippy::cast_precision_loss)]
8373 Value::Time(us) => Ok(*us as f64),
8374 Value::Year(y) => Ok(f64::from(*y)),
8378 #[allow(clippy::cast_precision_loss)]
8383 Value::TimeTz { us, offset_secs } => Ok((us - i64::from(*offset_secs) * 1_000_000) as f64),
8384 #[allow(clippy::cast_precision_loss)]
8386 Value::Money(c) => Ok(*c as f64),
8387 Value::Range { .. } => Err(EngineError::Unsupported(
8390 "ORDER BY of a range value is not supported in v7.17.0".into(),
8391 )),
8392 Value::Hstore(_) => Err(EngineError::Unsupported(
8394 "ORDER BY of a hstore value is not supported".into(),
8395 )),
8396 Value::IntArray2D(_) | Value::BigIntArray2D(_) | Value::TextArray2D(_) => Err(
8398 EngineError::Unsupported("ORDER BY of a 2D array is not supported in v7.17.0".into()),
8399 ),
8400 #[allow(clippy::cast_precision_loss)]
8401 Value::Numeric { scaled, scale } => {
8402 let mut divisor = 1.0_f64;
8408 for _ in 0..*scale {
8409 divisor *= 10.0;
8410 }
8411 Ok((*scaled as f64) / divisor)
8412 }
8413 #[allow(clippy::cast_precision_loss)]
8414 Value::BigInt(n) => Ok(*n as f64),
8415 Value::Float(x) => Ok(*x),
8416 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
8417 Value::Text(s) => {
8418 let mut key: u64 = 0;
8422 for &b in s.as_bytes().iter().take(8) {
8423 key = (key << 8) | u64::from(b);
8424 }
8425 #[allow(clippy::cast_precision_loss)]
8426 Ok(key as f64)
8427 }
8428 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
8429 Err(EngineError::Unsupported(
8430 "ORDER BY of a raw vector column is not meaningful — use `<->`".into(),
8431 ))
8432 }
8433 Value::Interval { .. } => Err(EngineError::Unsupported(
8434 "ORDER BY of an INTERVAL is not supported in v2.11 \
8435 (months vs micros has no single canonical ordering)"
8436 .into(),
8437 )),
8438 Value::Json(_) => Err(EngineError::Unsupported(
8439 "ORDER BY of a JSON value is not supported — cast the document to text first".into(),
8440 )),
8441 _ => Err(EngineError::Unsupported(
8445 "ORDER BY of this value type is not supported".into(),
8446 )),
8447 }
8448}
8449
8450fn try_nsw_knn(
8464 stmt: &SelectStatement,
8465 table: &Table,
8466 schema_cols: &[ColumnSchema],
8467 table_alias: &str,
8468) -> Option<Vec<usize>> {
8469 if stmt.distinct {
8470 return None;
8471 }
8472 let limit = usize::try_from(stmt.limit_literal()?).ok()?;
8473 if limit == 0 {
8474 return None;
8475 }
8476 if stmt.order_by.len() != 1 {
8480 return None;
8481 }
8482 let order = &stmt.order_by[0];
8483 if order.desc {
8487 return None;
8488 }
8489 let Expr::Binary { lhs, op, rhs } = &order.expr else {
8490 return None;
8491 };
8492 let metric = match op {
8493 BinOp::L2Distance => spg_storage::NswMetric::L2,
8494 BinOp::InnerProduct => spg_storage::NswMetric::InnerProduct,
8495 BinOp::CosineDistance => spg_storage::NswMetric::Cosine,
8496 _ => return None,
8497 };
8498 let ((Expr::Column(col), literal) | (literal, Expr::Column(col))) =
8500 (lhs.as_ref(), rhs.as_ref())
8501 else {
8502 return None;
8503 };
8504 if let Some(q) = &col.qualifier
8505 && q != table_alias
8506 {
8507 return None;
8508 }
8509 let col_pos = schema_cols.iter().position(|s| s.name == col.name)?;
8510 let query = literal_to_vector(literal)?;
8511 let idx = spg_storage::nsw_index_on(table, col_pos)?;
8512 if let Some(where_expr) = &stmt.where_ {
8513 let over_fetch = limit.saturating_mul(10).max(NSW_OVER_FETCH_FLOOR);
8517 let candidates = spg_storage::nsw_query(table, &idx.name, &query, over_fetch, metric);
8518 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8519 let mut kept: Vec<usize> = Vec::with_capacity(limit);
8520 for i in candidates {
8521 let row = &table.rows()[i];
8522 let cond = eval::eval_expr(where_expr, row, &ctx).ok()?;
8523 if matches!(cond, Value::Bool(true)) {
8524 kept.push(i);
8525 if kept.len() >= limit {
8526 break;
8527 }
8528 }
8529 }
8530 Some(kept)
8531 } else {
8532 Some(spg_storage::nsw_query(
8533 table, &idx.name, &query, limit, metric,
8534 ))
8535 }
8536}
8537
8538const NSW_OVER_FETCH_FLOOR: usize = 32;
8542
8543fn literal_to_vector(e: &Expr) -> Option<Vec<f32>> {
8546 match e {
8547 Expr::Literal(Literal::Vector(v)) => Some(v.clone()),
8548 Expr::Cast { expr, .. } => literal_to_vector(expr),
8549 _ => None,
8550 }
8551}
8552
8553fn materialise_in_order(
8557 stmt: &SelectStatement,
8558 table: &Table,
8559 schema_cols: &[ColumnSchema],
8560 table_alias: &str,
8561 ordered_rows: &[usize],
8562) -> Result<QueryResult, EngineError> {
8563 let ctx = EvalContext::new(schema_cols, Some(table_alias));
8564 let projection = build_projection(&stmt.items, schema_cols, table_alias)?;
8565 let mut output_rows: Vec<Row> = Vec::with_capacity(ordered_rows.len());
8566 for &i in ordered_rows {
8567 let row = &table.rows()[i];
8568 let mut values = Vec::with_capacity(projection.len());
8569 for p in &projection {
8570 values.push(eval::eval_expr(&p.expr, row, &ctx)?);
8571 }
8572 output_rows.push(Row::new(values));
8573 }
8574 apply_offset_and_limit(
8575 &mut output_rows,
8576 stmt.offset_literal(),
8577 stmt.limit_literal(),
8578 );
8579 let columns: Vec<ColumnSchema> = projection
8580 .into_iter()
8581 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
8582 .collect();
8583 Ok(QueryResult::Rows {
8584 columns,
8585 rows: output_rows,
8586 })
8587}
8588
8589fn try_index_seek<'a>(
8590 where_expr: &Expr,
8591 schema_cols: &[ColumnSchema],
8592 catalog: &'a Catalog,
8593 table: &'a Table,
8594 table_alias: &str,
8595) -> Option<Vec<Cow<'a, Row>>> {
8596 if let Expr::Binary {
8603 lhs,
8604 op: BinOp::And,
8605 rhs,
8606 } = where_expr
8607 {
8608 if let Some(rows) = try_index_seek(lhs, schema_cols, catalog, table, table_alias) {
8611 return Some(rows);
8612 }
8613 return try_index_seek(rhs, schema_cols, catalog, table, table_alias);
8614 }
8615 let Expr::Binary {
8616 lhs,
8617 op: BinOp::Eq,
8618 rhs,
8619 } = where_expr
8620 else {
8621 return None;
8622 };
8623 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
8624 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
8625 let idx = table.index_on(col_pos)?;
8626 let key = IndexKey::from_value(&value)?;
8627 let locators = idx.lookup_eq(&key);
8628 let table_name = table.schema().name.as_str();
8629 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(locators.len());
8637 for loc in locators {
8638 match *loc {
8639 spg_storage::RowLocator::Hot(i) => {
8640 if let Some(row) = table.rows().get(i) {
8641 out.push(Cow::Borrowed(row));
8642 }
8643 }
8644 spg_storage::RowLocator::Cold { segment_id, .. } => {
8645 if let Some(row) = catalog.resolve_cold_locator(table_name, segment_id, &key) {
8646 out.push(Cow::Owned(row));
8647 }
8648 }
8649 }
8650 }
8651 Some(out)
8652}
8653
8654fn try_gin_seek<'a>(
8673 where_expr: &Expr,
8674 schema_cols: &[ColumnSchema],
8675 catalog: &'a Catalog,
8676 table: &'a Table,
8677 table_alias: &str,
8678 ctx: &eval::EvalContext<'_>,
8679) -> Option<Vec<Cow<'a, Row>>> {
8680 if let Expr::Binary {
8681 lhs,
8682 op: BinOp::And,
8683 rhs,
8684 } = where_expr
8685 {
8686 if let Some(rows) = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx) {
8687 return Some(rows);
8688 }
8689 return try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx);
8690 }
8691 if let Expr::Binary {
8700 lhs,
8701 op: BinOp::Or,
8702 rhs,
8703 } = where_expr
8704 {
8705 let left = try_gin_seek(lhs, schema_cols, catalog, table, table_alias, ctx)?;
8706 let right = try_gin_seek(rhs, schema_cols, catalog, table, table_alias, ctx)?;
8707 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(left.len() + right.len());
8708 out.extend(left);
8709 out.extend(right);
8710 return Some(out);
8711 }
8712 let Expr::Binary {
8713 lhs,
8714 op: BinOp::TsMatch,
8715 rhs,
8716 } = where_expr
8717 else {
8718 return None;
8719 };
8720 let (col_pos, query) = resolve_gin_col_query(lhs, rhs, schema_cols, table_alias, ctx)
8725 .or_else(|| resolve_gin_col_query(rhs, lhs, schema_cols, table_alias, ctx))?;
8726 let idx = table
8733 .indices()
8734 .iter()
8735 .find(|i| i.column_position == col_pos && (i.is_gin() || i.is_gin_fulltext()))?;
8736 let candidates = gin_query_candidates(idx, &query)?;
8737 let _ = catalog; let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(candidates.len());
8739 for loc in candidates {
8740 match loc {
8741 spg_storage::RowLocator::Hot(i) => {
8742 if let Some(row) = table.rows().get(i) {
8743 out.push(Cow::Borrowed(row));
8744 }
8745 }
8746 spg_storage::RowLocator::Cold { .. } => {}
8753 }
8754 }
8755 Some(out)
8756}
8757
8758fn try_trgm_seek<'a>(
8774 where_expr: &Expr,
8775 schema_cols: &[ColumnSchema],
8776 table: &'a Table,
8777 table_alias: &str,
8778) -> Option<Vec<Cow<'a, Row>>> {
8779 if let Expr::Binary {
8780 lhs,
8781 op: BinOp::And,
8782 rhs,
8783 } = where_expr
8784 {
8785 if let Some(rows) = try_trgm_seek(lhs, schema_cols, table, table_alias) {
8786 return Some(rows);
8787 }
8788 return try_trgm_seek(rhs, schema_cols, table, table_alias);
8789 }
8790 let Expr::Like { expr, pattern, .. } = where_expr else {
8796 return None;
8797 };
8798 let Expr::Column(c) = expr.as_ref() else {
8800 return None;
8801 };
8802 if let Some(q) = &c.qualifier
8803 && q != table_alias
8804 {
8805 return None;
8806 }
8807 let col_pos = schema_cols
8808 .iter()
8809 .position(|s| s.name.eq_ignore_ascii_case(&c.name))?;
8810 let idx = table
8812 .indices()
8813 .iter()
8814 .find(|i| i.column_position == col_pos && i.is_gin_trgm())?;
8815 let Expr::Literal(spg_sql::ast::Literal::String(pat)) = pattern.as_ref() else {
8819 return None;
8820 };
8821 let trigrams = spg_storage::trgm::trigrams_from_like_pattern(pat)?;
8822 let mut iter = trigrams.iter();
8825 let first = iter.next()?;
8826 let mut acc: Vec<spg_storage::RowLocator> = {
8827 let mut v = idx.gin_trgm_lookup(first).to_vec();
8828 v.sort_by_key(locator_sort_key);
8829 v.dedup_by_key(|l| locator_sort_key(l));
8830 v
8831 };
8832 for tri in iter {
8833 let mut next: Vec<spg_storage::RowLocator> = idx.gin_trgm_lookup(tri).to_vec();
8834 next.sort_by_key(locator_sort_key);
8835 next.dedup_by_key(|l| locator_sort_key(l));
8836 let mut merged: Vec<spg_storage::RowLocator> =
8838 Vec::with_capacity(acc.len().min(next.len()));
8839 let (mut i, mut j) = (0usize, 0usize);
8840 while i < acc.len() && j < next.len() {
8841 let lk = locator_sort_key(&acc[i]);
8842 let rk = locator_sort_key(&next[j]);
8843 match lk.cmp(&rk) {
8844 core::cmp::Ordering::Less => i += 1,
8845 core::cmp::Ordering::Greater => j += 1,
8846 core::cmp::Ordering::Equal => {
8847 merged.push(acc[i]);
8848 i += 1;
8849 j += 1;
8850 }
8851 }
8852 }
8853 acc = merged;
8854 if acc.is_empty() {
8855 break;
8856 }
8857 }
8858 let mut out: Vec<Cow<'a, Row>> = Vec::with_capacity(acc.len());
8859 for loc in acc {
8860 if let spg_storage::RowLocator::Hot(i) = loc
8861 && let Some(row) = table.rows().get(i)
8862 {
8863 out.push(Cow::Borrowed(row));
8864 }
8865 }
8867 Some(out)
8868}
8869
8870fn resolve_gin_col_query(
8876 col_side: &Expr,
8877 query_side: &Expr,
8878 schema_cols: &[ColumnSchema],
8879 table_alias: &str,
8880 ctx: &eval::EvalContext<'_>,
8881) -> Option<(usize, spg_storage::TsQueryAst)> {
8882 let column = match col_side {
8887 Expr::Column(c) => c,
8888 Expr::FunctionCall { name, args }
8889 if name.eq_ignore_ascii_case("to_tsvector") && !args.is_empty() =>
8890 {
8891 if let Expr::Column(c) = args.last().unwrap() {
8895 c
8896 } else {
8897 return None;
8898 }
8899 }
8900 _ => return None,
8901 };
8902 let c = column;
8903 if let Some(q) = &c.qualifier
8904 && q != table_alias
8905 {
8906 return None;
8907 }
8908 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
8909 let empty_row = Row::new(Vec::new());
8913 let v = eval::eval_expr(query_side, &empty_row, ctx).ok()?;
8914 let Value::TsQuery(q) = v else { return None };
8915 Some((pos, q))
8916}
8917
8918fn gin_query_candidates(
8929 idx: &spg_storage::Index,
8930 query: &spg_storage::TsQueryAst,
8931) -> Option<Vec<spg_storage::RowLocator>> {
8932 use spg_storage::TsQueryAst;
8933 match query {
8934 TsQueryAst::Term { word, .. } => {
8935 let mut v: Vec<spg_storage::RowLocator> = idx.gin_lookup_word(word).to_vec();
8936 v.sort_by_key(locator_sort_key);
8937 v.dedup_by_key(|l| locator_sort_key(l));
8938 Some(v)
8939 }
8940 TsQueryAst::And(l, r) => {
8941 let mut left = gin_query_candidates(idx, l)?;
8942 let mut right = gin_query_candidates(idx, r)?;
8943 left.sort_by_key(locator_sort_key);
8944 right.sort_by_key(locator_sort_key);
8945 let mut out: Vec<spg_storage::RowLocator> = Vec::new();
8947 let (mut i, mut j) = (0usize, 0usize);
8948 while i < left.len() && j < right.len() {
8949 let lk = locator_sort_key(&left[i]);
8950 let rk = locator_sort_key(&right[j]);
8951 match lk.cmp(&rk) {
8952 core::cmp::Ordering::Less => i += 1,
8953 core::cmp::Ordering::Greater => j += 1,
8954 core::cmp::Ordering::Equal => {
8955 out.push(left[i]);
8956 i += 1;
8957 j += 1;
8958 }
8959 }
8960 }
8961 Some(out)
8962 }
8963 TsQueryAst::Or(l, r) => {
8964 let mut out = gin_query_candidates(idx, l)?;
8965 out.extend(gin_query_candidates(idx, r)?);
8966 out.sort_by_key(locator_sort_key);
8967 out.dedup_by_key(|l| locator_sort_key(l));
8968 Some(out)
8969 }
8970 TsQueryAst::Not(_) | TsQueryAst::Phrase { .. } => None,
8975 }
8976}
8977
8978fn locator_sort_key(l: &spg_storage::RowLocator) -> (u8, u64, u64) {
8983 match *l {
8984 spg_storage::RowLocator::Hot(i) => (0, i as u64, 0),
8985 spg_storage::RowLocator::Cold {
8986 segment_id,
8987 page_offset,
8988 } => (1, u64::from(segment_id), u64::from(page_offset)),
8989 }
8990}
8991
8992fn try_pk_predicate(
9004 where_expr: &Expr,
9005 schema_cols: &[ColumnSchema],
9006 table_alias: &str,
9007) -> Option<(usize, IndexKey)> {
9008 let Expr::Binary {
9009 lhs,
9010 op: BinOp::Eq,
9011 rhs,
9012 } = where_expr
9013 else {
9014 return None;
9015 };
9016 let (col_pos, value) = resolve_col_literal_pair(lhs, rhs, schema_cols, table_alias)
9017 .or_else(|| resolve_col_literal_pair(rhs, lhs, schema_cols, table_alias))?;
9018 let key = IndexKey::from_value(&value)?;
9019 Some((col_pos, key))
9020}
9021
9022fn resolve_col_literal_pair(
9023 col_side: &Expr,
9024 lit_side: &Expr,
9025 schema_cols: &[ColumnSchema],
9026 table_alias: &str,
9027) -> Option<(usize, Value)> {
9028 let Expr::Column(c) = col_side else {
9029 return None;
9030 };
9031 if let Some(q) = &c.qualifier
9032 && q != table_alias
9033 {
9034 return None;
9035 }
9036 let pos = schema_cols.iter().position(|s| s.name == c.name)?;
9037 let Expr::Literal(l) = lit_side else {
9038 return None;
9039 };
9040 let v = match l {
9041 Literal::Integer(n) => {
9042 if let Ok(small) = i32::try_from(*n) {
9043 Value::Int(small)
9044 } else {
9045 Value::BigInt(*n)
9046 }
9047 }
9048 Literal::Float(x) => Value::Float(*x),
9049 Literal::String(s) => Value::Text(s.clone()),
9050 Literal::Bool(b) => Value::Bool(*b),
9051 Literal::Null => Value::Null,
9052 Literal::Vector(_) | Literal::Interval { .. } => return None,
9055 };
9056 Some((pos, v))
9057}
9058
9059fn resolve_projection_column<'a>(
9064 c: &ColumnName,
9065 schema_cols: &'a [ColumnSchema],
9066 table_alias: &str,
9067) -> Result<&'a ColumnSchema, EngineError> {
9068 if let Some(q) = &c.qualifier {
9069 let composite = alloc::format!("{q}.{name}", name = c.name);
9070 if let Some(s) = schema_cols.iter().find(|s| s.name == composite) {
9071 return Ok(s);
9072 }
9073 if q == table_alias
9076 && let Some(s) = schema_cols.iter().find(|s| s.name == c.name)
9077 {
9078 return Ok(s);
9079 }
9080 let prefix = alloc::format!("{q}.");
9084 let qualifier_known =
9085 q == table_alias || schema_cols.iter().any(|s| s.name.starts_with(&prefix));
9086 if !qualifier_known {
9087 return Err(EngineError::Eval(EvalError::UnknownQualifier {
9088 qualifier: q.clone(),
9089 }));
9090 }
9091 return Err(EngineError::Eval(EvalError::ColumnNotFound {
9092 name: c.name.clone(),
9093 }));
9094 }
9095 if let Some(s) = schema_cols.iter().find(|s| s.name == c.name) {
9096 return Ok(s);
9097 }
9098 let suffix = alloc::format!(".{name}", name = c.name);
9099 let mut matches = schema_cols.iter().filter(|s| s.name.ends_with(&suffix));
9100 let first = matches.next();
9101 let extra = matches.next();
9102 match (first, extra) {
9103 (Some(s), None) => Ok(s),
9104 (Some(_), Some(_)) => Err(EngineError::Eval(EvalError::TypeMismatch {
9105 detail: alloc::format!("ambiguous column reference: {}", c.name),
9106 })),
9107 _ => Err(EngineError::Eval(EvalError::ColumnNotFound {
9108 name: c.name.clone(),
9109 })),
9110 }
9111}
9112
9113fn build_projection(
9114 items: &[SelectItem],
9115 schema_cols: &[ColumnSchema],
9116 table_alias: &str,
9117) -> Result<Vec<ProjectedItem>, EngineError> {
9118 let mut out = Vec::new();
9119 for item in items {
9120 match item {
9121 SelectItem::Wildcard => {
9122 for col in schema_cols {
9123 out.push(ProjectedItem {
9124 expr: Expr::Column(ColumnName {
9125 qualifier: None,
9126 name: col.name.clone(),
9127 }),
9128 output_name: col.name.clone(),
9129 ty: col.ty,
9130 nullable: col.nullable,
9131 });
9132 }
9133 }
9134 SelectItem::Expr { expr, alias } => {
9135 if let Expr::Column(c) = expr {
9142 let sch = resolve_projection_column(c, schema_cols, table_alias)?;
9143 let output_name = alias.clone().unwrap_or_else(|| c.name.clone());
9144 out.push(ProjectedItem {
9145 expr: expr.clone(),
9146 output_name,
9147 ty: sch.ty,
9148 nullable: sch.nullable,
9149 });
9150 } else if let Some(shape) = describe::describe_expr(expr, schema_cols) {
9151 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9152 out.push(ProjectedItem {
9153 expr: expr.clone(),
9154 output_name,
9155 ty: shape.ty,
9156 nullable: shape.nullable,
9157 });
9158 } else {
9159 let output_name = alias.clone().unwrap_or_else(|| expr.to_string());
9160 out.push(ProjectedItem {
9161 expr: expr.clone(),
9162 output_name,
9163 ty: DataType::Text,
9164 nullable: true,
9165 });
9166 }
9167 }
9168 }
9169 }
9170 Ok(out)
9171}
9172
9173fn numeric_from_integer(
9177 n: i128,
9178 precision: u8,
9179 scale: u8,
9180 col_name: &str,
9181) -> Result<Value, EngineError> {
9182 let factor = pow10_i128(scale);
9183 let scaled = n.checked_mul(factor).ok_or_else(|| {
9184 EngineError::Unsupported(alloc::format!(
9185 "integer overflow scaling value for column `{col_name}` to scale {scale}"
9186 ))
9187 })?;
9188 check_precision(scaled, precision, col_name)?;
9189 Ok(Value::Numeric { scaled, scale })
9190}
9191
9192#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
9195fn numeric_from_float(
9196 x: f64,
9197 precision: u8,
9198 scale: u8,
9199 col_name: &str,
9200) -> Result<Value, EngineError> {
9201 if !x.is_finite() {
9202 return Err(EngineError::Unsupported(alloc::format!(
9203 "cannot store non-finite float in NUMERIC column `{col_name}`"
9204 )));
9205 }
9206 let mut factor = 1.0_f64;
9207 for _ in 0..scale {
9208 factor *= 10.0;
9209 }
9210 let shifted = x * factor;
9215 let biased = if shifted >= 0.0 {
9216 shifted + 0.5
9217 } else {
9218 shifted - 0.5
9219 };
9220 if !(-1e38..=1e38).contains(&biased) {
9223 return Err(EngineError::Unsupported(alloc::format!(
9224 "value {x} overflows NUMERIC range for column `{col_name}`"
9225 )));
9226 }
9227 let scaled = biased as i128;
9228 check_precision(scaled, precision, col_name)?;
9229 Ok(Value::Numeric { scaled, scale })
9230}
9231
9232fn parse_numeric_text(s: &str) -> Option<(i128, u8)> {
9239 let s = s.trim();
9240 if s.is_empty() {
9241 return None;
9242 }
9243 let (negative, rest) = match s.as_bytes()[0] {
9244 b'-' => (true, &s[1..]),
9245 b'+' => (false, &s[1..]),
9246 _ => (false, s),
9247 };
9248 if rest.is_empty() {
9249 return None;
9250 }
9251 if rest.bytes().any(|b| b == b'e' || b == b'E') {
9255 return None;
9256 }
9257 let (int_part, frac_part) = match rest.find('.') {
9258 Some(idx) => (&rest[..idx], &rest[idx + 1..]),
9259 None => (rest, ""),
9260 };
9261 if int_part.is_empty() && frac_part.is_empty() {
9262 return None;
9263 }
9264 if int_part.bytes().any(|b| !b.is_ascii_digit()) {
9265 return None;
9266 }
9267 if frac_part.bytes().any(|b| !b.is_ascii_digit()) {
9268 return None;
9269 }
9270 let scale_u32 = u32::try_from(frac_part.len()).ok()?;
9271 if scale_u32 > u32::from(u8::MAX) {
9272 return None;
9273 }
9274 let scale = scale_u32 as u8;
9275 let mut digits = alloc::string::String::with_capacity(int_part.len() + frac_part.len() + 1);
9276 if negative {
9277 digits.push('-');
9278 }
9279 digits.push_str(int_part);
9280 digits.push_str(frac_part);
9281 let digits = if digits == "-" {
9283 return None;
9284 } else if digits.is_empty() {
9285 "0"
9286 } else {
9287 digits.as_str()
9288 };
9289 let mantissa: i128 = digits.parse().ok()?;
9290 Some((mantissa, scale))
9291}
9292
9293fn numeric_rescale(
9296 scaled: i128,
9297 src_scale: u8,
9298 precision: u8,
9299 dst_scale: u8,
9300 col_name: &str,
9301) -> Result<Value, EngineError> {
9302 let new_scaled = if dst_scale >= src_scale {
9303 let bump = pow10_i128(dst_scale - src_scale);
9304 scaled.checked_mul(bump).ok_or_else(|| {
9305 EngineError::Unsupported(alloc::format!(
9306 "overflow rescaling NUMERIC for column `{col_name}`"
9307 ))
9308 })?
9309 } else {
9310 let drop = pow10_i128(src_scale - dst_scale);
9311 let half = drop / 2;
9312 if scaled >= 0 {
9313 (scaled + half) / drop
9314 } else {
9315 (scaled - half) / drop
9316 }
9317 };
9318 check_precision(new_scaled, precision, col_name)?;
9319 Ok(Value::Numeric {
9320 scaled: new_scaled,
9321 scale: dst_scale,
9322 })
9323}
9324
9325const fn numeric_truncate_to_integer(scaled: i128, scale: u8) -> i128 {
9328 if scale == 0 {
9329 return scaled;
9330 }
9331 let factor = pow10_i128_const(scale);
9332 scaled / factor
9333}
9334
9335fn check_precision(scaled: i128, precision: u8, col_name: &str) -> Result<(), EngineError> {
9339 if precision == 0 {
9340 return Ok(());
9341 }
9342 let limit = pow10_i128(precision);
9343 if scaled.unsigned_abs() >= limit.unsigned_abs() {
9344 return Err(EngineError::Unsupported(alloc::format!(
9345 "NUMERIC value exceeds precision {precision} for column `{col_name}`"
9346 )));
9347 }
9348 Ok(())
9349}
9350
9351const fn pow10_i128_const(p: u8) -> i128 {
9352 let mut acc: i128 = 1;
9353 let mut i = 0;
9354 while i < p {
9355 acc *= 10;
9356 i += 1;
9357 }
9358 acc
9359}
9360
9361fn pow10_i128(p: u8) -> i128 {
9362 pow10_i128_const(p)
9363}
9364
9365impl Engine {
9380 #[allow(
9391 clippy::too_many_lines,
9392 clippy::type_complexity,
9393 clippy::needless_range_loop
9394 )] fn exec_select_with_window(
9396 &self,
9397 stmt: &SelectStatement,
9398 cancel: CancelToken<'_>,
9399 ) -> Result<QueryResult, EngineError> {
9400 let from = stmt.from.as_ref().ok_or_else(|| {
9401 EngineError::Unsupported("window functions require a FROM clause".into())
9402 })?;
9403 let (schema_cols_owned, alias_opt): (Vec<ColumnSchema>, Option<&str>);
9412 let filtered: Vec<Row>;
9413 if from.joins.is_empty() {
9414 let primary = &from.primary;
9415 let table = self.active_catalog().get(&primary.name).ok_or_else(|| {
9416 StorageError::TableNotFound {
9417 name: primary.name.clone(),
9418 }
9419 })?;
9420 let alias = primary.alias.as_deref().unwrap_or(primary.name.as_str());
9421 schema_cols_owned = table.schema().columns.clone();
9422 alias_opt = Some(alias);
9423 let ctx = self.ev_ctx(&schema_cols_owned, alias_opt);
9428 let mut owned: Vec<Row> = Vec::new();
9429 for (i, row) in table.rows().iter().enumerate() {
9430 if i.is_multiple_of(256) {
9431 cancel.check()?;
9432 }
9433 if let Some(w) = &stmt.where_ {
9434 let cond = eval::eval_expr(w, row, &ctx)?;
9435 if !matches!(cond, Value::Bool(true)) {
9436 continue;
9437 }
9438 }
9439 owned.push(row.clone());
9440 }
9441 filtered = owned;
9442 } else {
9443 let (combined_schema, rows) =
9444 self.build_joined_filtered_rows(from, stmt.where_.as_ref())?;
9445 schema_cols_owned = combined_schema;
9446 alias_opt = None;
9447 filtered = rows;
9448 }
9449 let schema_cols = &schema_cols_owned;
9450 let ctx = self.ev_ctx(schema_cols, alias_opt);
9451 let alias = alias_opt.unwrap_or("");
9452 let n_rows = filtered.len();
9453 let filtered_refs: Vec<&Row> = filtered.iter().collect();
9457
9458 let mut window_nodes: Vec<Expr> = Vec::new();
9460 for item in &stmt.items {
9461 if let SelectItem::Expr { expr, .. } = item {
9462 collect_window_nodes(expr, &mut window_nodes);
9463 }
9464 }
9465
9466 let mut win_vals: Vec<Vec<Value>> = Vec::with_capacity(window_nodes.len());
9469 for wnode in &window_nodes {
9470 let Expr::WindowFunction {
9471 name,
9472 args,
9473 partition_by,
9474 order_by,
9475 frame,
9476 null_treatment,
9477 } = wnode
9478 else {
9479 unreachable!("collect_window_nodes pushes only WindowFunction");
9480 };
9481 let mut indexed: Vec<(Vec<Value>, Vec<(Value, bool)>, usize)> =
9483 Vec::with_capacity(n_rows);
9484 for (i, row) in filtered.iter().enumerate() {
9485 let pkey: Vec<Value> = partition_by
9486 .iter()
9487 .map(|p| eval::eval_expr(p, row, &ctx))
9488 .collect::<Result<_, _>>()?;
9489 let okey: Vec<(Value, bool)> = order_by
9490 .iter()
9491 .map(|(e, desc)| eval::eval_expr(e, row, &ctx).map(|v| (v, *desc)))
9492 .collect::<Result<_, _>>()?;
9493 indexed.push((pkey, okey, i));
9494 }
9495 indexed.sort_by(|a, b| {
9498 let p_cmp = partition_key_cmp(&a.0, &b.0);
9499 if p_cmp != core::cmp::Ordering::Equal {
9500 return p_cmp;
9501 }
9502 order_key_cmp(&a.1, &b.1)
9503 });
9504 let mut out_vals: Vec<Value> = alloc::vec![Value::Null; n_rows];
9506 let mut p_start = 0;
9507 while p_start < indexed.len() {
9508 let mut p_end = p_start + 1;
9509 while p_end < indexed.len()
9510 && partition_key_cmp(&indexed[p_start].0, &indexed[p_end].0)
9511 == core::cmp::Ordering::Equal
9512 {
9513 p_end += 1;
9514 }
9515 compute_window_partition(
9517 name,
9518 args,
9519 !order_by.is_empty(),
9520 frame.as_ref(),
9521 *null_treatment,
9522 &indexed[p_start..p_end],
9523 &filtered_refs,
9524 &ctx,
9525 &mut out_vals,
9526 )?;
9527 p_start = p_end;
9528 }
9529 win_vals.push(out_vals);
9530 }
9531
9532 let mut ext_cols = schema_cols.clone();
9534 for i in 0..window_nodes.len() {
9535 ext_cols.push(ColumnSchema::new(
9536 alloc::format!("__win_{i}"),
9537 DataType::Text, true,
9539 ));
9540 }
9541 let mut ext_rows: Vec<Row> = Vec::with_capacity(n_rows);
9543 for i in 0..n_rows {
9544 let mut values = filtered[i].values.clone();
9545 for w in 0..window_nodes.len() {
9546 values.push(win_vals[w][i].clone());
9547 }
9548 ext_rows.push(Row::new(values));
9549 }
9550 let mut rewritten_items: Vec<SelectItem> = Vec::with_capacity(stmt.items.len());
9552 for item in &stmt.items {
9553 let new_item = match item {
9554 SelectItem::Wildcard => SelectItem::Wildcard,
9555 SelectItem::Expr { expr, alias } => {
9556 let mut e = expr.clone();
9557 rewrite_window_to_columns(&mut e, &window_nodes);
9558 SelectItem::Expr {
9559 expr: e,
9560 alias: alias.clone(),
9561 }
9562 }
9563 };
9564 rewritten_items.push(new_item);
9565 }
9566
9567 let ext_ctx = EvalContext::new(&ext_cols, alias_opt);
9573 let projection = build_projection(&rewritten_items, &ext_cols, alias)?;
9574 let mut tagged: Vec<(Vec<f64>, Row)> = Vec::with_capacity(n_rows);
9575 for (i, row) in ext_rows.iter().enumerate() {
9576 if i.is_multiple_of(256) {
9577 cancel.check()?;
9578 }
9579 let mut values = Vec::with_capacity(projection.len());
9580 for p in &projection {
9581 values.push(eval::eval_expr(&p.expr, row, &ext_ctx)?);
9582 }
9583 let order_keys = if stmt.order_by.is_empty() {
9584 Vec::new()
9585 } else {
9586 let mut keys = Vec::with_capacity(stmt.order_by.len());
9587 for o in &stmt.order_by {
9588 let mut e = o.expr.clone();
9589 rewrite_window_to_columns(&mut e, &window_nodes);
9590 let key = eval::eval_expr(&e, row, &ext_ctx)?;
9591 keys.push(value_to_order_key(&key)?);
9592 }
9593 keys
9594 };
9595 tagged.push((order_keys, Row::new(values)));
9596 }
9597 if !stmt.order_by.is_empty() {
9599 let descs: Vec<bool> = stmt.order_by.iter().map(|o| o.desc).collect();
9600 sort_by_keys(&mut tagged, &descs);
9601 }
9602 let mut out_rows: Vec<Row> = tagged.into_iter().map(|(_, r)| r).collect();
9603 apply_offset_and_limit(&mut out_rows, stmt.offset_literal(), stmt.limit_literal());
9604 let final_cols: Vec<ColumnSchema> = projection
9605 .into_iter()
9606 .map(|p| ColumnSchema::new(p.output_name, p.ty, p.nullable))
9607 .collect();
9608 Ok(QueryResult::Rows {
9609 columns: final_cols,
9610 rows: out_rows,
9611 })
9612 }
9613
9614 fn exec_select_with_meta_views(
9631 &self,
9632 stmt: &SelectStatement,
9633 cancel: CancelToken<'_>,
9634 ) -> Result<QueryResult, EngineError> {
9635 let mut needed: alloc::collections::BTreeSet<String> = alloc::collections::BTreeSet::new();
9636 collect_meta_view_names(stmt, &mut needed);
9637 let mut catalog = self.active_catalog().clone();
9638 for view in &needed {
9639 if catalog.get(view).is_some() {
9640 continue;
9641 }
9642 match view.as_str() {
9643 "__spg_info_columns" => {
9644 let (schema, rows) = synth_information_schema_columns(self.active_catalog());
9645 materialise_meta_view(&mut catalog, view, schema, rows)?;
9646 }
9647 "__spg_info_tables" => {
9648 let (schema, rows) = synth_information_schema_tables(self.active_catalog());
9649 materialise_meta_view(&mut catalog, view, schema, rows)?;
9650 }
9651 "__spg_pg_class" => {
9652 let (schema, rows) = synth_pg_class(self.active_catalog());
9653 materialise_meta_view(&mut catalog, view, schema, rows)?;
9654 }
9655 "__spg_pg_attribute" => {
9656 let (schema, rows) = synth_pg_attribute(self.active_catalog());
9657 materialise_meta_view(&mut catalog, view, schema, rows)?;
9658 }
9659 "__spg_pg_type" => {
9662 let (schema, rows) = synth_pg_type(self.active_catalog());
9663 materialise_meta_view(&mut catalog, view, schema, rows)?;
9664 }
9665 "__spg_pg_proc" => {
9668 let (schema, rows) = synth_pg_proc(self.active_catalog());
9669 materialise_meta_view(&mut catalog, view, schema, rows)?;
9670 }
9671 "__spg_pg_namespace" => {
9674 let (schema, rows) = synth_pg_namespace(self.active_catalog());
9675 materialise_meta_view(&mut catalog, view, schema, rows)?;
9676 }
9677 "__spg_pg_indexes" => {
9680 let (schema, rows) = synth_pg_indexes(self.active_catalog());
9681 materialise_meta_view(&mut catalog, view, schema, rows)?;
9682 }
9683 "__spg_pg_index" => {
9686 let (schema, rows) = synth_pg_index_raw(self.active_catalog());
9687 materialise_meta_view(&mut catalog, view, schema, rows)?;
9688 }
9689 "__spg_pg_constraint" => {
9692 let (schema, rows) = synth_pg_constraint(self.active_catalog());
9693 materialise_meta_view(&mut catalog, view, schema, rows)?;
9694 }
9695 "__spg_pg_database" => {
9700 let (schema, rows) = synth_pg_database(self.active_catalog());
9701 materialise_meta_view(&mut catalog, view, schema, rows)?;
9702 }
9703 "__spg_pg_roles" | "__spg_pg_user" => {
9704 let (schema, rows) = synth_pg_roles(self);
9705 materialise_meta_view(&mut catalog, view, schema, rows)?;
9706 }
9707 "__spg_pg_views" => {
9711 let (schema, rows) = synth_pg_views(self.active_catalog());
9712 materialise_meta_view(&mut catalog, view, schema, rows)?;
9713 }
9714 "__spg_pg_matviews" => {
9718 let (schema, _) = synth_pg_views(self.active_catalog());
9719 materialise_meta_view(&mut catalog, view, schema, Vec::new())?;
9720 }
9721 "__spg_pg_settings" => {
9723 let (schema, rows) = synth_pg_settings(self);
9724 materialise_meta_view(&mut catalog, view, schema, rows)?;
9725 }
9726 "__spg_info_key_column_usage" => {
9728 let (schema, rows) = synth_info_key_column_usage(self.active_catalog());
9729 materialise_meta_view(&mut catalog, view, schema, rows)?;
9730 }
9731 "__spg_info_referential_constraints" => {
9733 let (schema, rows) = synth_info_referential_constraints(self.active_catalog());
9734 materialise_meta_view(&mut catalog, view, schema, rows)?;
9735 }
9736 "__spg_info_statistics" => {
9738 let (schema, rows) = synth_info_statistics(self.active_catalog());
9739 materialise_meta_view(&mut catalog, view, schema, rows)?;
9740 }
9741 "__spg_info_routines" => {
9743 let (schema, rows) = synth_info_routines();
9744 materialise_meta_view(&mut catalog, view, schema, rows)?;
9745 }
9746 "__spg_mysql_user" => {
9748 let (schema, rows) = synth_mysql_user(self);
9749 materialise_meta_view(&mut catalog, view, schema, rows)?;
9750 }
9751 "__spg_mysql_db" => {
9752 let (schema, rows) = synth_mysql_db();
9753 materialise_meta_view(&mut catalog, view, schema, rows)?;
9754 }
9755 _ => {
9756 return Err(EngineError::Unsupported(alloc::format!(
9757 "meta view {view:?} is not yet materialisable; \
9758 v7.16.2 covers information_schema.columns / .tables \
9759 and pg_catalog.pg_class / pg_attribute; \
9760 v7.17.0 P0-50..P0-57 add pg_type / pg_proc / pg_namespace / \
9761 pg_indexes / pg_index / pg_constraint / pg_database / pg_roles / \
9762 pg_user / pg_views / pg_matviews / pg_settings"
9763 )));
9764 }
9765 }
9766 }
9767 let mut temp = Engine::restore(catalog);
9768 if let Some(c) = self.clock {
9769 temp = temp.with_clock(c);
9770 }
9771 if let Some(f) = self.salt_fn {
9772 temp = temp.with_salt_fn(f);
9773 }
9774 temp.meta_views_materialised = true;
9775 temp.exec_select_cancel(stmt, cancel)
9776 }
9777
9778 fn exec_with_ctes(
9779 &self,
9780 stmt: &SelectStatement,
9781 cancel: CancelToken<'_>,
9782 ) -> Result<QueryResult, EngineError> {
9783 cancel.check()?;
9784 let mut catalog = self.active_catalog().clone();
9785 for cte in &stmt.ctes {
9786 if catalog.get(&cte.name).is_some() {
9787 return Err(EngineError::Unsupported(alloc::format!(
9788 "CTE name {:?} shadows an existing table; rename the CTE",
9789 cte.name
9790 )));
9791 }
9792 let (columns, rows) = if cte.recursive {
9793 self.materialise_recursive_cte(cte, &catalog, cancel)?
9794 } else {
9795 let body_result = self.exec_select_cancel(&cte.body, cancel)?;
9796 let QueryResult::Rows { columns, rows } = body_result else {
9797 return Err(EngineError::Unsupported(alloc::format!(
9798 "CTE {:?} body did not return rows",
9799 cte.name
9800 )));
9801 };
9802 (columns, rows)
9803 };
9804 let inferred = infer_column_types(&columns, &rows);
9809 let mut columns = inferred;
9810 if !cte.column_overrides.is_empty() {
9812 if cte.column_overrides.len() != columns.len() {
9813 return Err(EngineError::Unsupported(alloc::format!(
9814 "CTE {:?} column list has {} names but body returns {} columns",
9815 cte.name,
9816 cte.column_overrides.len(),
9817 columns.len()
9818 )));
9819 }
9820 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9821 col.name.clone_from(name);
9822 }
9823 }
9824 let schema = TableSchema::new(cte.name.clone(), columns);
9825 catalog.create_table(schema).map_err(EngineError::Storage)?;
9826 let table = catalog
9827 .get_mut(&cte.name)
9828 .expect("just-created CTE table must exist");
9829 for row in rows {
9830 table.insert(row).map_err(EngineError::Storage)?;
9831 }
9832 }
9833 let mut body = stmt.clone();
9836 body.ctes = Vec::new();
9837 let mut temp = Engine::restore(catalog);
9838 if let Some(c) = self.clock {
9839 temp = temp.with_clock(c);
9840 }
9841 if let Some(f) = self.salt_fn {
9842 temp = temp.with_salt_fn(f);
9843 }
9844 temp.exec_select_cancel(&body, cancel)
9845 }
9846
9847 #[allow(clippy::too_many_lines)]
9857 fn materialise_recursive_cte(
9858 &self,
9859 cte: &spg_sql::ast::Cte,
9860 base_catalog: &Catalog,
9861 cancel: CancelToken<'_>,
9862 ) -> Result<(Vec<ColumnSchema>, Vec<Row>), EngineError> {
9863 const MAX_TOTAL_ROWS: usize = 1_000_000;
9864 const MAX_ITERATIONS: usize = 100_000;
9865 cancel.check()?;
9866 if cte.body.unions.is_empty() {
9867 return Err(EngineError::Unsupported(alloc::format!(
9868 "WITH RECURSIVE {:?} body must be a UNION of an anchor and a recursive term",
9869 cte.name
9870 )));
9871 }
9872 let mut anchor = cte.body.clone();
9874 let union_terms = core::mem::take(&mut anchor.unions);
9875 anchor.ctes = Vec::new();
9876 if select_refers_to(&anchor, &cte.name) {
9878 return Err(EngineError::Unsupported(alloc::format!(
9879 "WITH RECURSIVE {:?}: the anchor must not reference the CTE itself",
9880 cte.name
9881 )));
9882 }
9883 let anchor_result = self.exec_select_cancel(&anchor, cancel)?;
9884 let QueryResult::Rows {
9885 columns: anchor_cols,
9886 rows: anchor_rows,
9887 } = anchor_result
9888 else {
9889 return Err(EngineError::Unsupported(alloc::format!(
9890 "WITH RECURSIVE {:?}: anchor did not return rows",
9891 cte.name
9892 )));
9893 };
9894 let mut columns = infer_column_types(&anchor_cols, &anchor_rows);
9898 if !cte.column_overrides.is_empty() {
9899 if cte.column_overrides.len() != columns.len() {
9900 return Err(EngineError::Unsupported(alloc::format!(
9901 "CTE {:?} column list has {} names but anchor returns {} columns",
9902 cte.name,
9903 cte.column_overrides.len(),
9904 columns.len()
9905 )));
9906 }
9907 for (col, name) in columns.iter_mut().zip(cte.column_overrides.iter()) {
9908 col.name.clone_from(name);
9909 }
9910 }
9911 let mut all_rows: Vec<Row> = anchor_rows.clone();
9912 let mut working_set: Vec<Row> = anchor_rows;
9913 let mut seen: alloc::collections::BTreeSet<Vec<u8>> = alloc::collections::BTreeSet::new();
9914 let all_union_all = union_terms.iter().all(|(k, _)| matches!(k, UnionKind::All));
9917 if !all_union_all {
9918 for r in &all_rows {
9919 seen.insert(encode_row_key(r));
9920 }
9921 }
9922 for iter in 0..MAX_ITERATIONS {
9923 cancel.check()?;
9924 if working_set.is_empty() {
9925 break;
9926 }
9927 let mut iter_catalog = base_catalog.clone();
9929 let schema = TableSchema::new(cte.name.clone(), columns.clone());
9930 iter_catalog
9931 .create_table(schema)
9932 .map_err(EngineError::Storage)?;
9933 {
9934 let table = iter_catalog.get_mut(&cte.name).expect("just-created");
9935 for row in &working_set {
9936 table.insert(row.clone()).map_err(EngineError::Storage)?;
9937 }
9938 }
9939 let mut iter_engine = Engine::restore(iter_catalog);
9940 if let Some(c) = self.clock {
9941 iter_engine = iter_engine.with_clock(c);
9942 }
9943 if let Some(f) = self.salt_fn {
9944 iter_engine = iter_engine.with_salt_fn(f);
9945 }
9946 let mut next_set: Vec<Row> = Vec::new();
9948 for (_, term) in &union_terms {
9949 let mut term = term.clone();
9950 term.ctes = Vec::new();
9951 let r = iter_engine.exec_select_cancel(&term, cancel)?;
9952 let QueryResult::Rows {
9953 columns: rc,
9954 rows: rs,
9955 } = r
9956 else {
9957 return Err(EngineError::Unsupported(alloc::format!(
9958 "WITH RECURSIVE {:?}: recursive term did not return rows",
9959 cte.name
9960 )));
9961 };
9962 if rc.len() != columns.len() {
9963 return Err(EngineError::Unsupported(alloc::format!(
9964 "WITH RECURSIVE {:?}: column count of recursive term ({}) does not match anchor ({})",
9965 cte.name,
9966 rc.len(),
9967 columns.len()
9968 )));
9969 }
9970 for row in rs {
9971 if !all_union_all {
9972 let key = encode_row_key(&row);
9973 if !seen.insert(key) {
9974 continue;
9975 }
9976 }
9977 next_set.push(row);
9978 }
9979 }
9980 if next_set.is_empty() {
9981 break;
9982 }
9983 all_rows.extend(next_set.iter().cloned());
9984 working_set = next_set;
9985 if all_rows.len() > MAX_TOTAL_ROWS {
9986 return Err(EngineError::Unsupported(alloc::format!(
9987 "WITH RECURSIVE {:?}: produced more than {MAX_TOTAL_ROWS} rows — likely runaway recursion",
9988 cte.name
9989 )));
9990 }
9991 if iter + 1 == MAX_ITERATIONS {
9992 return Err(EngineError::Unsupported(alloc::format!(
9993 "WITH RECURSIVE {:?}: exceeded {MAX_ITERATIONS} iterations",
9994 cte.name
9995 )));
9996 }
9997 }
9998 Ok((columns, all_rows))
9999 }
10000
10001 fn resolve_select_subqueries(
10002 &self,
10003 stmt: &mut SelectStatement,
10004 cancel: CancelToken<'_>,
10005 ) -> Result<(), EngineError> {
10006 for item in &mut stmt.items {
10007 if let SelectItem::Expr { expr, .. } = item {
10008 self.resolve_expr_subqueries(expr, cancel)?;
10009 }
10010 }
10011 if let Some(w) = &mut stmt.where_ {
10012 self.resolve_expr_subqueries(w, cancel)?;
10013 }
10014 if let Some(gs) = &mut stmt.group_by {
10015 for g in gs {
10016 self.resolve_expr_subqueries(g, cancel)?;
10017 }
10018 }
10019 if let Some(h) = &mut stmt.having {
10020 self.resolve_expr_subqueries(h, cancel)?;
10021 }
10022 for o in &mut stmt.order_by {
10023 self.resolve_expr_subqueries(&mut o.expr, cancel)?;
10024 }
10025 for (_, peer) in &mut stmt.unions {
10026 self.resolve_select_subqueries(peer, cancel)?;
10027 }
10028 Ok(())
10029 }
10030
10031 #[allow(clippy::only_used_in_recursion)] fn resolve_expr_subqueries(
10033 &self,
10034 e: &mut Expr,
10035 cancel: CancelToken<'_>,
10036 ) -> Result<(), EngineError> {
10037 if let Some(replacement) = self.subquery_replacement(e, cancel)? {
10039 *e = replacement;
10040 return Ok(());
10041 }
10042 match e {
10043 Expr::Binary { lhs, rhs, .. } => {
10044 self.resolve_expr_subqueries(lhs, cancel)?;
10045 self.resolve_expr_subqueries(rhs, cancel)?;
10046 }
10047 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10048 self.resolve_expr_subqueries(expr, cancel)?;
10049 }
10050 Expr::FunctionCall { args, .. } => {
10051 for a in args {
10052 self.resolve_expr_subqueries(a, cancel)?;
10053 }
10054 }
10055 Expr::Like { expr, pattern, .. } => {
10056 self.resolve_expr_subqueries(expr, cancel)?;
10057 self.resolve_expr_subqueries(pattern, cancel)?;
10058 }
10059 Expr::Extract { source, .. } => self.resolve_expr_subqueries(source, cancel)?,
10060 Expr::WindowFunction {
10063 args,
10064 partition_by,
10065 order_by,
10066 ..
10067 } => {
10068 for a in args {
10069 self.resolve_expr_subqueries(a, cancel)?;
10070 }
10071 for p in partition_by {
10072 self.resolve_expr_subqueries(p, cancel)?;
10073 }
10074 for (e, _) in order_by {
10075 self.resolve_expr_subqueries(e, cancel)?;
10076 }
10077 }
10078 Expr::ScalarSubquery(_)
10082 | Expr::Exists { .. }
10083 | Expr::InSubquery { .. }
10084 | Expr::Literal(_)
10085 | Expr::Placeholder(_)
10086 | Expr::Column(_) => {}
10087 Expr::Array(items) => {
10089 for elem in items {
10090 self.resolve_expr_subqueries(elem, cancel)?;
10091 }
10092 }
10093 Expr::ArraySubscript { target, index } => {
10094 self.resolve_expr_subqueries(target, cancel)?;
10095 self.resolve_expr_subqueries(index, cancel)?;
10096 }
10097 Expr::AnyAll { expr, array, .. } => {
10098 self.resolve_expr_subqueries(expr, cancel)?;
10099 self.resolve_expr_subqueries(array, cancel)?;
10100 }
10101 Expr::Case {
10102 operand,
10103 branches,
10104 else_branch,
10105 } => {
10106 if let Some(o) = operand {
10107 self.resolve_expr_subqueries(o, cancel)?;
10108 }
10109 for (w, t) in branches {
10110 self.resolve_expr_subqueries(w, cancel)?;
10111 self.resolve_expr_subqueries(t, cancel)?;
10112 }
10113 if let Some(e) = else_branch {
10114 self.resolve_expr_subqueries(e, cancel)?;
10115 }
10116 }
10117 }
10118 Ok(())
10119 }
10120
10121 fn eval_expr_with_correlated(
10129 &self,
10130 expr: &Expr,
10131 row: &Row,
10132 ctx: &EvalContext<'_>,
10133 cancel: CancelToken<'_>,
10134 memo: Option<&mut memoize::MemoizeCache>,
10135 ) -> Result<Value, EngineError> {
10136 if !expr_has_subquery(expr) {
10137 return eval::eval_expr(expr, row, ctx).map_err(EngineError::Eval);
10138 }
10139 let mut e = expr.clone();
10140 self.resolve_correlated_in_expr(&mut e, row, ctx, cancel, memo)?;
10141 eval::eval_expr(&e, row, ctx).map_err(EngineError::Eval)
10142 }
10143
10144 fn resolve_correlated_in_expr(
10145 &self,
10146 e: &mut Expr,
10147 row: &Row,
10148 ctx: &EvalContext<'_>,
10149 cancel: CancelToken<'_>,
10150 mut memo: Option<&mut memoize::MemoizeCache>,
10151 ) -> Result<(), EngineError> {
10152 match e {
10153 Expr::ScalarSubquery(inner) => {
10154 let cache_key = memo.as_ref().map(|_| memoize::CacheKey {
10159 subquery_repr: alloc::format!("{}", **inner),
10160 outer_values: row.values.clone(),
10161 });
10162 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key.as_ref())
10163 && let Some(cached) = cache.get(k)
10164 {
10165 *e = value_to_literal_expr(cached)?;
10166 return Ok(());
10167 }
10168 let mut s = (**inner).clone();
10169 substitute_outer_columns(&mut s, row, ctx);
10170 let r = self.exec_select_cancel(&s, cancel)?;
10171 let QueryResult::Rows { rows, .. } = r else {
10172 return Err(EngineError::Unsupported(
10173 "scalar subquery: inner did not return rows".into(),
10174 ));
10175 };
10176 let value = match rows.as_slice() {
10177 [] => Value::Null,
10178 [r0] => r0.values.first().cloned().unwrap_or(Value::Null),
10179 _ => {
10180 return Err(EngineError::Unsupported(alloc::format!(
10181 "scalar subquery returned {} rows; expected 0 or 1",
10182 rows.len()
10183 )));
10184 }
10185 };
10186 if let (Some(cache), Some(k)) = (memo.as_deref_mut(), cache_key) {
10187 cache.insert(k, value.clone());
10188 }
10189 *e = value_to_literal_expr(value)?;
10190 }
10191 Expr::Exists { subquery, negated } => {
10192 let mut s = (**subquery).clone();
10193 substitute_outer_columns(&mut s, row, ctx);
10194 let r = self.exec_select_cancel(&s, cancel)?;
10195 let exists = matches!(r, QueryResult::Rows { rows, .. } if !rows.is_empty());
10196 let bit = if *negated { !exists } else { exists };
10197 *e = Expr::Literal(Literal::Bool(bit));
10198 }
10199 Expr::InSubquery {
10200 expr: lhs,
10201 subquery,
10202 negated,
10203 } => {
10204 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10205 let lhs_val = eval::eval_expr(lhs, row, ctx).map_err(EngineError::Eval)?;
10206 let mut s = (**subquery).clone();
10207 substitute_outer_columns(&mut s, row, ctx);
10208 let r = self.exec_select_cancel(&s, cancel)?;
10209 let QueryResult::Rows { columns, rows, .. } = r else {
10210 return Err(EngineError::Unsupported(
10211 "IN-subquery: inner did not return rows".into(),
10212 ));
10213 };
10214 if columns.len() != 1 {
10215 return Err(EngineError::Unsupported(alloc::format!(
10216 "IN-subquery must project exactly one column; got {}",
10217 columns.len()
10218 )));
10219 }
10220 let mut found = false;
10221 let mut any_null = false;
10222 for r0 in rows {
10223 let v = r0.values.into_iter().next().unwrap_or(Value::Null);
10224 if v.is_null() {
10225 any_null = true;
10226 continue;
10227 }
10228 if value_cmp(&v, &lhs_val) == core::cmp::Ordering::Equal {
10229 found = true;
10230 break;
10231 }
10232 }
10233 let bit = if found {
10234 !*negated
10235 } else if any_null {
10236 return Err(EngineError::Unsupported(
10237 "IN-subquery with NULL in result and no match: NULL semantics not yet implemented".into(),
10238 ));
10239 } else {
10240 *negated
10241 };
10242 *e = Expr::Literal(Literal::Bool(bit));
10243 }
10244 Expr::Binary { lhs, rhs, .. } => {
10245 self.resolve_correlated_in_expr(lhs, row, ctx, cancel, memo.as_deref_mut())?;
10246 self.resolve_correlated_in_expr(rhs, row, ctx, cancel, memo.as_deref_mut())?;
10247 }
10248 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10249 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10250 }
10251 Expr::Like { expr, pattern, .. } => {
10252 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10253 self.resolve_correlated_in_expr(pattern, row, ctx, cancel, memo.as_deref_mut())?;
10254 }
10255 Expr::FunctionCall { args, .. } => {
10256 for a in args {
10257 self.resolve_correlated_in_expr(a, row, ctx, cancel, memo.as_deref_mut())?;
10258 }
10259 }
10260 Expr::Extract { source, .. } => {
10261 self.resolve_correlated_in_expr(source, row, ctx, cancel, memo.as_deref_mut())?;
10262 }
10263 Expr::WindowFunction { .. }
10264 | Expr::Literal(_)
10265 | Expr::Placeholder(_)
10266 | Expr::Column(_) => {}
10267 Expr::Array(items) => {
10269 for elem in items {
10270 self.resolve_correlated_in_expr(elem, row, ctx, cancel, memo.as_deref_mut())?;
10271 }
10272 }
10273 Expr::ArraySubscript { target, index } => {
10274 self.resolve_correlated_in_expr(target, row, ctx, cancel, memo.as_deref_mut())?;
10275 self.resolve_correlated_in_expr(index, row, ctx, cancel, memo.as_deref_mut())?;
10276 }
10277 Expr::AnyAll { expr, array, .. } => {
10278 self.resolve_correlated_in_expr(expr, row, ctx, cancel, memo.as_deref_mut())?;
10279 self.resolve_correlated_in_expr(array, row, ctx, cancel, memo.as_deref_mut())?;
10280 }
10281 Expr::Case {
10282 operand,
10283 branches,
10284 else_branch,
10285 } => {
10286 if let Some(o) = operand {
10287 self.resolve_correlated_in_expr(o, row, ctx, cancel, memo.as_deref_mut())?;
10288 }
10289 for (w, t) in branches {
10290 self.resolve_correlated_in_expr(w, row, ctx, cancel, memo.as_deref_mut())?;
10291 self.resolve_correlated_in_expr(t, row, ctx, cancel, memo.as_deref_mut())?;
10292 }
10293 if let Some(e) = else_branch {
10294 self.resolve_correlated_in_expr(e, row, ctx, cancel, memo.as_deref_mut())?;
10295 }
10296 }
10297 }
10298 Ok(())
10299 }
10300
10301 fn subquery_replacement(
10302 &self,
10303 e: &Expr,
10304 cancel: CancelToken<'_>,
10305 ) -> Result<Option<Expr>, EngineError> {
10306 match e {
10307 Expr::ScalarSubquery(inner) => {
10308 let mut s = (**inner).clone();
10309 self.resolve_select_subqueries(&mut s, cancel)?;
10312 let r = match self.exec_bare_select_cancel(&s, cancel) {
10313 Ok(r) => r,
10314 Err(e) if is_correlation_error(&e) => return Ok(None),
10315 Err(e) => return Err(e),
10316 };
10317 let QueryResult::Rows { rows, .. } = r else {
10318 return Err(EngineError::Unsupported(
10319 "scalar subquery: inner statement did not return rows".into(),
10320 ));
10321 };
10322 let value = match rows.as_slice() {
10323 [] => Value::Null,
10324 [row] => row.values.first().cloned().unwrap_or(Value::Null),
10325 _ => {
10326 return Err(EngineError::Unsupported(alloc::format!(
10327 "scalar subquery returned {} rows; expected 0 or 1",
10328 rows.len()
10329 )));
10330 }
10331 };
10332 Ok(Some(value_to_literal_expr(value)?))
10333 }
10334 Expr::Exists { subquery, negated } => {
10335 let mut s = (**subquery).clone();
10336 self.resolve_select_subqueries(&mut s, cancel)?;
10337 let r = match self.exec_bare_select_cancel(&s, cancel) {
10338 Ok(r) => r,
10339 Err(e) if is_correlation_error(&e) => return Ok(None),
10340 Err(e) => return Err(e),
10341 };
10342 let exists = match r {
10343 QueryResult::Rows { rows, .. } => !rows.is_empty(),
10344 QueryResult::CommandOk { .. } => false,
10345 };
10346 let bit = if *negated { !exists } else { exists };
10347 Ok(Some(Expr::Literal(Literal::Bool(bit))))
10348 }
10349 Expr::InSubquery {
10350 expr,
10351 subquery,
10352 negated,
10353 } => {
10354 let mut s = (**subquery).clone();
10355 self.resolve_select_subqueries(&mut s, cancel)?;
10356 let r = match self.exec_bare_select_cancel(&s, cancel) {
10357 Ok(r) => r,
10358 Err(e) if is_correlation_error(&e) => return Ok(None),
10359 Err(e) => return Err(e),
10360 };
10361 let QueryResult::Rows { columns, rows, .. } = r else {
10362 return Err(EngineError::Unsupported(
10363 "IN-subquery: inner statement did not return rows".into(),
10364 ));
10365 };
10366 if columns.len() != 1 {
10367 return Err(EngineError::Unsupported(alloc::format!(
10368 "IN-subquery must project exactly one column; got {}",
10369 columns.len()
10370 )));
10371 }
10372 let mut acc: Option<Expr> = None;
10375 for row in rows {
10376 let v = row.values.into_iter().next().unwrap_or(Value::Null);
10377 let lit = value_to_literal_expr(v)?;
10378 let cmp = Expr::Binary {
10379 lhs: expr.clone(),
10380 op: BinOp::Eq,
10381 rhs: Box::new(lit),
10382 };
10383 acc = Some(match acc {
10384 None => cmp,
10385 Some(prev) => Expr::Binary {
10386 lhs: Box::new(prev),
10387 op: BinOp::Or,
10388 rhs: Box::new(cmp),
10389 },
10390 });
10391 }
10392 let combined = acc.unwrap_or(Expr::Literal(Literal::Bool(false)));
10393 let final_expr = if *negated {
10394 Expr::Unary {
10395 op: UnOp::Not,
10396 expr: Box::new(combined),
10397 }
10398 } else {
10399 combined
10400 };
10401 Ok(Some(final_expr))
10402 }
10403 _ => Ok(None),
10404 }
10405 }
10406}
10407
10408fn select_refers_to(stmt: &SelectStatement, target: &str) -> bool {
10420 if let Some(from) = &stmt.from
10421 && from_refers_to(from, target)
10422 {
10423 return true;
10424 }
10425 for (_, peer) in &stmt.unions {
10426 if select_refers_to(peer, target) {
10427 return true;
10428 }
10429 }
10430 for item in &stmt.items {
10431 if let SelectItem::Expr { expr, .. } = item
10432 && expr_refers_to(expr, target)
10433 {
10434 return true;
10435 }
10436 }
10437 if let Some(w) = &stmt.where_
10438 && expr_refers_to(w, target)
10439 {
10440 return true;
10441 }
10442 false
10443}
10444
10445fn from_refers_to(from: &FromClause, target: &str) -> bool {
10446 if from.primary.name.eq_ignore_ascii_case(target) {
10447 return true;
10448 }
10449 from.joins
10450 .iter()
10451 .any(|j| j.table.name.eq_ignore_ascii_case(target))
10452}
10453
10454fn expr_refers_to(e: &Expr, target: &str) -> bool {
10455 match e {
10456 Expr::ScalarSubquery(s) => select_refers_to(s, target),
10457 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
10458 select_refers_to(subquery, target)
10459 }
10460 Expr::Binary { lhs, rhs, .. } => expr_refers_to(lhs, target) || expr_refers_to(rhs, target),
10461 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
10462 expr_refers_to(expr, target)
10463 }
10464 Expr::Like { expr, pattern, .. } => {
10465 expr_refers_to(expr, target) || expr_refers_to(pattern, target)
10466 }
10467 Expr::FunctionCall { args, .. } => args.iter().any(|a| expr_refers_to(a, target)),
10468 Expr::Extract { source, .. } => expr_refers_to(source, target),
10469 Expr::WindowFunction {
10470 args,
10471 partition_by,
10472 order_by,
10473 ..
10474 } => {
10475 args.iter().any(|a| expr_refers_to(a, target))
10476 || partition_by.iter().any(|p| expr_refers_to(p, target))
10477 || order_by.iter().any(|(o, _)| expr_refers_to(o, target))
10478 }
10479 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
10480 Expr::Array(items) => items.iter().any(|e| expr_refers_to(e, target)),
10481 Expr::ArraySubscript { target: t, index } => {
10482 expr_refers_to(t, target) || expr_refers_to(index, target)
10483 }
10484 Expr::AnyAll { expr, array, .. } => {
10485 expr_refers_to(expr, target) || expr_refers_to(array, target)
10486 }
10487 Expr::Case {
10488 operand,
10489 branches,
10490 else_branch,
10491 } => {
10492 operand
10493 .as_deref()
10494 .is_some_and(|o| expr_refers_to(o, target))
10495 || branches
10496 .iter()
10497 .any(|(w, t)| expr_refers_to(w, target) || expr_refers_to(t, target))
10498 || else_branch
10499 .as_deref()
10500 .is_some_and(|e| expr_refers_to(e, target))
10501 }
10502 }
10503}
10504
10505fn pg_data_type_text(ty: DataType) -> alloc::string::String {
10516 let s = match ty {
10517 DataType::Int => "integer",
10518 DataType::BigInt => "bigint",
10519 DataType::SmallInt => "smallint",
10520 DataType::Float => "double precision",
10521 DataType::Bool => "boolean",
10522 DataType::Text => "text",
10523 DataType::Varchar(_) => "character varying",
10524 DataType::Date => "date",
10525 DataType::Timestamp => "timestamp without time zone",
10526 DataType::Timestamptz => "timestamp with time zone",
10527 DataType::Json => "jsonb",
10528 DataType::Bytes => "bytea",
10529 DataType::TextArray | DataType::IntArray | DataType::BigIntArray => "ARRAY",
10530 DataType::TsVector => "tsvector",
10531 DataType::TsQuery => "tsquery",
10532 DataType::Vector { .. } => "USER-DEFINED",
10533 _ => "USER-DEFINED",
10536 };
10537 alloc::string::String::from(s)
10538}
10539
10540fn synth_information_schema_columns(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10547 let schema = alloc::vec![
10548 ColumnSchema::new("table_catalog", DataType::Text, false),
10549 ColumnSchema::new("table_schema", DataType::Text, false),
10550 ColumnSchema::new("table_name", DataType::Text, false),
10551 ColumnSchema::new("column_name", DataType::Text, false),
10552 ColumnSchema::new("ordinal_position", DataType::Int, false),
10553 ColumnSchema::new("is_nullable", DataType::Text, false),
10554 ColumnSchema::new("data_type", DataType::Text, false),
10555 ];
10556 let mut rows: Vec<Row> = Vec::new();
10557 for tname in cat.table_names() {
10558 let Some(t) = cat.get(&tname) else { continue };
10559 for (i, col) in t.schema().columns.iter().enumerate() {
10560 #[allow(clippy::cast_possible_wrap)]
10561 let ordinal = (i + 1) as i32;
10562 rows.push(Row::new(alloc::vec![
10563 Value::Text("spg".into()),
10564 Value::Text("public".into()),
10565 Value::Text(tname.clone()),
10566 Value::Text(col.name.clone()),
10567 Value::Int(ordinal),
10568 Value::Text(if col.nullable {
10569 "YES".into()
10570 } else {
10571 "NO".into()
10572 }),
10573 Value::Text(pg_data_type_text(col.ty)),
10574 ]));
10575 }
10576 }
10577 (schema, rows)
10578}
10579
10580fn synth_information_schema_tables(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10582 let schema = alloc::vec![
10583 ColumnSchema::new("table_catalog", DataType::Text, false),
10584 ColumnSchema::new("table_schema", DataType::Text, false),
10585 ColumnSchema::new("table_name", DataType::Text, false),
10586 ColumnSchema::new("table_type", DataType::Text, false),
10587 ];
10588 let mut rows: Vec<Row> = Vec::new();
10589 for tname in cat.table_names() {
10590 rows.push(Row::new(alloc::vec![
10591 Value::Text("spg".into()),
10592 Value::Text("public".into()),
10593 Value::Text(tname.clone()),
10594 Value::Text("BASE TABLE".into()),
10595 ]));
10596 }
10597 (schema, rows)
10598}
10599
10600fn synth_pg_class(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10604 let schema = alloc::vec![
10605 ColumnSchema::new("relname", DataType::Text, false),
10606 ColumnSchema::new("relkind", DataType::Text, false),
10607 ColumnSchema::new("relnamespace", DataType::BigInt, false),
10608 ];
10609 let mut rows: Vec<Row> = Vec::new();
10610 for tname in cat.table_names() {
10611 rows.push(Row::new(alloc::vec![
10612 Value::Text(tname.clone()),
10613 Value::Text("r".into()),
10614 Value::BigInt(2200), ]));
10616 }
10617 (schema, rows)
10618}
10619
10620fn synth_pg_attribute(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10624 let schema = alloc::vec![
10625 ColumnSchema::new("attrelid", DataType::Text, false),
10626 ColumnSchema::new("attname", DataType::Text, false),
10627 ColumnSchema::new("attnum", DataType::Int, false),
10628 ColumnSchema::new("atttypid", DataType::Text, false),
10629 ColumnSchema::new("attnotnull", DataType::Bool, false),
10630 ];
10631 let mut rows: Vec<Row> = Vec::new();
10632 for tname in cat.table_names() {
10633 let Some(t) = cat.get(&tname) else { continue };
10634 for (i, col) in t.schema().columns.iter().enumerate() {
10635 #[allow(clippy::cast_possible_wrap)]
10636 let ordinal = (i + 1) as i32;
10637 rows.push(Row::new(alloc::vec![
10638 Value::Text(tname.clone()),
10639 Value::Text(col.name.clone()),
10640 Value::Int(ordinal),
10641 Value::Text(pg_data_type_text(col.ty)),
10642 Value::Bool(!col.nullable),
10643 ]));
10644 }
10645 }
10646 (schema, rows)
10647}
10648
10649fn synth_pg_type(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10666 let schema = alloc::vec![
10667 ColumnSchema::new("oid", DataType::BigInt, false),
10668 ColumnSchema::new("typname", DataType::Text, false),
10669 ColumnSchema::new("typlen", DataType::SmallInt, false),
10670 ColumnSchema::new("typtype", DataType::Text, false),
10671 ColumnSchema::new("typcategory", DataType::Text, false),
10672 ColumnSchema::new("typelem", DataType::BigInt, false),
10673 ColumnSchema::new("typarray", DataType::BigInt, false),
10674 ColumnSchema::new("typnamespace", DataType::BigInt, false),
10675 ];
10676 let scalars: &[(i64, &str, i16, &str, &str, i64, i64)] = &[
10679 (16, "bool", 1, "b", "B", 0, 1000),
10681 (17, "bytea", -1, "b", "U", 0, 1001),
10682 (18, "char", 1, "b", "S", 0, 1002),
10683 (19, "name", 64, "b", "S", 0, 1003),
10684 (20, "int8", 8, "b", "N", 0, 1016),
10685 (21, "int2", 2, "b", "N", 0, 1005),
10686 (23, "int4", 4, "b", "N", 0, 1007),
10687 (24, "regproc", 4, "b", "N", 0, 1008),
10688 (25, "text", -1, "b", "S", 0, 1009),
10689 (26, "oid", 4, "b", "N", 0, 1028),
10690 (114, "json", -1, "b", "U", 0, 199),
10691 (142, "xml", -1, "b", "U", 0, 143),
10692 (700, "float4", 4, "b", "N", 0, 1021),
10693 (701, "float8", 8, "b", "N", 0, 1022),
10694 (650, "cidr", -1, "b", "I", 0, 651),
10695 (869, "inet", -1, "b", "I", 0, 1041),
10696 (829, "macaddr", 6, "b", "U", 0, 1040),
10697 (1042, "bpchar", -1, "b", "S", 0, 1014),
10698 (1043, "varchar", -1, "b", "S", 0, 1015),
10699 (1082, "date", 4, "b", "D", 0, 1182),
10700 (1083, "time", 8, "b", "D", 0, 1183),
10701 (1114, "timestamp", 8, "b", "D", 0, 1115),
10702 (1184, "timestamptz", 8, "b", "D", 0, 1185),
10703 (1186, "interval", 16, "b", "T", 0, 1187),
10704 (1266, "timetz", 12, "b", "D", 0, 1270),
10705 (1700, "numeric", -1, "b", "N", 0, 1231),
10706 (790, "money", 8, "b", "N", 0, 791),
10707 (2950, "uuid", 16, "b", "U", 0, 2951),
10708 (3802, "jsonb", -1, "b", "U", 0, 3807),
10709 (3614, "tsvector", -1, "b", "U", 0, 3643),
10710 (3615, "tsquery", -1, "b", "U", 0, 3645),
10711 (3908, "tstzrange", -1, "r", "R", 0, 3909),
10713 (3910, "tsrange", -1, "r", "R", 0, 3911),
10714 (3904, "int4range", -1, "r", "R", 0, 3905),
10715 (3926, "int8range", -1, "r", "R", 0, 3927),
10716 (3906, "numrange", -1, "r", "R", 0, 3907),
10717 (3912, "daterange", -1, "r", "R", 0, 3913),
10718 ];
10719 let arrays: &[(i64, &str, i64)] = &[
10722 (1000, "_bool", 16),
10723 (1001, "_bytea", 17),
10724 (1002, "_char", 18),
10725 (1003, "_name", 19),
10726 (1016, "_int8", 20),
10727 (1005, "_int2", 21),
10728 (1007, "_int4", 23),
10729 (1008, "_regproc", 24),
10730 (1009, "_text", 25),
10731 (1028, "_oid", 26),
10732 (199, "_json", 114),
10733 (143, "_xml", 142),
10734 (1021, "_float4", 700),
10735 (1022, "_float8", 701),
10736 (651, "_cidr", 650),
10737 (1041, "_inet", 869),
10738 (1040, "_macaddr", 829),
10739 (1014, "_bpchar", 1042),
10740 (1015, "_varchar", 1043),
10741 (1182, "_date", 1082),
10742 (1183, "_time", 1083),
10743 (1115, "_timestamp", 1114),
10744 (1185, "_timestamptz", 1184),
10745 (1187, "_interval", 1186),
10746 (1270, "_timetz", 1266),
10747 (1231, "_numeric", 1700),
10748 (791, "_money", 790),
10749 (2951, "_uuid", 2950),
10750 (3807, "_jsonb", 3802),
10751 (3643, "_tsvector", 3614),
10752 (3645, "_tsquery", 3615),
10753 ];
10754 let mut rows: Vec<Row> = Vec::with_capacity(scalars.len() + arrays.len());
10755 for &(oid, name, len, ty, cat, elem, arr) in scalars {
10756 rows.push(Row::new(alloc::vec![
10757 Value::BigInt(oid),
10758 Value::Text(name.into()),
10759 Value::SmallInt(len),
10760 Value::Text(ty.into()),
10761 Value::Text(cat.into()),
10762 Value::BigInt(elem),
10763 Value::BigInt(arr),
10764 Value::BigInt(2200),
10765 ]));
10766 }
10767 for &(oid, name, elem) in arrays {
10768 rows.push(Row::new(alloc::vec![
10769 Value::BigInt(oid),
10770 Value::Text(name.into()),
10771 Value::SmallInt(-1),
10772 Value::Text("b".into()),
10773 Value::Text("A".into()),
10774 Value::BigInt(elem),
10775 Value::BigInt(0),
10776 Value::BigInt(2200),
10777 ]));
10778 }
10779 (schema, rows)
10780}
10781
10782fn synth_pg_proc(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10796 let schema = alloc::vec![
10797 ColumnSchema::new("oid", DataType::BigInt, false),
10798 ColumnSchema::new("proname", DataType::Text, false),
10799 ColumnSchema::new("pronamespace", DataType::BigInt, false),
10800 ColumnSchema::new("prokind", DataType::Text, false),
10801 ColumnSchema::new("pronargs", DataType::Int, false),
10802 ColumnSchema::new("prorettype", DataType::BigInt, false),
10803 ];
10804 let funcs: &[(i64, &str, &str, i32, i64)] = &[
10807 (1318, "length", "f", 1, 23),
10809 (871, "upper", "f", 1, 25),
10810 (870, "lower", "f", 1, 25),
10811 (936, "substring", "f", 3, 25),
10812 (937, "substring", "f", 2, 25),
10813 (3055, "btrim", "f", 1, 25),
10814 (885, "btrim", "f", 2, 25),
10815 (3056, "ltrim", "f", 1, 25),
10816 (875, "ltrim", "f", 2, 25),
10817 (3057, "rtrim", "f", 1, 25),
10818 (876, "rtrim", "f", 2, 25),
10819 (1397, "abs", "f", 1, 23),
10820 (1396, "abs", "f", 1, 20),
10821 (1606, "round", "f", 1, 1700),
10822 (1707, "round", "f", 2, 1700),
10823 (2308, "ceil", "f", 1, 701),
10824 (2309, "ceiling", "f", 1, 701),
10825 (2310, "floor", "f", 1, 701),
10826 (1376, "sqrt", "f", 1, 701),
10827 (1369, "ln", "f", 1, 701),
10828 (1373, "exp", "f", 1, 701),
10829 (1368, "power", "f", 2, 701),
10830 (2228, "random", "f", 0, 701),
10831 (1299, "now", "f", 0, 1184),
10833 (1274, "current_timestamp", "f", 0, 1184),
10834 (1140, "current_date", "f", 0, 1082),
10835 (2050, "current_time", "f", 0, 1083),
10836 (1158, "date_trunc", "f", 2, 1184),
10837 (1171, "date_part", "f", 2, 701),
10838 (1172, "age", "f", 1, 1186),
10839 (936, "to_char", "f", 2, 25),
10840 (861, "current_database", "f", 0, 19),
10842 (745, "current_user", "f", 0, 19),
10843 (745, "session_user", "f", 0, 19),
10844 (1402, "current_schema", "f", 0, 19),
10845 (3058, "concat", "f", -1, 25),
10847 (3059, "concat_ws", "f", -1, 25),
10848 (3539, "format", "f", -1, 25),
10849 (2877, "pg_typeof", "f", 1, 2206),
10851 (3198, "json_build_object", "f", -1, 114),
10853 (3199, "jsonb_build_object", "f", -1, 3802),
10854 (3271, "json_build_array", "f", -1, 114),
10855 (3272, "jsonb_build_array", "f", -1, 3802),
10856 (3253, "gen_random_uuid", "f", 0, 2950),
10858 (3252, "uuid_generate_v4", "f", 0, 2950),
10859 (2147, "count", "a", 0, 20),
10861 (2803, "count", "a", -1, 20),
10862 (2116, "max", "a", 1, 23),
10863 (2132, "min", "a", 1, 23),
10864 (2108, "sum", "a", 1, 20),
10865 (2100, "avg", "a", 1, 1700),
10866 (2517, "string_agg", "a", 2, 25),
10867 (2747, "array_agg", "a", 1, 1009),
10868 (2517, "bool_and", "a", 1, 16),
10869 (2518, "bool_or", "a", 1, 16),
10870 (2519, "every", "a", 1, 16),
10871 (3100, "row_number", "w", 0, 20),
10873 (3101, "rank", "w", 0, 20),
10874 (3102, "dense_rank", "w", 0, 20),
10875 (3103, "percent_rank", "w", 0, 701),
10876 (3104, "cume_dist", "w", 0, 701),
10877 (3105, "lag", "w", -1, 2283),
10878 (3106, "lead", "w", -1, 2283),
10879 (3107, "first_value", "w", 1, 2283),
10880 (3108, "last_value", "w", 1, 2283),
10881 (3109, "nth_value", "w", 2, 2283),
10882 ];
10883 let mut rows: Vec<Row> = Vec::with_capacity(funcs.len());
10884 for &(oid, name, kind, nargs, rettype) in funcs {
10885 rows.push(Row::new(alloc::vec![
10886 Value::BigInt(oid),
10887 Value::Text(name.into()),
10888 Value::BigInt(11),
10889 Value::Text(kind.into()),
10890 Value::Int(nargs),
10891 Value::BigInt(rettype),
10892 ]));
10893 }
10894 (schema, rows)
10895}
10896
10897fn synth_mysql_user(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
10903 let schema = alloc::vec![
10904 ColumnSchema::new("user", DataType::Text, false),
10905 ColumnSchema::new("host", DataType::Text, false),
10906 ColumnSchema::new("select_priv", DataType::Text, false),
10907 ];
10908 let mut rows: Vec<Row> = Vec::new();
10909 rows.push(Row::new(alloc::vec![
10910 Value::Text("root".into()),
10911 Value::Text("localhost".into()),
10912 Value::Text("Y".into()),
10913 ]));
10914 for (name, _) in engine.users.iter() {
10915 if name != "root" {
10916 rows.push(Row::new(alloc::vec![
10917 Value::Text(name.to_string()),
10918 Value::Text("%".into()),
10919 Value::Text("Y".into()),
10920 ]));
10921 }
10922 }
10923 (schema, rows)
10924}
10925
10926fn synth_mysql_db() -> (Vec<ColumnSchema>, Vec<Row>) {
10931 let schema = alloc::vec![
10932 ColumnSchema::new("host", DataType::Text, false),
10933 ColumnSchema::new("db", DataType::Text, false),
10934 ColumnSchema::new("user", DataType::Text, false),
10935 ColumnSchema::new("select_priv", DataType::Text, false),
10936 ];
10937 let rows = alloc::vec![Row::new(alloc::vec![
10938 Value::Text("localhost".into()),
10939 Value::Text("postgres".into()),
10940 Value::Text("root".into()),
10941 Value::Text("Y".into()),
10942 ])];
10943 (schema, rows)
10944}
10945
10946fn synth_info_key_column_usage(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
10959 let schema = alloc::vec![
10960 ColumnSchema::new("constraint_name", DataType::Text, false),
10961 ColumnSchema::new("table_name", DataType::Text, false),
10962 ColumnSchema::new("column_name", DataType::Text, false),
10963 ColumnSchema::new("ordinal_position", DataType::Int, false),
10964 ColumnSchema::new("referenced_table_name", DataType::Text, false),
10965 ColumnSchema::new("referenced_column_name", DataType::Text, false),
10966 ];
10967 let mut rows: Vec<Row> = Vec::new();
10968 for tname in cat.table_names() {
10969 let Some(t) = cat.get(&tname) else { continue };
10970 let cols = &t.schema().columns;
10971 let col_name_at = |pos: usize| -> String {
10972 cols.get(pos)
10973 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
10974 };
10975 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
10977 let conname = fk
10978 .name
10979 .clone()
10980 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
10981 for (i, (&local, &parent)) in fk
10982 .local_columns
10983 .iter()
10984 .zip(fk.parent_columns.iter())
10985 .enumerate()
10986 {
10987 let parent_name = cat
10988 .get(&fk.parent_table)
10989 .and_then(|pt| pt.schema().columns.get(parent).map(|c| c.name.clone()))
10990 .unwrap_or_else(|| alloc::format!("col{parent}"));
10991 #[allow(clippy::cast_possible_wrap)]
10992 let ordinal = (i + 1) as i32;
10993 rows.push(Row::new(alloc::vec![
10994 Value::Text(conname.clone()),
10995 Value::Text(tname.clone()),
10996 Value::Text(col_name_at(local)),
10997 Value::Int(ordinal),
10998 Value::Text(fk.parent_table.clone()),
10999 Value::Text(parent_name),
11000 ]));
11001 }
11002 }
11003 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11005 let conname = if uc.is_primary_key {
11006 alloc::format!("{}_pkey", tname)
11007 } else {
11008 alloc::format!("{}_uniq{ci}", tname)
11009 };
11010 for (i, &local) in uc.columns.iter().enumerate() {
11011 #[allow(clippy::cast_possible_wrap)]
11012 let ordinal = (i + 1) as i32;
11013 rows.push(Row::new(alloc::vec![
11014 Value::Text(conname.clone()),
11015 Value::Text(tname.clone()),
11016 Value::Text(col_name_at(local)),
11017 Value::Int(ordinal),
11018 Value::Text(String::new()),
11019 Value::Text(String::new()),
11020 ]));
11021 }
11022 }
11023 }
11024 (schema, rows)
11025}
11026
11027fn synth_info_referential_constraints(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11030 let schema = alloc::vec![
11031 ColumnSchema::new("constraint_name", DataType::Text, false),
11032 ColumnSchema::new("table_name", DataType::Text, false),
11033 ColumnSchema::new("referenced_table_name", DataType::Text, false),
11034 ColumnSchema::new("update_rule", DataType::Text, false),
11035 ColumnSchema::new("delete_rule", DataType::Text, false),
11036 ];
11037 fn rule_name(a: spg_storage::FkAction) -> &'static str {
11038 match a {
11039 spg_storage::FkAction::Cascade => "CASCADE",
11040 spg_storage::FkAction::SetNull => "SET NULL",
11041 spg_storage::FkAction::SetDefault => "SET DEFAULT",
11042 spg_storage::FkAction::Restrict => "RESTRICT",
11043 spg_storage::FkAction::NoAction => "NO ACTION",
11044 }
11045 }
11046 let mut rows: Vec<Row> = Vec::new();
11047 for tname in cat.table_names() {
11048 let Some(t) = cat.get(&tname) else { continue };
11049 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11050 let conname = fk
11051 .name
11052 .clone()
11053 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11054 rows.push(Row::new(alloc::vec![
11055 Value::Text(conname),
11056 Value::Text(tname.clone()),
11057 Value::Text(fk.parent_table.clone()),
11058 Value::Text(rule_name(fk.on_update).into()),
11059 Value::Text(rule_name(fk.on_delete).into()),
11060 ]));
11061 }
11062 }
11063 (schema, rows)
11064}
11065
11066fn synth_info_statistics(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11070 let schema = alloc::vec![
11071 ColumnSchema::new("table_name", DataType::Text, false),
11072 ColumnSchema::new("index_name", DataType::Text, false),
11073 ColumnSchema::new("column_name", DataType::Text, false),
11074 ColumnSchema::new("seq_in_index", DataType::Int, false),
11075 ColumnSchema::new("non_unique", DataType::Int, false),
11076 ColumnSchema::new("index_type", DataType::Text, false),
11077 ];
11078 let mut rows: Vec<Row> = Vec::new();
11079 for tname in cat.table_names() {
11080 let Some(t) = cat.get(&tname) else { continue };
11081 for idx in t.indices() {
11082 let col = t
11083 .schema()
11084 .columns
11085 .get(idx.column_position)
11086 .map_or("?".into(), |c| c.name.clone());
11087 rows.push(Row::new(alloc::vec![
11088 Value::Text(tname.clone()),
11089 Value::Text(idx.name.clone()),
11090 Value::Text(col),
11091 Value::Int(1),
11092 Value::Int(i32::from(!idx.is_unique)),
11093 Value::Text("BTREE".into()),
11094 ]));
11095 }
11096 }
11097 (schema, rows)
11098}
11099
11100fn synth_info_routines() -> (Vec<ColumnSchema>, Vec<Row>) {
11104 let schema = alloc::vec![
11105 ColumnSchema::new("routine_name", DataType::Text, false),
11106 ColumnSchema::new("routine_type", DataType::Text, false),
11107 ColumnSchema::new("data_type", DataType::Text, false),
11108 ];
11109 (schema, Vec::new())
11110}
11111
11112fn synth_pg_constraint(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11127 let schema = alloc::vec![
11128 ColumnSchema::new("conname", DataType::Text, false),
11129 ColumnSchema::new("contype", DataType::Text, false),
11130 ColumnSchema::new("conrelid", DataType::Text, false),
11131 ColumnSchema::new("confrelid", DataType::Text, false),
11132 ColumnSchema::new("conkey", DataType::Text, false),
11133 ColumnSchema::new("confkey", DataType::Text, false),
11134 ];
11135 let mut rows: Vec<Row> = Vec::new();
11136 for tname in cat.table_names() {
11137 let Some(t) = cat.get(&tname) else { continue };
11138 let cols = &t.schema().columns;
11139 let col_name_at = |pos: usize| -> String {
11140 cols.get(pos)
11141 .map_or_else(|| alloc::format!("col{pos}"), |c| c.name.clone())
11142 };
11143 for (ci, uc) in t.schema().uniqueness_constraints.iter().enumerate() {
11145 let kind = if uc.is_primary_key { "p" } else { "u" };
11146 let conname = if uc.is_primary_key {
11147 alloc::format!("{}_pkey", tname)
11148 } else {
11149 alloc::format!("{}_uniq{ci}", tname)
11150 };
11151 let conkey: Vec<String> = uc.columns.iter().map(|&p| col_name_at(p)).collect();
11152 rows.push(Row::new(alloc::vec![
11153 Value::Text(conname),
11154 Value::Text(kind.into()),
11155 Value::Text(tname.clone()),
11156 Value::Text(String::new()),
11157 Value::Text(conkey.join(",")),
11158 Value::Text(String::new()),
11159 ]));
11160 }
11161 for idx in t.indices() {
11166 if !idx.is_unique {
11167 continue;
11168 }
11169 let is_primary = idx.name.ends_with("_pkey");
11170 let conname = idx.name.clone();
11171 let kind = if is_primary { "p" } else { "u" };
11172 let col_name = col_name_at(idx.column_position);
11173 let already = t
11176 .schema()
11177 .uniqueness_constraints
11178 .iter()
11179 .any(|uc| uc.columns.len() == 1 && uc.columns[0] == idx.column_position);
11180 if already {
11181 continue;
11182 }
11183 rows.push(Row::new(alloc::vec![
11184 Value::Text(conname),
11185 Value::Text(kind.into()),
11186 Value::Text(tname.clone()),
11187 Value::Text(String::new()),
11188 Value::Text(col_name),
11189 Value::Text(String::new()),
11190 ]));
11191 }
11192 for (fi, fk) in t.schema().foreign_keys.iter().enumerate() {
11194 let conname = fk
11195 .name
11196 .clone()
11197 .unwrap_or_else(|| alloc::format!("{}_fk{fi}", tname));
11198 let conkey: Vec<String> = fk.local_columns.iter().map(|&p| col_name_at(p)).collect();
11199 let confkey: Vec<String> = if let Some(parent) = cat.get(&fk.parent_table) {
11202 fk.parent_columns
11203 .iter()
11204 .map(|&p| {
11205 parent
11206 .schema()
11207 .columns
11208 .get(p)
11209 .map_or_else(|| alloc::format!("col{p}"), |c| c.name.clone())
11210 })
11211 .collect()
11212 } else {
11213 fk.parent_columns
11214 .iter()
11215 .map(|p| alloc::format!("col{p}"))
11216 .collect()
11217 };
11218 rows.push(Row::new(alloc::vec![
11219 Value::Text(conname),
11220 Value::Text("f".into()),
11221 Value::Text(tname.clone()),
11222 Value::Text(fk.parent_table.clone()),
11223 Value::Text(conkey.join(",")),
11224 Value::Text(confkey.join(",")),
11225 ]));
11226 }
11227 }
11228 (schema, rows)
11229}
11230
11231fn synth_pg_database(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11236 let schema = alloc::vec![
11237 ColumnSchema::new("oid", DataType::BigInt, false),
11238 ColumnSchema::new("datname", DataType::Text, false),
11239 ColumnSchema::new("datdba", DataType::BigInt, false),
11240 ColumnSchema::new("encoding", DataType::Int, false),
11241 ColumnSchema::new("datcollate", DataType::Text, false),
11242 ];
11243 let rows = alloc::vec![Row::new(alloc::vec![
11244 Value::BigInt(16384),
11245 Value::Text("postgres".into()),
11246 Value::BigInt(10),
11247 Value::Int(6), Value::Text("en_US.UTF-8".into()),
11249 ])];
11250 (schema, rows)
11251}
11252
11253fn synth_pg_roles(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11258 let schema = alloc::vec![
11259 ColumnSchema::new("oid", DataType::BigInt, false),
11260 ColumnSchema::new("rolname", DataType::Text, false),
11261 ColumnSchema::new("rolsuper", DataType::Bool, false),
11262 ColumnSchema::new("rolinherit", DataType::Bool, false),
11263 ColumnSchema::new("rolcanlogin", DataType::Bool, false),
11264 ];
11265 let mut rows: Vec<Row> = Vec::new();
11266 let oid: i64 = 10;
11267 for (i, (name, _)) in engine.users.iter().enumerate() {
11268 rows.push(Row::new(alloc::vec![
11269 Value::BigInt(oid + (i as i64) + 1),
11270 Value::Text(name.to_string()),
11271 Value::Bool(false),
11272 Value::Bool(true),
11273 Value::Bool(true),
11274 ]));
11275 }
11276 if !rows
11279 .iter()
11280 .any(|r| matches!(&r.values[1], Value::Text(s) if s == "postgres"))
11281 {
11282 rows.insert(
11283 0,
11284 Row::new(alloc::vec![
11285 Value::BigInt(10),
11286 Value::Text("postgres".into()),
11287 Value::Bool(true),
11288 Value::Bool(true),
11289 Value::Bool(true),
11290 ]),
11291 );
11292 }
11293 (schema, rows)
11294}
11295
11296fn synth_pg_views(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11300 let schema = alloc::vec![
11301 ColumnSchema::new("schemaname", DataType::Text, false),
11302 ColumnSchema::new("viewname", DataType::Text, false),
11303 ColumnSchema::new("definition", DataType::Text, false),
11304 ];
11305 let mut rows: Vec<Row> = Vec::new();
11306 for (name, def) in cat.views() {
11307 rows.push(Row::new(alloc::vec![
11308 Value::Text("public".into()),
11309 Value::Text(name.clone()),
11310 Value::Text(def.body.clone()),
11311 ]));
11312 }
11313 (schema, rows)
11314}
11315
11316fn synth_pg_settings(engine: &Engine) -> (Vec<ColumnSchema>, Vec<Row>) {
11322 let schema = alloc::vec![
11323 ColumnSchema::new("name", DataType::Text, false),
11324 ColumnSchema::new("setting", DataType::Text, false),
11325 ColumnSchema::new("category", DataType::Text, false),
11326 ];
11327 let mut rows: Vec<Row> = Vec::new();
11328 let defaults: &[(&str, &str, &str)] = &[
11330 ("server_version", "16.0 (spg)", "Preset Options"),
11331 ("server_encoding", "UTF8", "Client Connection Defaults"),
11332 ("client_encoding", "UTF8", "Client Connection Defaults"),
11333 ("DateStyle", "ISO, MDY", "Client Connection Defaults"),
11334 ("TimeZone", "UTC", "Client Connection Defaults"),
11335 ("standard_conforming_strings", "on", "Compatibility"),
11336 ("integer_datetimes", "on", "Compatibility"),
11337 ("max_connections", "100", "Connections and Authentication"),
11338 ];
11339 for &(name, val, cat) in defaults {
11340 rows.push(Row::new(alloc::vec![
11341 Value::Text(name.into()),
11342 Value::Text(val.into()),
11343 Value::Text(cat.into()),
11344 ]));
11345 }
11346 for (k, v) in &engine.session_params {
11348 if !defaults
11349 .iter()
11350 .any(|(n, _, _)| (*n).eq_ignore_ascii_case(k))
11351 {
11352 rows.push(Row::new(alloc::vec![
11353 Value::Text(k.clone()),
11354 Value::Text(v.clone()),
11355 Value::Text("Session".into()),
11356 ]));
11357 }
11358 }
11359 (schema, rows)
11360}
11361
11362fn synth_pg_indexes(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11373 let schema = alloc::vec![
11374 ColumnSchema::new("schemaname", DataType::Text, false),
11375 ColumnSchema::new("tablename", DataType::Text, false),
11376 ColumnSchema::new("indexname", DataType::Text, false),
11377 ColumnSchema::new("indexdef", DataType::Text, false),
11378 ];
11379 let mut rows: Vec<Row> = Vec::new();
11380 for tname in cat.table_names() {
11381 let Some(t) = cat.get(&tname) else { continue };
11382 for idx in t.indices() {
11383 let col_name = t
11384 .schema()
11385 .columns
11386 .get(idx.column_position)
11387 .map_or("?".into(), |c| c.name.clone());
11388 let unique_kw = if idx.is_unique { "UNIQUE " } else { "" };
11389 let indexdef = alloc::format!(
11390 "CREATE {unique_kw}INDEX {} ON public.{} ({})",
11391 idx.name,
11392 tname,
11393 col_name
11394 );
11395 rows.push(Row::new(alloc::vec![
11396 Value::Text("public".into()),
11397 Value::Text(tname.clone()),
11398 Value::Text(idx.name.clone()),
11399 Value::Text(indexdef),
11400 ]));
11401 }
11402 }
11403 (schema, rows)
11404}
11405
11406fn synth_pg_index_raw(cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11418 let schema = alloc::vec![
11419 ColumnSchema::new("indexrelid", DataType::BigInt, false),
11420 ColumnSchema::new("indrelid", DataType::BigInt, false),
11421 ColumnSchema::new("indnatts", DataType::Int, false),
11422 ColumnSchema::new("indisunique", DataType::Bool, false),
11423 ColumnSchema::new("indisprimary", DataType::Bool, false),
11424 ];
11425 let mut rows: Vec<Row> = Vec::new();
11426 let mut idx_oid: i64 = 100_000;
11427 for (table_idx, tname) in cat.table_names().iter().enumerate() {
11428 let Some(t) = cat.get(tname) else { continue };
11429 for idx in t.indices() {
11430 idx_oid += 1;
11431 #[allow(clippy::cast_possible_wrap)]
11432 let nattrs = (1 + idx.extra_column_positions.len()) as i32;
11433 let is_primary = idx.name.ends_with("_pkey");
11436 rows.push(Row::new(alloc::vec![
11437 Value::BigInt(idx_oid),
11438 Value::BigInt((table_idx + 1) as i64),
11439 Value::Int(nattrs),
11440 Value::Bool(idx.is_unique),
11441 Value::Bool(is_primary),
11442 ]));
11443 }
11444 }
11445 (schema, rows)
11446}
11447
11448fn synth_pg_namespace(_cat: &Catalog) -> (Vec<ColumnSchema>, Vec<Row>) {
11453 let schema = alloc::vec![
11454 ColumnSchema::new("oid", DataType::BigInt, false),
11455 ColumnSchema::new("nspname", DataType::Text, false),
11456 ColumnSchema::new("nspowner", DataType::BigInt, false),
11457 ];
11458 let rows = alloc::vec![
11459 Row::new(alloc::vec![
11460 Value::BigInt(11),
11461 Value::Text("pg_catalog".into()),
11462 Value::BigInt(10),
11463 ]),
11464 Row::new(alloc::vec![
11465 Value::BigInt(2200),
11466 Value::Text("public".into()),
11467 Value::BigInt(10),
11468 ]),
11469 Row::new(alloc::vec![
11470 Value::BigInt(13000),
11471 Value::Text("information_schema".into()),
11472 Value::BigInt(10),
11473 ]),
11474 ];
11475 (schema, rows)
11476}
11477
11478fn materialise_meta_view(
11481 catalog: &mut Catalog,
11482 name: &str,
11483 columns: Vec<ColumnSchema>,
11484 rows: Vec<Row>,
11485) -> Result<(), EngineError> {
11486 let schema = TableSchema::new(name.to_string(), columns);
11487 catalog.create_table(schema).map_err(EngineError::Storage)?;
11488 let table = catalog
11489 .get_mut(name)
11490 .expect("just-created meta view must exist");
11491 for row in rows {
11492 table.insert(row).map_err(EngineError::Storage)?;
11493 }
11494 Ok(())
11495}
11496
11497fn collect_view_refs(
11510 tref: &spg_sql::ast::TableRef,
11511 cat: &spg_storage::Catalog,
11512 into: &mut Vec<String>,
11513) {
11514 if cat.views().contains_key(&tref.name)
11515 && cat.get(&tref.name).is_none()
11516 && !into.iter().any(|n| n == &tref.name)
11517 {
11518 into.push(tref.name.clone());
11519 }
11520}
11521
11522fn select_references_meta_view(stmt: &SelectStatement) -> bool {
11523 fn is_meta(name: &str) -> bool {
11524 name.starts_with("__spg_info_")
11525 || name.starts_with("__spg_pg_")
11526 || name.starts_with("__spg_mysql_")
11527 }
11528 if let Some(from) = &stmt.from {
11529 if is_meta(&from.primary.name) {
11530 return true;
11531 }
11532 for j in &from.joins {
11533 if is_meta(&j.table.name) {
11534 return true;
11535 }
11536 }
11537 }
11538 for cte in &stmt.ctes {
11539 if select_references_meta_view(&cte.body) {
11540 return true;
11541 }
11542 }
11543 false
11544}
11545
11546fn collect_meta_view_names(
11551 stmt: &SelectStatement,
11552 into: &mut alloc::collections::BTreeSet<String>,
11553) {
11554 fn is_meta(name: &str) -> bool {
11555 name.starts_with("__spg_info_")
11556 || name.starts_with("__spg_pg_")
11557 || name.starts_with("__spg_mysql_")
11558 }
11559 if let Some(from) = &stmt.from {
11560 if is_meta(&from.primary.name) {
11561 into.insert(from.primary.name.clone());
11562 }
11563 for j in &from.joins {
11564 if is_meta(&j.table.name) {
11565 into.insert(j.table.name.clone());
11566 }
11567 }
11568 }
11569 for cte in &stmt.ctes {
11570 collect_meta_view_names(&cte.body, into);
11571 }
11572}
11573
11574fn infer_column_types(columns: &[ColumnSchema], rows: &[Row]) -> Vec<ColumnSchema> {
11575 let mut out = columns.to_vec();
11576 for (col_idx, col) in out.iter_mut().enumerate() {
11577 if col.ty != DataType::Text {
11578 continue;
11579 }
11580 let mut inferred: Option<DataType> = None;
11581 let mut all_null = true;
11582 for row in rows {
11583 let Some(v) = row.values.get(col_idx) else {
11584 continue;
11585 };
11586 let ty = match v {
11587 Value::Null => continue,
11588 Value::SmallInt(_) => DataType::SmallInt,
11589 Value::Int(_) => DataType::Int,
11590 Value::BigInt(_) => DataType::BigInt,
11591 Value::Float(_) => DataType::Float,
11592 Value::Bool(_) => DataType::Bool,
11593 Value::Vector(_) => DataType::Vector {
11594 dim: 0,
11595 encoding: VecEncoding::F32,
11596 },
11597 _ => DataType::Text,
11598 };
11599 all_null = false;
11600 inferred = Some(match inferred {
11601 None => ty,
11602 Some(prev) if prev == ty => prev,
11603 Some(_) => DataType::Text,
11604 });
11605 }
11606 if let Some(t) = inferred {
11607 col.ty = t;
11608 col.nullable = true;
11609 } else if all_null {
11610 col.nullable = true;
11611 }
11612 }
11613 out
11614}
11615
11616#[allow(clippy::too_many_lines, clippy::format_push_string)]
11621fn build_index_suggestions(stmt: &SelectStatement, engine: &Engine) -> Vec<String> {
11638 use alloc::collections::BTreeSet;
11639 let mut seen: BTreeSet<(String, String)> = BTreeSet::new();
11640 let mut out: Vec<String> = Vec::new();
11641 let cat = engine.active_catalog();
11642 let Some(from) = &stmt.from else {
11646 return out;
11647 };
11648 let mut tables: Vec<String> = Vec::new();
11649 tables.push(from.primary.name.clone());
11650 for j in &from.joins {
11651 tables.push(j.table.name.clone());
11652 }
11653 let mut col_refs: Vec<spg_sql::ast::ColumnName> = Vec::new();
11656 if let Some(w) = &stmt.where_ {
11657 collect_column_refs(w, &mut col_refs);
11658 }
11659 for j in &from.joins {
11660 if let Some(on) = &j.on {
11661 collect_column_refs(on, &mut col_refs);
11662 }
11663 }
11664 for cn in &col_refs {
11665 let owner: Option<String> = if let Some(q) = &cn.qualifier {
11668 tables.iter().find(|t| t == &q).cloned()
11669 } else {
11670 tables.iter().find_map(|t| {
11671 cat.get(t).and_then(|tbl| {
11672 if tbl.schema().column_position(&cn.name).is_some() {
11673 Some(t.clone())
11674 } else {
11675 None
11676 }
11677 })
11678 })
11679 };
11680 let Some(owner) = owner else {
11681 continue;
11682 };
11683 let Some(tbl) = cat.get(&owner) else {
11684 continue;
11685 };
11686 let Some(col_pos) = tbl.schema().column_position(&cn.name) else {
11687 continue;
11688 };
11689 let already_indexed = tbl.indices().iter().any(|i| {
11692 matches!(i.kind, spg_storage::IndexKind::BTree(_))
11693 && i.column_position == col_pos
11694 && i.expression.is_none()
11695 && i.partial_predicate.is_none()
11696 });
11697 if already_indexed {
11698 continue;
11699 }
11700 if seen.insert((owner.clone(), cn.name.clone())) {
11701 out.push(alloc::format!(
11702 "SUGGEST: CREATE INDEX ix_{}_{} ON {} ({})",
11703 owner,
11704 cn.name,
11705 owner,
11706 cn.name
11707 ));
11708 }
11709 }
11710 out
11711}
11712
11713fn collect_column_refs(expr: &Expr, out: &mut Vec<spg_sql::ast::ColumnName>) {
11716 match expr {
11717 Expr::Column(cn) => out.push(cn.clone()),
11718 Expr::FunctionCall { args, .. } => {
11719 for a in args {
11720 collect_column_refs(a, out);
11721 }
11722 }
11723 Expr::Binary { lhs, rhs, .. } => {
11724 collect_column_refs(lhs, out);
11725 collect_column_refs(rhs, out);
11726 }
11727 Expr::Unary { expr: e, .. } => collect_column_refs(e, out),
11728 _ => {}
11729 }
11730}
11731
11732fn annotate_explain_lines(lines: &mut [String], total_rows: usize, engine: &Engine) {
11733 let catalog = engine.active_catalog();
11734 let cold_ids = catalog.cold_segment_ids_global();
11735 let any_cold = !cold_ids.is_empty();
11736 let cold_ids_repr = if any_cold {
11737 let mut s = alloc::string::String::from("[");
11738 for (i, id) in cold_ids.iter().enumerate() {
11739 if i > 0 {
11740 s.push(',');
11741 }
11742 s.push_str(&alloc::format!("{id}"));
11743 }
11744 s.push(']');
11745 s
11746 } else {
11747 alloc::string::String::new()
11748 };
11749 for (idx, line) in lines.iter_mut().enumerate() {
11750 let trimmed = line.trim_start();
11751 let is_top_level = idx == 0;
11752 if is_top_level {
11753 line.push_str(&alloc::format!(" (rows={total_rows})"));
11754 continue;
11755 }
11756 if let Some(rest) = trimmed.strip_prefix("From: ") {
11757 let (name, scan_kind) = match rest.split_once(" [") {
11758 Some((n, k)) => (n.trim(), k.trim_end_matches(']')),
11759 None => (rest.trim(), ""),
11760 };
11761 let bare = name.split_whitespace().next().unwrap_or(name);
11762 let hot = catalog.get(bare).map(|t| t.rows().len());
11763 let annot = match (hot, scan_kind) {
11768 (Some(h), "full scan") => {
11769 let mut s = alloc::format!(" (hot_rows={h}");
11770 if any_cold {
11771 s.push_str(&alloc::format!(
11772 ", cold_tier=present, cold_segments={cold_ids_repr}"
11773 ));
11774 }
11775 s.push(')');
11776 s
11777 }
11778 (Some(h), "index seek") => {
11779 let mut s = alloc::format!(" (hot_rows≤{h}");
11780 if any_cold {
11781 s.push_str(&alloc::format!(
11782 ", cold_tier=present, cold_segments={cold_ids_repr}"
11783 ));
11784 }
11785 s.push(')');
11786 s
11787 }
11788 _ => " (rows=—)".to_string(),
11789 };
11790 line.push_str(&annot);
11791 continue;
11792 }
11793 line.push_str(" (rows=—)");
11795 }
11796}
11797
11798fn explain_select(stmt: &SelectStatement, engine: &Engine, depth: usize, out: &mut Vec<String>) {
11799 let pad = " ".repeat(depth);
11800 let top = if !stmt.ctes.is_empty() {
11802 if stmt.ctes.iter().any(|c| c.recursive) {
11803 "CTEScan (WITH RECURSIVE)"
11804 } else {
11805 "CTEScan (WITH)"
11806 }
11807 } else if !stmt.unions.is_empty() {
11808 "UnionScan"
11809 } else if select_has_window(stmt) {
11810 "WindowAgg"
11811 } else if aggregate::uses_aggregate(stmt) {
11812 "Aggregate"
11813 } else if stmt.distinct {
11814 "Distinct"
11815 } else if stmt.from.is_some() {
11816 "TableScan"
11817 } else {
11818 "Result"
11819 };
11820 out.push(alloc::format!("{pad}{top}"));
11821 let child = " ".repeat(depth + 1);
11822 for cte in &stmt.ctes {
11824 let head = if cte.recursive {
11825 alloc::format!("{child}CTE (recursive): {}", cte.name)
11826 } else {
11827 alloc::format!("{child}CTE: {}", cte.name)
11828 };
11829 out.push(head);
11830 explain_select(&cte.body, engine, depth + 2, out);
11831 }
11832 if let Some(from) = &stmt.from {
11834 let mut tag = alloc::format!("{child}From: {}", from.primary.name);
11835 if let Some(alias) = &from.primary.alias {
11836 tag.push_str(&alloc::format!(" AS {alias}"));
11837 }
11838 if let Some(w) = &stmt.where_
11841 && let Some(table) = engine.active_catalog().get(&from.primary.name)
11842 {
11843 let alias = from.primary.alias.as_deref().unwrap_or(&from.primary.name);
11844 let cols = &table.schema().columns;
11845 if try_index_seek(w, cols, engine.active_catalog(), table, alias).is_some() {
11846 tag.push_str(" [index seek]");
11847 } else {
11848 tag.push_str(" [full scan]");
11849 }
11850 } else {
11851 tag.push_str(" [full scan]");
11852 }
11853 out.push(tag);
11854 for j in &from.joins {
11855 let kind = match j.kind {
11856 spg_sql::ast::JoinKind::Inner => "INNER JOIN",
11857 spg_sql::ast::JoinKind::Left => "LEFT JOIN",
11858 spg_sql::ast::JoinKind::Cross => "CROSS JOIN",
11859 };
11860 let mut s = alloc::format!("{child}{kind}: {}", j.table.name);
11861 if let Some(alias) = &j.table.alias {
11862 s.push_str(&alloc::format!(" AS {alias}"));
11863 }
11864 if j.on.is_some() {
11865 s.push_str(" (ON …)");
11866 }
11867 out.push(s);
11868 }
11869 }
11870 if let Some(w) = &stmt.where_ {
11872 let mut s = alloc::format!("{child}Filter: {w}");
11873 if expr_has_subquery(w) {
11874 s.push_str(" [subquery]");
11875 }
11876 out.push(s);
11877 }
11878 if let Some(gs) = &stmt.group_by {
11879 let mut parts = Vec::new();
11880 for g in gs {
11881 parts.push(alloc::format!("{g}"));
11882 }
11883 out.push(alloc::format!("{child}GroupBy: {}", parts.join(", ")));
11884 }
11885 if let Some(h) = &stmt.having {
11886 out.push(alloc::format!("{child}Having: {h}"));
11887 }
11888 for o in &stmt.order_by {
11889 let dir = if o.desc { "DESC" } else { "ASC" };
11890 out.push(alloc::format!("{child}OrderBy: {} {dir}", o.expr));
11891 }
11892 if let Some(lim) = stmt.limit {
11893 out.push(alloc::format!("{child}Limit: {lim}"));
11894 }
11895 if let Some(off) = stmt.offset {
11896 out.push(alloc::format!("{child}Offset: {off}"));
11897 }
11898 if stmt
11900 .items
11901 .iter()
11902 .any(|it| matches!(it, SelectItem::Wildcard))
11903 {
11904 out.push(alloc::format!("{child}Project: *"));
11905 } else {
11906 out.push(alloc::format!(
11907 "{child}Project: {} item(s)",
11908 stmt.items.len()
11909 ));
11910 }
11911 for (kind, peer) in &stmt.unions {
11913 let label = match kind {
11914 UnionKind::All => "UNION ALL",
11915 UnionKind::Distinct => "UNION",
11916 };
11917 out.push(alloc::format!("{child}{label}"));
11918 explain_select(peer, engine, depth + 2, out);
11919 }
11920}
11921
11922fn is_correlation_error(e: &EngineError) -> bool {
11927 matches!(
11928 e,
11929 EngineError::Eval(
11930 eval::EvalError::ColumnNotFound { .. } | eval::EvalError::UnknownQualifier { .. }
11931 )
11932 )
11933}
11934
11935struct JoinedPeer<'a> {
11946 eager_rows: Option<Vec<Row>>,
11947 cols: Vec<ColumnSchema>,
11948 alias: String,
11949 kind: JoinKind,
11950 on: Option<&'a Expr>,
11951 lateral: Option<&'a SelectStatement>,
11952}
11953
11954fn synth_lateral_col_name(expr: &Expr, idx: usize) -> String {
11961 match expr {
11962 Expr::Column(c) => c.name.clone(),
11964 Expr::FunctionCall { name, .. } => name.clone(),
11967 Expr::Cast { expr: inner, .. } => synth_lateral_col_name(inner, idx),
11969 _ => alloc::format!("column{}", idx + 1),
11971 }
11972}
11973
11974fn substitute_outer_columns_multi(
11981 stmt: &mut SelectStatement,
11982 outer_row: &Row,
11983 outer_schema: &[ColumnSchema],
11984) {
11985 substitute_outer_in_select(stmt, outer_row, outer_schema);
11986}
11987
11988fn substitute_outer_in_select(
11989 stmt: &mut SelectStatement,
11990 outer_row: &Row,
11991 outer_schema: &[ColumnSchema],
11992) {
11993 for item in &mut stmt.items {
11994 if let SelectItem::Expr { expr, .. } = item {
11995 substitute_outer_in_expr(expr, outer_row, outer_schema);
11996 }
11997 }
11998 if let Some(w) = &mut stmt.where_ {
11999 substitute_outer_in_expr(w, outer_row, outer_schema);
12000 }
12001 if let Some(gs) = &mut stmt.group_by {
12002 for g in gs {
12003 substitute_outer_in_expr(g, outer_row, outer_schema);
12004 }
12005 }
12006 if let Some(h) = &mut stmt.having {
12007 substitute_outer_in_expr(h, outer_row, outer_schema);
12008 }
12009 for o in &mut stmt.order_by {
12010 substitute_outer_in_expr(&mut o.expr, outer_row, outer_schema);
12011 }
12012 for (_, peer) in &mut stmt.unions {
12013 substitute_outer_in_select(peer, outer_row, outer_schema);
12014 }
12015}
12016
12017fn substitute_outer_in_expr(e: &mut Expr, outer_row: &Row, outer_schema: &[ColumnSchema]) {
12018 if let Expr::Column(c) = e
12019 && let Some(qual) = &c.qualifier
12020 {
12021 let composite = alloc::format!("{qual}.{}", c.name);
12022 if let Some(idx) = outer_schema
12023 .iter()
12024 .position(|sc| sc.name.eq_ignore_ascii_case(&composite))
12025 {
12026 let v = outer_row.values.get(idx).cloned().unwrap_or(Value::Null);
12027 if let Ok(lit) = value_to_literal_expr(v) {
12028 *e = lit;
12029 return;
12030 }
12031 }
12032 }
12033 match e {
12034 Expr::Binary { lhs, rhs, .. } => {
12035 substitute_outer_in_expr(lhs, outer_row, outer_schema);
12036 substitute_outer_in_expr(rhs, outer_row, outer_schema);
12037 }
12038 Expr::Unary { expr: inner, .. } => {
12039 substitute_outer_in_expr(inner, outer_row, outer_schema);
12040 }
12041 Expr::FunctionCall { args, .. } => {
12042 for a in args {
12043 substitute_outer_in_expr(a, outer_row, outer_schema);
12044 }
12045 }
12046 Expr::Cast { expr: inner, .. } => {
12047 substitute_outer_in_expr(inner, outer_row, outer_schema);
12048 }
12049 Expr::Case {
12050 operand,
12051 branches,
12052 else_branch,
12053 } => {
12054 if let Some(op) = operand {
12055 substitute_outer_in_expr(op, outer_row, outer_schema);
12056 }
12057 for (cond, val) in branches {
12058 substitute_outer_in_expr(cond, outer_row, outer_schema);
12059 substitute_outer_in_expr(val, outer_row, outer_schema);
12060 }
12061 if let Some(e) = else_branch {
12062 substitute_outer_in_expr(e, outer_row, outer_schema);
12063 }
12064 }
12065 _ => {}
12066 }
12067}
12068
12069fn substitute_outer_columns(stmt: &mut SelectStatement, row: &Row, ctx: &EvalContext<'_>) {
12070 let Some(outer_alias) = ctx.table_alias else {
12071 return;
12072 };
12073 substitute_in_select(stmt, row, ctx, outer_alias);
12074}
12075
12076fn substitute_in_select(
12077 stmt: &mut SelectStatement,
12078 row: &Row,
12079 ctx: &EvalContext<'_>,
12080 outer_alias: &str,
12081) {
12082 for item in &mut stmt.items {
12083 if let SelectItem::Expr { expr, .. } = item {
12084 substitute_in_expr(expr, row, ctx, outer_alias);
12085 }
12086 }
12087 if let Some(w) = &mut stmt.where_ {
12088 substitute_in_expr(w, row, ctx, outer_alias);
12089 }
12090 if let Some(gs) = &mut stmt.group_by {
12091 for g in gs {
12092 substitute_in_expr(g, row, ctx, outer_alias);
12093 }
12094 }
12095 if let Some(h) = &mut stmt.having {
12096 substitute_in_expr(h, row, ctx, outer_alias);
12097 }
12098 for o in &mut stmt.order_by {
12099 substitute_in_expr(&mut o.expr, row, ctx, outer_alias);
12100 }
12101 for (_, peer) in &mut stmt.unions {
12102 substitute_in_select(peer, row, ctx, outer_alias);
12103 }
12104}
12105
12106fn substitute_in_expr(e: &mut Expr, row: &Row, ctx: &EvalContext<'_>, outer_alias: &str) {
12107 if let Expr::Column(c) = e
12108 && let Some(qual) = &c.qualifier
12109 && qual.eq_ignore_ascii_case(outer_alias)
12110 {
12111 if let Some(idx) = ctx
12113 .columns
12114 .iter()
12115 .position(|sc| sc.name.eq_ignore_ascii_case(&c.name))
12116 {
12117 let v = row.values.get(idx).cloned().unwrap_or(Value::Null);
12118 if let Ok(lit) = value_to_literal_expr(v) {
12119 *e = lit;
12120 return;
12121 }
12122 }
12123 }
12124 match e {
12125 Expr::Binary { lhs, rhs, .. } => {
12126 substitute_in_expr(lhs, row, ctx, outer_alias);
12127 substitute_in_expr(rhs, row, ctx, outer_alias);
12128 }
12129 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12130 substitute_in_expr(expr, row, ctx, outer_alias);
12131 }
12132 Expr::Like { expr, pattern, .. } => {
12133 substitute_in_expr(expr, row, ctx, outer_alias);
12134 substitute_in_expr(pattern, row, ctx, outer_alias);
12135 }
12136 Expr::FunctionCall { args, .. } => {
12137 for a in args {
12138 substitute_in_expr(a, row, ctx, outer_alias);
12139 }
12140 }
12141 Expr::Extract { source, .. } => substitute_in_expr(source, row, ctx, outer_alias),
12142 Expr::WindowFunction {
12143 args,
12144 partition_by,
12145 order_by,
12146 ..
12147 } => {
12148 for a in args {
12149 substitute_in_expr(a, row, ctx, outer_alias);
12150 }
12151 for p in partition_by {
12152 substitute_in_expr(p, row, ctx, outer_alias);
12153 }
12154 for (o, _) in order_by {
12155 substitute_in_expr(o, row, ctx, outer_alias);
12156 }
12157 }
12158 Expr::ScalarSubquery(s) => substitute_in_select(s, row, ctx, outer_alias),
12159 Expr::Exists { subquery, .. } | Expr::InSubquery { subquery, .. } => {
12160 substitute_in_select(subquery, row, ctx, outer_alias);
12161 }
12162 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
12163 Expr::Array(items) => {
12164 for elem in items {
12165 substitute_in_expr(elem, row, ctx, outer_alias);
12166 }
12167 }
12168 Expr::ArraySubscript { target, index } => {
12169 substitute_in_expr(target, row, ctx, outer_alias);
12170 substitute_in_expr(index, row, ctx, outer_alias);
12171 }
12172 Expr::AnyAll { expr, array, .. } => {
12173 substitute_in_expr(expr, row, ctx, outer_alias);
12174 substitute_in_expr(array, row, ctx, outer_alias);
12175 }
12176 Expr::Case {
12177 operand,
12178 branches,
12179 else_branch,
12180 } => {
12181 if let Some(o) = operand {
12182 substitute_in_expr(o, row, ctx, outer_alias);
12183 }
12184 for (w, t) in branches {
12185 substitute_in_expr(w, row, ctx, outer_alias);
12186 substitute_in_expr(t, row, ctx, outer_alias);
12187 }
12188 if let Some(e) = else_branch {
12189 substitute_in_expr(e, row, ctx, outer_alias);
12190 }
12191 }
12192 }
12193}
12194
12195fn encode_row_key(row: &Row) -> Vec<u8> {
12199 let mut out = Vec::new();
12200 for v in &row.values {
12201 let s = alloc::format!("{v:?}|");
12202 out.extend_from_slice(s.as_bytes());
12203 }
12204 out
12205}
12206
12207fn select_has_window(stmt: &SelectStatement) -> bool {
12208 for item in &stmt.items {
12209 if let SelectItem::Expr { expr, .. } = item
12210 && expr_has_window(expr)
12211 {
12212 return true;
12213 }
12214 }
12215 false
12216}
12217
12218fn expr_has_window(e: &Expr) -> bool {
12219 match e {
12220 Expr::WindowFunction { .. } => true,
12221 Expr::Binary { lhs, rhs, .. } => expr_has_window(lhs) || expr_has_window(rhs),
12222 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12223 expr_has_window(expr)
12224 }
12225 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_window),
12226 Expr::Like { expr, pattern, .. } => expr_has_window(expr) || expr_has_window(pattern),
12227 Expr::Extract { source, .. } => expr_has_window(source),
12228 Expr::ScalarSubquery(_)
12229 | Expr::Exists { .. }
12230 | Expr::InSubquery { .. }
12231 | Expr::Literal(_)
12232 | Expr::Placeholder(_)
12233 | Expr::Column(_) => false,
12234 Expr::Array(items) => items.iter().any(expr_has_window),
12235 Expr::ArraySubscript { target, index } => expr_has_window(target) || expr_has_window(index),
12236 Expr::AnyAll { expr, array, .. } => expr_has_window(expr) || expr_has_window(array),
12237 Expr::Case {
12238 operand,
12239 branches,
12240 else_branch,
12241 } => {
12242 operand.as_deref().is_some_and(expr_has_window)
12243 || branches
12244 .iter()
12245 .any(|(w, t)| expr_has_window(w) || expr_has_window(t))
12246 || else_branch.as_deref().is_some_and(expr_has_window)
12247 }
12248 }
12249}
12250
12251fn collect_window_nodes(e: &Expr, out: &mut Vec<Expr>) {
12252 if let Expr::WindowFunction { .. } = e {
12253 if !out.iter().any(|x| x == e) {
12258 out.push(e.clone());
12259 }
12260 return;
12261 }
12262 match e {
12263 Expr::WindowFunction { .. } => unreachable!(),
12265 Expr::Binary { lhs, rhs, .. } => {
12266 collect_window_nodes(lhs, out);
12267 collect_window_nodes(rhs, out);
12268 }
12269 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12270 collect_window_nodes(expr, out);
12271 }
12272 Expr::FunctionCall { args, .. } => {
12273 for a in args {
12274 collect_window_nodes(a, out);
12275 }
12276 }
12277 Expr::Like { expr, pattern, .. } => {
12278 collect_window_nodes(expr, out);
12279 collect_window_nodes(pattern, out);
12280 }
12281 Expr::Extract { source, .. } => collect_window_nodes(source, out),
12282 _ => {}
12283 }
12284}
12285
12286fn rewrite_window_to_columns(e: &mut Expr, window_nodes: &[Expr]) {
12287 if let Expr::WindowFunction { .. } = e
12288 && let Some(idx) = window_nodes.iter().position(|w| w == e)
12289 {
12290 *e = Expr::Column(spg_sql::ast::ColumnName {
12291 qualifier: None,
12292 name: alloc::format!("__win_{idx}"),
12293 });
12294 return;
12295 }
12296 match e {
12297 Expr::Binary { lhs, rhs, .. } => {
12298 rewrite_window_to_columns(lhs, window_nodes);
12299 rewrite_window_to_columns(rhs, window_nodes);
12300 }
12301 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
12302 rewrite_window_to_columns(expr, window_nodes);
12303 }
12304 Expr::FunctionCall { args, .. } => {
12305 for a in args {
12306 rewrite_window_to_columns(a, window_nodes);
12307 }
12308 }
12309 Expr::Like { expr, pattern, .. } => {
12310 rewrite_window_to_columns(expr, window_nodes);
12311 rewrite_window_to_columns(pattern, window_nodes);
12312 }
12313 Expr::Extract { source, .. } => rewrite_window_to_columns(source, window_nodes),
12314 _ => {}
12315 }
12316}
12317
12318fn partition_key_cmp(a: &[Value], b: &[Value]) -> core::cmp::Ordering {
12322 for (x, y) in a.iter().zip(b.iter()) {
12323 let c = value_cmp(x, y);
12324 if c != core::cmp::Ordering::Equal {
12325 return c;
12326 }
12327 }
12328 a.len().cmp(&b.len())
12329}
12330
12331fn order_key_cmp(a: &[(Value, bool)], b: &[(Value, bool)]) -> core::cmp::Ordering {
12332 for ((va, desc), (vb, _)) in a.iter().zip(b.iter()) {
12333 let c = value_cmp(va, vb);
12334 let c = if *desc { c.reverse() } else { c };
12335 if c != core::cmp::Ordering::Equal {
12336 return c;
12337 }
12338 }
12339 a.len().cmp(&b.len())
12340}
12341
12342const fn value_is_integer(v: &Value) -> bool {
12348 matches!(v, Value::SmallInt(_) | Value::Int(_) | Value::BigInt(_))
12349}
12350
12351const fn value_to_i64(v: &Value) -> i64 {
12355 match v {
12356 Value::SmallInt(n) => *n as i64,
12357 Value::Int(n) => *n as i64,
12358 Value::BigInt(n) => *n,
12359 _ => panic!("value_to_i64 called on non-integer Value"),
12360 }
12361}
12362
12363fn generate_series_integers(
12369 start: i64,
12370 stop: i64,
12371 step: i64,
12372 cancel: &CancelToken<'_>,
12373) -> Result<alloc::vec::Vec<Row>, EngineError> {
12374 if step == 0 {
12375 return Err(EngineError::Unsupported(
12376 "generate_series(): step argument cannot be zero".into(),
12377 ));
12378 }
12379 let mut out = alloc::vec::Vec::new();
12380 let mut cur = start;
12381 const MAX_ROWS: usize = 10_000_000;
12385 loop {
12386 cancel.check()?;
12387 if step > 0 && cur > stop {
12388 break;
12389 }
12390 if step < 0 && cur < stop {
12391 break;
12392 }
12393 out.push(Row::new(alloc::vec![Value::BigInt(cur)]));
12394 if out.len() > MAX_ROWS {
12395 return Err(EngineError::Unsupported(alloc::format!(
12396 "generate_series(): exceeded {MAX_ROWS} rows; \
12397 narrow start/stop or use a larger step"
12398 )));
12399 }
12400 cur = match cur.checked_add(step) {
12401 Some(n) => n,
12402 None => break,
12403 };
12404 }
12405 Ok(out)
12406}
12407
12408fn generate_series_timestamps(
12413 start: i64,
12414 stop: i64,
12415 step: Value,
12416 cancel: &CancelToken<'_>,
12417) -> Result<alloc::vec::Vec<Row>, EngineError> {
12418 let (months, micros) = match &step {
12419 Value::Interval { months, micros } => (*months, *micros),
12420 _ => unreachable!("caller guards step.is_interval"),
12421 };
12422 if months == 0 && micros == 0 {
12423 return Err(EngineError::Unsupported(
12424 "generate_series(): INTERVAL step cannot be zero".into(),
12425 ));
12426 }
12427 let ascending = months > 0 || micros > 0;
12428 let mut out = alloc::vec::Vec::new();
12429 let mut cur = Value::Timestamp(start);
12430 const MAX_ROWS: usize = 10_000_000;
12431 loop {
12432 cancel.check()?;
12433 let cur_t = match cur {
12434 Value::Timestamp(t) => t,
12435 _ => unreachable!("loop invariant: cur is Timestamp"),
12436 };
12437 if ascending && cur_t > stop {
12438 break;
12439 }
12440 if !ascending && cur_t < stop {
12441 break;
12442 }
12443 out.push(Row::new(alloc::vec![Value::Timestamp(cur_t)]));
12444 if out.len() > MAX_ROWS {
12445 return Err(EngineError::Unsupported(alloc::format!(
12446 "generate_series(): exceeded {MAX_ROWS} rows; \
12447 narrow start/stop or use a larger step"
12448 )));
12449 }
12450 let next = eval::apply_binary_interval(
12451 spg_sql::ast::BinOp::Add,
12452 &cur,
12453 &Value::Interval { months, micros },
12454 )
12455 .map_err(EngineError::Eval)?;
12456 cur = match next {
12457 Some(v) => v,
12458 None => break,
12459 };
12460 }
12461 Ok(out)
12462}
12463
12464#[allow(clippy::match_same_arms)] fn value_cmp(a: &Value, b: &Value) -> core::cmp::Ordering {
12466 use core::cmp::Ordering;
12467 match (a, b) {
12468 (Value::Null, Value::Null) => Ordering::Equal,
12469 (Value::Null, _) => Ordering::Less,
12470 (_, Value::Null) => Ordering::Greater,
12471 (Value::Int(x), Value::Int(y)) => x.cmp(y),
12472 (Value::BigInt(x), Value::BigInt(y)) => x.cmp(y),
12473 (Value::SmallInt(x), Value::SmallInt(y)) => x.cmp(y),
12474 (Value::Text(x), Value::Text(y)) => x.cmp(y),
12475 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
12476 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
12477 (Value::Date(x), Value::Date(y)) => x.cmp(y),
12478 (Value::Timestamp(x), Value::Timestamp(y)) => x.cmp(y),
12479 _ => alloc::format!("{a:?}").cmp(&alloc::format!("{b:?}")),
12482 }
12483}
12484
12485#[allow(
12491 clippy::too_many_arguments,
12492 clippy::cast_possible_truncation,
12493 clippy::cast_possible_wrap,
12494 clippy::cast_precision_loss,
12495 clippy::cast_sign_loss,
12496 clippy::doc_markdown,
12497 clippy::too_many_lines,
12498 clippy::type_complexity,
12499 clippy::match_same_arms
12500)]
12501fn compute_window_partition(
12502 name: &str,
12503 args: &[Expr],
12504 ordered: bool,
12505 frame: Option<&WindowFrame>,
12506 null_treatment: spg_sql::ast::NullTreatment,
12507 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12508 filtered_rows: &[&Row],
12509 ctx: &EvalContext<'_>,
12510 out_vals: &mut [Value],
12511) -> Result<(), EngineError> {
12512 let ignore_nulls = matches!(null_treatment, spg_sql::ast::NullTreatment::Ignore);
12513 let lower = name.to_ascii_lowercase();
12514 match lower.as_str() {
12515 "row_number" => {
12516 for (rank, (_, _, idx)) in slice.iter().enumerate() {
12517 out_vals[*idx] = Value::BigInt((rank + 1) as i64);
12518 }
12519 Ok(())
12520 }
12521 "rank" => {
12522 let mut prev_key: Option<&[(Value, bool)]> = None;
12523 let mut current_rank: i64 = 1;
12524 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12525 if let Some(p) = prev_key
12526 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12527 {
12528 current_rank = (i + 1) as i64;
12529 }
12530 if prev_key.is_none() {
12531 current_rank = 1;
12532 }
12533 out_vals[*idx] = Value::BigInt(current_rank);
12534 prev_key = Some(okey.as_slice());
12535 }
12536 Ok(())
12537 }
12538 "dense_rank" => {
12539 let mut prev_key: Option<&[(Value, bool)]> = None;
12540 let mut current_rank: i64 = 0;
12541 for (_, okey, idx) in slice {
12542 if prev_key.is_none_or(|p| order_key_cmp(p, okey) != core::cmp::Ordering::Equal) {
12543 current_rank += 1;
12544 }
12545 out_vals[*idx] = Value::BigInt(current_rank);
12546 prev_key = Some(okey.as_slice());
12547 }
12548 Ok(())
12549 }
12550 "sum" | "avg" | "min" | "max" | "count" | "count_star" => {
12551 let arg_values: Vec<Value> = if lower == "count_star" || args.is_empty() {
12554 slice.iter().map(|_| Value::Null).collect()
12555 } else {
12556 slice
12557 .iter()
12558 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12559 .collect::<Result<_, _>>()
12560 .map_err(EngineError::Eval)?
12561 };
12562 let eff = effective_frame(frame, ordered)?;
12566 #[allow(clippy::needless_range_loop)]
12567 for i in 0..slice.len() {
12568 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12569 let mut sum: f64 = 0.0;
12570 let mut count: i64 = 0;
12571 let mut min_v: Option<f64> = None;
12572 let mut max_v: Option<f64> = None;
12573 let mut row_count: i64 = 0;
12574 if lo <= hi {
12575 for j in lo..=hi {
12576 let v = &arg_values[j];
12577 match lower.as_str() {
12578 "count_star" => row_count += 1,
12579 "count" => {
12580 if !v.is_null() {
12581 count += 1;
12582 }
12583 }
12584 _ => {
12585 if let Some(x) = value_to_f64(v) {
12586 sum += x;
12587 count += 1;
12588 min_v = Some(min_v.map_or(x, |m| m.min(x)));
12589 max_v = Some(max_v.map_or(x, |m| m.max(x)));
12590 }
12591 }
12592 }
12593 }
12594 }
12595 let value = match lower.as_str() {
12596 "count_star" => Value::BigInt(row_count),
12597 "count" => Value::BigInt(count),
12598 "sum" => Value::Float(sum),
12599 "avg" => {
12600 if count == 0 {
12601 Value::Null
12602 } else {
12603 Value::Float(sum / count as f64)
12604 }
12605 }
12606 "min" => min_v.map_or(Value::Null, Value::Float),
12607 "max" => max_v.map_or(Value::Null, Value::Float),
12608 _ => unreachable!(),
12609 };
12610 let (_, _, idx) = &slice[i];
12611 out_vals[*idx] = value;
12612 }
12613 Ok(())
12614 }
12615 "lag" | "lead" => {
12616 if args.is_empty() {
12619 return Err(EngineError::Unsupported(alloc::format!(
12620 "{lower}() requires at least one argument"
12621 )));
12622 }
12623 let offset: i64 = if args.len() >= 2 {
12624 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12625 .map_err(EngineError::Eval)?;
12626 match v {
12627 Value::SmallInt(n) => i64::from(n),
12628 Value::Int(n) => i64::from(n),
12629 Value::BigInt(n) => n,
12630 _ => {
12631 return Err(EngineError::Unsupported(alloc::format!(
12632 "{lower}() offset must be integer"
12633 )));
12634 }
12635 }
12636 } else {
12637 1
12638 };
12639 let default: Value = if args.len() >= 3 {
12640 eval::eval_expr(&args[2], filtered_rows[slice[0].2], ctx)
12641 .map_err(EngineError::Eval)?
12642 } else {
12643 Value::Null
12644 };
12645 let values: Vec<Value> = slice
12646 .iter()
12647 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12648 .collect::<Result<_, _>>()
12649 .map_err(EngineError::Eval)?;
12650 let n = slice.len();
12651 for (i, (_, _, idx)) in slice.iter().enumerate() {
12652 let signed_offset = if lower == "lag" { -offset } else { offset };
12653 let v = if ignore_nulls {
12654 let step: i64 = if signed_offset >= 0 { 1 } else { -1 };
12658 let needed: i64 = signed_offset.abs();
12659 if needed == 0 {
12660 values[i].clone()
12661 } else {
12662 let mut j: i64 = i as i64;
12663 let mut hits: i64 = 0;
12664 let mut found: Option<Value> = None;
12665 loop {
12666 j += step;
12667 if j < 0 || j >= n as i64 {
12668 break;
12669 }
12670 #[allow(clippy::cast_sign_loss)]
12671 let v = &values[j as usize];
12672 if !v.is_null() {
12673 hits += 1;
12674 if hits == needed {
12675 found = Some(v.clone());
12676 break;
12677 }
12678 }
12679 }
12680 found.unwrap_or_else(|| default.clone())
12681 }
12682 } else {
12683 let target_signed = i64::try_from(i).unwrap_or(i64::MAX) + signed_offset;
12684 if target_signed < 0 || target_signed >= i64::try_from(n).unwrap_or(i64::MAX) {
12685 default.clone()
12686 } else {
12687 #[allow(clippy::cast_sign_loss)]
12688 {
12689 values[target_signed as usize].clone()
12690 }
12691 }
12692 };
12693 out_vals[*idx] = v;
12694 }
12695 Ok(())
12696 }
12697 "first_value" | "last_value" | "nth_value" => {
12698 if args.is_empty() {
12699 return Err(EngineError::Unsupported(alloc::format!(
12700 "{lower}() requires at least one argument"
12701 )));
12702 }
12703 let values: Vec<Value> = slice
12704 .iter()
12705 .map(|(_, _, idx)| eval::eval_expr(&args[0], filtered_rows[*idx], ctx))
12706 .collect::<Result<_, _>>()
12707 .map_err(EngineError::Eval)?;
12708 let nth: usize = if lower == "nth_value" {
12709 if args.len() < 2 {
12710 return Err(EngineError::Unsupported(
12711 "nth_value() requires (expr, n)".into(),
12712 ));
12713 }
12714 let v = eval::eval_expr(&args[1], filtered_rows[slice[0].2], ctx)
12715 .map_err(EngineError::Eval)?;
12716 let raw = match v {
12717 Value::SmallInt(n) => i64::from(n),
12718 Value::Int(n) => i64::from(n),
12719 Value::BigInt(n) => n,
12720 _ => {
12721 return Err(EngineError::Unsupported(
12722 "nth_value() n must be integer".into(),
12723 ));
12724 }
12725 };
12726 if raw < 1 {
12727 return Err(EngineError::Unsupported(
12728 "nth_value() n must be >= 1".into(),
12729 ));
12730 }
12731 #[allow(clippy::cast_sign_loss)]
12732 {
12733 raw as usize
12734 }
12735 } else {
12736 0
12737 };
12738 let eff = effective_frame(frame, ordered)?;
12739 for i in 0..slice.len() {
12740 let (lo, hi) = frame_bounds_for_row(&eff, i, slice);
12741 let (_, _, idx) = &slice[i];
12742 let v = if lo > hi {
12743 Value::Null
12744 } else if ignore_nulls && matches!(lower.as_str(), "first_value" | "last_value") {
12745 if lower == "first_value" {
12748 (lo..=hi)
12749 .find_map(|j| {
12750 let v = &values[j];
12751 (!v.is_null()).then(|| v.clone())
12752 })
12753 .unwrap_or(Value::Null)
12754 } else {
12755 (lo..=hi)
12756 .rev()
12757 .find_map(|j| {
12758 let v = &values[j];
12759 (!v.is_null()).then(|| v.clone())
12760 })
12761 .unwrap_or(Value::Null)
12762 }
12763 } else {
12764 match lower.as_str() {
12765 "first_value" => values[lo].clone(),
12766 "last_value" => values[hi].clone(),
12767 "nth_value" => {
12768 let pos = lo + nth - 1;
12769 if pos > hi {
12770 Value::Null
12771 } else {
12772 values[pos].clone()
12773 }
12774 }
12775 _ => unreachable!(),
12776 }
12777 };
12778 out_vals[*idx] = v;
12779 }
12780 Ok(())
12781 }
12782 "ntile" => {
12783 if args.is_empty() {
12784 return Err(EngineError::Unsupported(
12785 "ntile(n) requires an integer argument".into(),
12786 ));
12787 }
12788 let v = eval::eval_expr(&args[0], filtered_rows[slice[0].2], ctx)
12789 .map_err(EngineError::Eval)?;
12790 let bucket_count: i64 = match v {
12791 Value::SmallInt(n) => i64::from(n),
12792 Value::Int(n) => i64::from(n),
12793 Value::BigInt(n) => n,
12794 _ => {
12795 return Err(EngineError::Unsupported(
12796 "ntile() argument must be integer".into(),
12797 ));
12798 }
12799 };
12800 if bucket_count < 1 {
12801 return Err(EngineError::Unsupported(
12802 "ntile() argument must be >= 1".into(),
12803 ));
12804 }
12805 #[allow(clippy::cast_sign_loss)]
12806 let buckets = bucket_count as usize;
12807 let n = slice.len();
12808 let base = n / buckets;
12811 let extras = n % buckets;
12812 let mut bucket: usize = 1;
12813 let mut remaining_in_bucket = if extras > 0 { base + 1 } else { base };
12814 let mut buckets_with_extra_remaining = extras;
12815 for (_, _, idx) in slice {
12816 if remaining_in_bucket == 0 {
12817 bucket += 1;
12818 buckets_with_extra_remaining = buckets_with_extra_remaining.saturating_sub(1);
12819 remaining_in_bucket = if buckets_with_extra_remaining > 0 {
12820 base + 1
12821 } else {
12822 base
12823 };
12824 if remaining_in_bucket == 0 {
12827 remaining_in_bucket = 1;
12828 }
12829 }
12830 out_vals[*idx] = Value::BigInt(i64::try_from(bucket).unwrap_or(i64::MAX));
12831 remaining_in_bucket -= 1;
12832 }
12833 Ok(())
12834 }
12835 "percent_rank" => {
12836 let n = slice.len();
12839 let mut prev_key: Option<&[(Value, bool)]> = None;
12840 let mut current_rank: i64 = 1;
12841 for (i, (_, okey, idx)) in slice.iter().enumerate() {
12842 if let Some(p) = prev_key
12843 && order_key_cmp(p, okey) != core::cmp::Ordering::Equal
12844 {
12845 current_rank = i64::try_from(i + 1).unwrap_or(i64::MAX);
12846 }
12847 if prev_key.is_none() {
12848 current_rank = 1;
12849 }
12850 #[allow(clippy::cast_precision_loss)]
12851 let pr = if n <= 1 {
12852 0.0
12853 } else {
12854 (current_rank - 1) as f64 / (n - 1) as f64
12855 };
12856 out_vals[*idx] = Value::Float(pr);
12857 prev_key = Some(okey.as_slice());
12858 }
12859 Ok(())
12860 }
12861 "cume_dist" => {
12862 let n = slice.len();
12864 for i in 0..slice.len() {
12866 let peer_end = peer_group_end(slice, i);
12867 #[allow(clippy::cast_precision_loss)]
12868 let cd = (peer_end + 1) as f64 / n as f64;
12869 let (_, _, idx) = &slice[i];
12870 out_vals[*idx] = Value::Float(cd);
12871 }
12872 Ok(())
12873 }
12874 other => Err(EngineError::Unsupported(alloc::format!(
12875 "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)"
12876 ))),
12877 }
12878}
12879
12880fn effective_frame(
12887 frame: Option<&WindowFrame>,
12888 ordered: bool,
12889) -> Result<(FrameKind, FrameBound, FrameBound), EngineError> {
12890 match frame {
12891 None => {
12892 if ordered {
12893 Ok((
12894 FrameKind::Range,
12895 FrameBound::UnboundedPreceding,
12896 FrameBound::CurrentRow,
12897 ))
12898 } else {
12899 Ok((
12900 FrameKind::Rows,
12901 FrameBound::UnboundedPreceding,
12902 FrameBound::UnboundedFollowing,
12903 ))
12904 }
12905 }
12906 Some(fr) => {
12907 let end = fr.end.clone().unwrap_or(FrameBound::CurrentRow);
12908 if matches!(fr.start, FrameBound::UnboundedFollowing)
12910 || matches!(end, FrameBound::UnboundedPreceding)
12911 {
12912 return Err(EngineError::Unsupported(alloc::format!(
12913 "invalid frame: start={:?} end={:?}",
12914 fr.start,
12915 end
12916 )));
12917 }
12918 if fr.kind == FrameKind::Range
12923 && (matches!(
12924 fr.start,
12925 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
12926 ) || matches!(
12927 end,
12928 FrameBound::OffsetPreceding(_) | FrameBound::OffsetFollowing(_)
12929 ))
12930 {
12931 return Err(EngineError::Unsupported(
12932 "RANGE with explicit offset bounds is not supported (v4.20: only UNBOUNDED / CURRENT ROW for RANGE)".into(),
12933 ));
12934 }
12935 Ok((fr.kind, fr.start.clone(), end))
12936 }
12937 }
12938}
12939
12940#[allow(clippy::type_complexity)]
12944fn frame_bounds_for_row(
12945 eff: &(FrameKind, FrameBound, FrameBound),
12946 i: usize,
12947 slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)],
12948) -> (usize, usize) {
12949 let (kind, start, end) = eff;
12950 let n = slice.len();
12951 let last = n.saturating_sub(1);
12952 let (mut lo, mut hi) = match kind {
12953 FrameKind::Rows => {
12954 let lo = match start {
12955 FrameBound::UnboundedPreceding => 0,
12956 FrameBound::OffsetPreceding(k) => {
12957 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12958 i.saturating_sub(k)
12959 }
12960 FrameBound::CurrentRow => i,
12961 FrameBound::OffsetFollowing(k) => {
12962 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12963 i.saturating_add(k).min(last)
12964 }
12965 FrameBound::UnboundedFollowing => last,
12966 };
12967 let hi = match end {
12968 FrameBound::UnboundedPreceding => 0,
12969 FrameBound::OffsetPreceding(k) => {
12970 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12971 i.saturating_sub(k)
12972 }
12973 FrameBound::CurrentRow => i,
12974 FrameBound::OffsetFollowing(k) => {
12975 let k = usize::try_from(*k).unwrap_or(usize::MAX);
12976 i.saturating_add(k).min(last)
12977 }
12978 FrameBound::UnboundedFollowing => last,
12979 };
12980 (lo, hi)
12981 }
12982 FrameKind::Range => {
12983 let lo = match start {
12989 FrameBound::UnboundedPreceding => 0,
12990 FrameBound::CurrentRow => peer_group_start(slice, i),
12991 FrameBound::UnboundedFollowing => last,
12992 _ => unreachable!("offset bounds rejected for RANGE"),
12993 };
12994 let hi = match end {
12995 FrameBound::UnboundedPreceding => 0,
12996 FrameBound::CurrentRow => peer_group_end(slice, i),
12997 FrameBound::UnboundedFollowing => last,
12998 _ => unreachable!("offset bounds rejected for RANGE"),
12999 };
13000 (lo, hi)
13001 }
13002 };
13003 if hi >= n {
13004 hi = last;
13005 }
13006 if lo >= n {
13007 lo = last;
13008 }
13009 (lo, hi)
13010}
13011
13012#[allow(clippy::type_complexity)]
13016fn peer_group_start(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13017 let key = &slice[i].1;
13018 let mut j = i;
13019 while j > 0 && order_key_cmp(&slice[j - 1].1, key) == core::cmp::Ordering::Equal {
13020 j -= 1;
13021 }
13022 j
13023}
13024
13025#[allow(clippy::type_complexity)]
13028fn peer_group_end(slice: &[(Vec<Value>, Vec<(Value, bool)>, usize)], i: usize) -> usize {
13029 let key = &slice[i].1;
13030 let mut j = i;
13031 while j + 1 < slice.len() && order_key_cmp(&slice[j + 1].1, key) == core::cmp::Ordering::Equal {
13032 j += 1;
13033 }
13034 j
13035}
13036
13037fn value_to_f64(v: &Value) -> Option<f64> {
13038 match v {
13039 Value::SmallInt(n) => Some(f64::from(*n)),
13040 Value::Int(n) => Some(f64::from(*n)),
13041 #[allow(clippy::cast_precision_loss)]
13042 Value::BigInt(n) => Some(*n as f64),
13043 Value::Float(x) => Some(*x),
13044 _ => None,
13045 }
13046}
13047
13048fn expr_tree_has_subquery(stmt: &SelectStatement) -> bool {
13052 let mut any = false;
13053 for item in &stmt.items {
13054 if let SelectItem::Expr { expr, .. } = item {
13055 any = any || expr_has_subquery(expr);
13056 }
13057 }
13058 if let Some(w) = &stmt.where_ {
13059 any = any || expr_has_subquery(w);
13060 }
13061 if let Some(h) = &stmt.having {
13062 any = any || expr_has_subquery(h);
13063 }
13064 for o in &stmt.order_by {
13065 any = any || expr_has_subquery(&o.expr);
13066 }
13067 for (_, peer) in &stmt.unions {
13068 any = any || expr_tree_has_subquery(peer);
13069 }
13070 any
13071}
13072
13073fn expr_has_subquery(e: &Expr) -> bool {
13074 match e {
13075 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => true,
13076 Expr::Binary { lhs, rhs, .. } => expr_has_subquery(lhs) || expr_has_subquery(rhs),
13077 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13078 expr_has_subquery(expr)
13079 }
13080 Expr::FunctionCall { args, .. } => args.iter().any(expr_has_subquery),
13081 Expr::Like { expr, pattern, .. } => expr_has_subquery(expr) || expr_has_subquery(pattern),
13082 Expr::Extract { source, .. } => expr_has_subquery(source),
13083 Expr::WindowFunction {
13084 args,
13085 partition_by,
13086 order_by,
13087 ..
13088 } => {
13089 args.iter().any(expr_has_subquery)
13090 || partition_by.iter().any(expr_has_subquery)
13091 || order_by.iter().any(|(e, _)| expr_has_subquery(e))
13092 }
13093 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => false,
13094 Expr::Array(items) => items.iter().any(expr_has_subquery),
13095 Expr::ArraySubscript { target, index } => {
13096 expr_has_subquery(target) || expr_has_subquery(index)
13097 }
13098 Expr::AnyAll { expr, array, .. } => expr_has_subquery(expr) || expr_has_subquery(array),
13099 Expr::Case {
13100 operand,
13101 branches,
13102 else_branch,
13103 } => {
13104 operand.as_deref().is_some_and(expr_has_subquery)
13105 || branches
13106 .iter()
13107 .any(|(w, t)| expr_has_subquery(w) || expr_has_subquery(t))
13108 || else_branch.as_deref().is_some_and(expr_has_subquery)
13109 }
13110 }
13111}
13112
13113fn value_to_literal_expr(v: Value) -> Result<Expr, EngineError> {
13120 let lit = match v {
13121 Value::Null => Literal::Null,
13122 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13123 Value::Int(n) => Literal::Integer(i64::from(n)),
13124 Value::BigInt(n) => Literal::Integer(n),
13125 Value::Float(x) => Literal::Float(x),
13126 Value::Text(s) | Value::Json(s) => Literal::String(s),
13127 Value::Bool(b) => Literal::Bool(b),
13128 other => {
13129 return Err(EngineError::Unsupported(alloc::format!(
13130 "subquery result type {:?} not yet materialisable; cast to text or integer in the inner SELECT",
13131 other.data_type()
13132 )));
13133 }
13134 };
13135 Ok(Expr::Literal(lit))
13136}
13137
13138fn value_to_literal_expr_permissive(v: Value) -> Result<Expr, EngineError> {
13144 let lit = match v {
13145 Value::Null => Literal::Null,
13146 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13147 Value::Int(n) => Literal::Integer(i64::from(n)),
13148 Value::BigInt(n) => Literal::Integer(n),
13149 Value::Float(x) => Literal::Float(x),
13150 Value::Text(s) | Value::Json(s) => Literal::String(s),
13151 Value::Bool(b) => Literal::Bool(b),
13152 Value::Vector(xs) => Literal::Vector(xs),
13153 Value::Date(days) => {
13157 let micros = (i64::from(days)) * 86_400_000_000;
13158 Literal::String(format_timestamp_micros_as_date(micros))
13159 }
13160 Value::Timestamp(us) => Literal::String(format_timestamp_micros(us)),
13161 Value::Numeric { scaled, scale } => Literal::String(format_numeric(scaled, scale)),
13162 other => {
13163 return Err(EngineError::Unsupported(alloc::format!(
13164 "INSERT … SELECT cannot materialise value of type {:?}; \
13165 add an explicit CAST in the inner SELECT",
13166 other.data_type()
13167 )));
13168 }
13169 };
13170 Ok(Expr::Literal(lit))
13171}
13172
13173fn format_timestamp_micros(us: i64) -> String {
13174 let days = us.div_euclid(86_400_000_000);
13176 let intra_day = us.rem_euclid(86_400_000_000);
13177 let date = format_timestamp_micros_as_date(days * 86_400_000_000);
13178 let secs = intra_day / 1_000_000;
13179 let us_rem = intra_day % 1_000_000;
13180 let h = (secs / 3600) % 24;
13181 let m = (secs / 60) % 60;
13182 let s = secs % 60;
13183 if us_rem == 0 {
13184 alloc::format!("{date} {h:02}:{m:02}:{s:02}")
13185 } else {
13186 alloc::format!("{date} {h:02}:{m:02}:{s:02}.{us_rem:06}")
13187 }
13188}
13189
13190fn format_timestamp_micros_as_date(us: i64) -> String {
13191 let days = us.div_euclid(86_400_000_000);
13194 let jdn = days + 2_440_588;
13196 let (y, mo, d) = jdn_to_ymd(jdn);
13197 alloc::format!("{y:04}-{mo:02}-{d:02}")
13198}
13199
13200fn jdn_to_ymd(jdn: i64) -> (i64, u32, u32) {
13201 let l = jdn + 68569;
13203 let n = (4 * l) / 146_097;
13204 let l = l - (146_097 * n + 3) / 4;
13205 let i = (4000 * (l + 1)) / 1_461_001;
13206 let l = l - (1461 * i) / 4 + 31;
13207 let j = (80 * l) / 2447;
13208 let day = (l - (2447 * j) / 80) as u32;
13209 let l = j / 11;
13210 let month = (j + 2 - 12 * l) as u32;
13211 let year = 100 * (n - 49) + i + l;
13212 (year, month, day)
13213}
13214
13215fn format_numeric(scaled: i128, scale: u8) -> String {
13216 if scale == 0 {
13217 return alloc::format!("{scaled}");
13218 }
13219 let abs = scaled.unsigned_abs();
13220 let divisor = 10u128.pow(u32::from(scale));
13221 let whole = abs / divisor;
13222 let frac = abs % divisor;
13223 let sign = if scaled < 0 { "-" } else { "" };
13224 alloc::format!("{sign}{whole}.{frac:0width$}", width = usize::from(scale))
13225}
13226
13227fn rewrite_column_in_source(
13251 src: &str,
13252 old: &str,
13253 new: &str,
13254) -> Result<alloc::string::String, EngineError> {
13255 let mut expr = spg_sql::parser::parse_expression(src).map_err(|e| {
13256 EngineError::Unsupported(alloc::format!(
13257 "ALTER TABLE RENAME COLUMN: stored predicate source {src:?} \
13258 failed to parse for rewrite ({e})"
13259 ))
13260 })?;
13261 rewrite_column_in_expr(&mut expr, old, new);
13262 Ok(alloc::format!("{expr}"))
13263}
13264
13265fn rewrite_column_in_expr(e: &mut Expr, old: &str, new: &str) {
13273 match e {
13274 Expr::Column(c) => {
13275 if c.name.eq_ignore_ascii_case(old) {
13276 c.name = new.to_string();
13277 }
13278 }
13279 Expr::Binary { lhs, rhs, .. } => {
13280 rewrite_column_in_expr(lhs, old, new);
13281 rewrite_column_in_expr(rhs, old, new);
13282 }
13283 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13284 rewrite_column_in_expr(expr, old, new);
13285 }
13286 Expr::FunctionCall { args, .. } => {
13287 for a in args {
13288 rewrite_column_in_expr(a, old, new);
13289 }
13290 }
13291 Expr::Like { expr, pattern, .. } => {
13292 rewrite_column_in_expr(expr, old, new);
13293 rewrite_column_in_expr(pattern, old, new);
13294 }
13295 Expr::Extract { source, .. } => rewrite_column_in_expr(source, old, new),
13296 Expr::WindowFunction {
13297 args,
13298 partition_by,
13299 order_by,
13300 ..
13301 } => {
13302 for a in args {
13303 rewrite_column_in_expr(a, old, new);
13304 }
13305 for p in partition_by {
13306 rewrite_column_in_expr(p, old, new);
13307 }
13308 for (o, _) in order_by {
13309 rewrite_column_in_expr(o, old, new);
13310 }
13311 }
13312 Expr::Array(items) => {
13313 for elem in items {
13314 rewrite_column_in_expr(elem, old, new);
13315 }
13316 }
13317 Expr::ArraySubscript { target, index } => {
13318 rewrite_column_in_expr(target, old, new);
13319 rewrite_column_in_expr(index, old, new);
13320 }
13321 Expr::AnyAll { expr, array, .. } => {
13322 rewrite_column_in_expr(expr, old, new);
13323 rewrite_column_in_expr(array, old, new);
13324 }
13325 Expr::Case {
13326 operand,
13327 branches,
13328 else_branch,
13329 } => {
13330 if let Some(o) = operand {
13331 rewrite_column_in_expr(o, old, new);
13332 }
13333 for (w, t) in branches {
13334 rewrite_column_in_expr(w, old, new);
13335 rewrite_column_in_expr(t, old, new);
13336 }
13337 if let Some(e) = else_branch {
13338 rewrite_column_in_expr(e, old, new);
13339 }
13340 }
13341 Expr::ScalarSubquery(_) | Expr::Exists { .. } | Expr::InSubquery { .. } => {}
13345 Expr::Literal(_) | Expr::Placeholder(_) => {}
13346 }
13347}
13348
13349pub fn substitute_placeholders(stmt: &mut Statement, params: &[Value]) -> Result<(), EngineError> {
13357 match stmt {
13358 Statement::Select(s) => substitute_select(s, params)?,
13359 Statement::Insert(ins) => {
13360 for row in &mut ins.rows {
13361 for e in row {
13362 substitute_expr(e, params)?;
13363 }
13364 }
13365 }
13366 Statement::Update(u) => {
13367 for (_, e) in &mut u.assignments {
13368 substitute_expr(e, params)?;
13369 }
13370 if let Some(w) = &mut u.where_ {
13371 substitute_expr(w, params)?;
13372 }
13373 }
13374 Statement::Delete(d) => {
13375 if let Some(w) = &mut d.where_ {
13376 substitute_expr(w, params)?;
13377 }
13378 }
13379 Statement::Explain(e) => substitute_select(&mut e.inner, params)?,
13380 _ => {}
13383 }
13384 Ok(())
13385}
13386
13387fn substitute_select(s: &mut SelectStatement, params: &[Value]) -> Result<(), EngineError> {
13388 for item in &mut s.items {
13389 if let SelectItem::Expr { expr, .. } = item {
13390 substitute_expr(expr, params)?;
13391 }
13392 }
13393 if let Some(w) = &mut s.where_ {
13394 substitute_expr(w, params)?;
13395 }
13396 if let Some(gs) = &mut s.group_by {
13397 for g in gs {
13398 substitute_expr(g, params)?;
13399 }
13400 }
13401 if let Some(h) = &mut s.having {
13402 substitute_expr(h, params)?;
13403 }
13404 for o in &mut s.order_by {
13405 substitute_expr(&mut o.expr, params)?;
13406 }
13407 for (_, peer) in &mut s.unions {
13408 substitute_select(peer, params)?;
13409 }
13410 if let Some(le) = s.limit {
13415 s.limit = Some(resolve_limit_placeholder(le, params)?);
13416 }
13417 if let Some(le) = s.offset {
13418 s.offset = Some(resolve_limit_placeholder(le, params)?);
13419 }
13420 Ok(())
13421}
13422
13423fn resolve_limit_placeholder(
13424 le: spg_sql::ast::LimitExpr,
13425 params: &[Value],
13426) -> Result<spg_sql::ast::LimitExpr, EngineError> {
13427 use spg_sql::ast::LimitExpr;
13428 match le {
13429 LimitExpr::Literal(_) => Ok(le),
13430 LimitExpr::Placeholder(n) => {
13431 let idx = usize::from(n).saturating_sub(1);
13432 let v = params.get(idx).ok_or_else(|| {
13433 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13434 n,
13435 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13436 })
13437 })?;
13438 let int = match v {
13439 Value::SmallInt(x) => Some(i64::from(*x)),
13440 Value::Int(x) => Some(i64::from(*x)),
13441 Value::BigInt(x) => Some(*x),
13442 _ => None,
13443 }
13444 .ok_or_else(|| {
13445 EngineError::Unsupported(alloc::format!(
13446 "LIMIT/OFFSET ${n} bound to non-integer {v:?}"
13447 ))
13448 })?;
13449 if int < 0 {
13450 return Err(EngineError::Unsupported(alloc::format!(
13451 "LIMIT/OFFSET ${n} bound to negative value {int}"
13452 )));
13453 }
13454 let bounded = u32::try_from(int).map_err(|_| {
13455 EngineError::Unsupported(alloc::format!(
13456 "LIMIT/OFFSET ${n} value {int} exceeds u32 range"
13457 ))
13458 })?;
13459 Ok(LimitExpr::Literal(bounded))
13460 }
13461 }
13462}
13463
13464fn substitute_expr(e: &mut Expr, params: &[Value]) -> Result<(), EngineError> {
13465 if let Expr::Placeholder(n) = e {
13466 let idx = usize::from(*n).saturating_sub(1);
13467 let v = params.get(idx).ok_or_else(|| {
13468 EngineError::Eval(EvalError::PlaceholderOutOfRange {
13469 n: *n,
13470 bound: u16::try_from(params.len()).unwrap_or(u16::MAX),
13471 })
13472 })?;
13473 *e = Expr::Literal(value_to_literal(v.clone()));
13474 return Ok(());
13475 }
13476 match e {
13477 Expr::Binary { lhs, rhs, .. } => {
13478 substitute_expr(lhs, params)?;
13479 substitute_expr(rhs, params)?;
13480 }
13481 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13482 substitute_expr(expr, params)?;
13483 }
13484 Expr::FunctionCall { args, .. } => {
13485 for a in args {
13486 substitute_expr(a, params)?;
13487 }
13488 }
13489 Expr::Like { expr, pattern, .. } => {
13490 substitute_expr(expr, params)?;
13491 substitute_expr(pattern, params)?;
13492 }
13493 Expr::Extract { source, .. } => substitute_expr(source, params)?,
13494 Expr::ScalarSubquery(s) => substitute_select(s, params)?,
13495 Expr::Exists { subquery, .. } => substitute_select(subquery, params)?,
13496 Expr::InSubquery { expr, subquery, .. } => {
13497 substitute_expr(expr, params)?;
13498 substitute_select(subquery, params)?;
13499 }
13500 Expr::WindowFunction {
13501 args,
13502 partition_by,
13503 order_by,
13504 ..
13505 } => {
13506 for a in args {
13507 substitute_expr(a, params)?;
13508 }
13509 for p in partition_by {
13510 substitute_expr(p, params)?;
13511 }
13512 for (e, _) in order_by {
13513 substitute_expr(e, params)?;
13514 }
13515 }
13516 Expr::Literal(_) | Expr::Column(_) => {}
13517 Expr::Placeholder(_) => unreachable!("Placeholder handled at top of fn"),
13519 Expr::Array(items) => {
13520 for elem in items {
13521 substitute_expr(elem, params)?;
13522 }
13523 }
13524 Expr::ArraySubscript { target, index } => {
13525 substitute_expr(target, params)?;
13526 substitute_expr(index, params)?;
13527 }
13528 Expr::AnyAll { expr, array, .. } => {
13529 substitute_expr(expr, params)?;
13530 substitute_expr(array, params)?;
13531 }
13532 Expr::Case {
13533 operand,
13534 branches,
13535 else_branch,
13536 } => {
13537 if let Some(o) = operand {
13538 substitute_expr(o, params)?;
13539 }
13540 for (w, t) in branches {
13541 substitute_expr(w, params)?;
13542 substitute_expr(t, params)?;
13543 }
13544 if let Some(e) = else_branch {
13545 substitute_expr(e, params)?;
13546 }
13547 }
13548 }
13549 Ok(())
13550}
13551
13552fn sort_values_for_histogram(a: &Value, b: &Value) -> core::cmp::Ordering {
13570 use core::cmp::Ordering;
13571 match (a, b) {
13572 (Value::SmallInt(a), Value::SmallInt(b)) => a.cmp(b),
13573 (Value::Int(a), Value::Int(b)) => a.cmp(b),
13574 (Value::BigInt(a), Value::BigInt(b)) => a.cmp(b),
13575 (Value::SmallInt(a), Value::Int(b)) => i32::from(*a).cmp(b),
13576 (Value::Int(a), Value::SmallInt(b)) => a.cmp(&i32::from(*b)),
13577 (Value::Int(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13578 (Value::BigInt(a), Value::Int(b)) => a.cmp(&i64::from(*b)),
13579 (Value::SmallInt(a), Value::BigInt(b)) => i64::from(*a).cmp(b),
13580 (Value::BigInt(a), Value::SmallInt(b)) => a.cmp(&i64::from(*b)),
13581 (Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
13582 (Value::Text(a), Value::Text(b)) | (Value::Json(a), Value::Json(b)) => a.cmp(b),
13583 (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
13584 (Value::Date(a), Value::Date(b)) => a.cmp(b),
13585 (Value::Timestamp(a), Value::Timestamp(b)) => a.cmp(b),
13586 (Value::SmallInt(n), Value::Float(x)) => {
13588 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13589 }
13590 (Value::Float(x), Value::SmallInt(n)) => {
13591 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13592 }
13593 (Value::Int(n), Value::Float(x)) => {
13594 (f64::from(*n)).partial_cmp(x).unwrap_or(Ordering::Equal)
13595 }
13596 (Value::Float(x), Value::Int(n)) => {
13597 x.partial_cmp(&f64::from(*n)).unwrap_or(Ordering::Equal)
13598 }
13599 (Value::BigInt(n), Value::Float(x)) => {
13600 #[allow(clippy::cast_precision_loss)]
13601 let nf = *n as f64;
13602 nf.partial_cmp(x).unwrap_or(Ordering::Equal)
13603 }
13604 (Value::Float(x), Value::BigInt(n)) => {
13605 #[allow(clippy::cast_precision_loss)]
13606 let nf = *n as f64;
13607 x.partial_cmp(&nf).unwrap_or(Ordering::Equal)
13608 }
13609 _ => canonical_value_repr(a).cmp(&canonical_value_repr(b)),
13612 }
13613}
13614
13615fn render_histogram_bounds(bounds: &[alloc::string::String]) -> alloc::string::String {
13622 let mut out = alloc::string::String::with_capacity(bounds.len() * 8 + 2);
13623 out.push('[');
13624 for (i, b) in bounds.iter().enumerate() {
13625 if i > 0 {
13626 out.push_str(", ");
13627 }
13628 let needs_quote = b.contains([',', '[', ']', '"']) || b.is_empty();
13629 if needs_quote {
13630 out.push('"');
13631 for ch in b.chars() {
13632 if ch == '"' || ch == '\\' {
13633 out.push('\\');
13634 }
13635 out.push(ch);
13636 }
13637 out.push('"');
13638 } else {
13639 out.push_str(b);
13640 }
13641 }
13642 out.push(']');
13643 out
13644}
13645
13646pub(crate) fn canonical_value_repr(v: &Value) -> alloc::string::String {
13656 match v {
13657 Value::Null => "NULL".to_string(),
13658 Value::SmallInt(n) => alloc::format!("{n}"),
13659 Value::Int(n) => alloc::format!("{n}"),
13660 Value::BigInt(n) => alloc::format!("{n}"),
13661 Value::Float(x) => alloc::format!("{x:?}"),
13662 Value::Text(s) | Value::Json(s) => s.clone(),
13663 Value::Bool(b) => if *b { "t" } else { "f" }.to_string(),
13664 Value::Date(d) => eval::format_date(*d),
13665 Value::Timestamp(t) => eval::format_timestamp(*t),
13666 Value::Time(us) => eval::format_time(*us),
13668 Value::Year(y) => alloc::format!("{y:04}"),
13670 Value::TimeTz { us, offset_secs } => eval::format_timetz(*us, *offset_secs),
13672 Value::Money(c) => eval::format_money(*c),
13674 v @ Value::Range { .. } => format_range_str(v),
13676 Value::Hstore(pairs) => format_hstore_str(pairs),
13678 Value::IntArray2D(rows) => format_int_2d_text(rows),
13680 Value::BigIntArray2D(rows) => format_bigint_2d_text(rows),
13681 Value::TextArray2D(rows) => format_text_2d_text(rows),
13682 Value::Interval { months, micros } => eval::format_interval(*months, *micros),
13683 Value::Numeric { scaled, scale } => eval::format_numeric(*scaled, *scale),
13684 Value::Vector(_) | Value::Sq8Vector(_) | Value::HalfVector(_) => {
13685 alloc::format!("{v:?}")
13689 }
13690 _ => alloc::format!("{v:?}"),
13694 }
13695}
13696
13697const fn is_internal_table_name(_name: &str) -> bool {
13704 false
13705}
13706
13707fn value_to_literal(v: Value) -> Literal {
13708 match v {
13709 Value::Null => Literal::Null,
13710 Value::SmallInt(n) => Literal::Integer(i64::from(n)),
13711 Value::Int(n) => Literal::Integer(i64::from(n)),
13712 Value::BigInt(n) => Literal::Integer(n),
13713 Value::Float(x) => Literal::Float(x),
13714 Value::Text(s) | Value::Json(s) => Literal::String(s),
13715 Value::Bool(b) => Literal::Bool(b),
13716 Value::Vector(v) => Literal::Vector(v),
13717 Value::Numeric { scaled, scale } => Literal::String(eval::format_numeric(scaled, scale)),
13718 Value::Date(d) => Literal::String(eval::format_date(d)),
13719 Value::Timestamp(t) => Literal::String(eval::format_timestamp(t)),
13720 Value::Uuid(b) => Literal::String(spg_storage::format_uuid(&b)),
13726 Value::Bytes(b) => Literal::String(eval::format_bytea_hex(&b)),
13731 Value::TextArray(items) => Literal::String(eval::format_text_array(&items)),
13736 Value::IntArray(items) => Literal::String(eval::format_int_array(&items)),
13737 Value::BigIntArray(items) => Literal::String(eval::format_bigint_array(&items)),
13738 Value::Interval { months, micros } => Literal::Interval {
13739 months,
13740 micros,
13741 text: eval::format_interval(months, micros),
13742 },
13743 Value::Sq8Vector(q) => Literal::Vector(spg_storage::quantize::dequantize(&q)),
13746 Value::HalfVector(h) => Literal::Vector(h.to_f32_vec()),
13747 v => Literal::String(alloc::format!("{v:?}")),
13751 }
13752}
13753
13754fn rewrite_clock_calls(stmt: &mut Statement, now_micros: Option<i64>) {
13755 let Some(now) = now_micros else {
13756 return;
13757 };
13758 match stmt {
13759 Statement::Select(s) => rewrite_select_clock(s, now),
13760 Statement::Insert(ins) => {
13761 for row in &mut ins.rows {
13762 for e in row {
13763 rewrite_expr_clock(e, now);
13764 }
13765 }
13766 }
13767 _ => {}
13768 }
13769}
13770
13771fn rewrite_select_clock(s: &mut SelectStatement, now: i64) {
13772 for item in &mut s.items {
13773 if let SelectItem::Expr { expr, .. } = item {
13774 rewrite_expr_clock(expr, now);
13775 }
13776 }
13777 if let Some(w) = &mut s.where_ {
13778 rewrite_expr_clock(w, now);
13779 }
13780 if let Some(gs) = &mut s.group_by {
13781 for g in gs {
13782 rewrite_expr_clock(g, now);
13783 }
13784 }
13785 if let Some(h) = &mut s.having {
13786 rewrite_expr_clock(h, now);
13787 }
13788 for o in &mut s.order_by {
13789 rewrite_expr_clock(&mut o.expr, now);
13790 }
13791 for (_, peer) in &mut s.unions {
13792 rewrite_select_clock(peer, now);
13793 }
13794}
13795
13796fn rewrite_expr_clock(e: &mut Expr, now: i64) {
13804 if let Some(replacement) = clock_replacement_for(e, now) {
13808 *e = replacement;
13809 return;
13810 }
13811 match e {
13812 Expr::Binary { lhs, rhs, .. } => {
13813 rewrite_expr_clock(lhs, now);
13814 rewrite_expr_clock(rhs, now);
13815 }
13816 Expr::Unary { expr, .. } | Expr::Cast { expr, .. } | Expr::IsNull { expr, .. } => {
13817 rewrite_expr_clock(expr, now);
13818 }
13819 Expr::FunctionCall { args, .. } => {
13820 for a in args {
13821 rewrite_expr_clock(a, now);
13822 }
13823 }
13824 Expr::Like { expr, pattern, .. } => {
13825 rewrite_expr_clock(expr, now);
13826 rewrite_expr_clock(pattern, now);
13827 }
13828 Expr::Extract { source, .. } => rewrite_expr_clock(source, now),
13829 Expr::ScalarSubquery(s) => rewrite_select_clock(s, now),
13833 Expr::Exists { subquery, .. } => rewrite_select_clock(subquery, now),
13834 Expr::InSubquery { expr, subquery, .. } => {
13835 rewrite_expr_clock(expr, now);
13836 rewrite_select_clock(subquery, now);
13837 }
13838 Expr::WindowFunction {
13841 args,
13842 partition_by,
13843 order_by,
13844 ..
13845 } => {
13846 for a in args {
13847 rewrite_expr_clock(a, now);
13848 }
13849 for p in partition_by {
13850 rewrite_expr_clock(p, now);
13851 }
13852 for (e, _) in order_by {
13853 rewrite_expr_clock(e, now);
13854 }
13855 }
13856 Expr::Literal(_) | Expr::Placeholder(_) | Expr::Column(_) => {}
13857 Expr::Array(items) => {
13858 for elem in items {
13859 rewrite_expr_clock(elem, now);
13860 }
13861 }
13862 Expr::ArraySubscript { target, index } => {
13863 rewrite_expr_clock(target, now);
13864 rewrite_expr_clock(index, now);
13865 }
13866 Expr::AnyAll { expr, array, .. } => {
13867 rewrite_expr_clock(expr, now);
13868 rewrite_expr_clock(array, now);
13869 }
13870 Expr::Case {
13871 operand,
13872 branches,
13873 else_branch,
13874 } => {
13875 if let Some(o) = operand {
13876 rewrite_expr_clock(o, now);
13877 }
13878 for (w, t) in branches {
13879 rewrite_expr_clock(w, now);
13880 rewrite_expr_clock(t, now);
13881 }
13882 if let Some(e) = else_branch {
13883 rewrite_expr_clock(e, now);
13884 }
13885 }
13886 }
13887}
13888
13889fn clock_replacement_for(e: &Expr, now: i64) -> Option<Expr> {
13896 let (kind, name) = match e {
13897 Expr::FunctionCall { name, args } if args.is_empty() => (ClockSite::Fn, name.as_str()),
13898 Expr::Column(c) if c.qualifier.is_none() => (ClockSite::BareIdent, c.name.as_str()),
13899 _ => return None,
13900 };
13901 enum ClockShape {
13909 Timestamp,
13910 Date,
13911 UnixSeconds,
13912 }
13913 let shape = match name.len() {
13914 3 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("now") => {
13915 Some(ClockShape::Timestamp)
13916 }
13917 12 if name.eq_ignore_ascii_case("current_date") => Some(ClockShape::Date),
13918 14 if kind == ClockSite::Fn && name.eq_ignore_ascii_case("unix_timestamp") => {
13919 Some(ClockShape::UnixSeconds)
13920 }
13921 17 if name.eq_ignore_ascii_case("current_timestamp") => Some(ClockShape::Timestamp),
13922 _ => None,
13923 };
13924 let shape = shape?;
13925 let payload = match shape {
13926 ClockShape::Timestamp => now,
13927 ClockShape::Date => now.div_euclid(86_400_000_000),
13928 ClockShape::UnixSeconds => now.div_euclid(1_000_000),
13929 };
13930 let target = match shape {
13931 ClockShape::Timestamp => spg_sql::ast::CastTarget::Timestamp,
13932 ClockShape::Date => spg_sql::ast::CastTarget::Date,
13933 ClockShape::UnixSeconds => spg_sql::ast::CastTarget::BigInt,
13934 };
13935 Some(Expr::Cast {
13936 expr: alloc::boxed::Box::new(Expr::Literal(spg_sql::ast::Literal::Integer(payload))),
13937 target,
13938 })
13939}
13940
13941#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13942enum ClockSite {
13943 Fn,
13944 BareIdent,
13945}
13946
13947fn expand_group_by_all(s: &mut SelectStatement) {
13958 if !s.group_by_all {
13959 for (_, peer) in &mut s.unions {
13960 expand_group_by_all(peer);
13961 }
13962 return;
13963 }
13964 let mut groups: Vec<Expr> = Vec::new();
13965 for item in &s.items {
13966 if let SelectItem::Expr { expr, .. } = item
13967 && !aggregate::contains_aggregate(expr)
13968 {
13969 groups.push(expr.clone());
13970 }
13971 }
13972 s.group_by = Some(groups);
13973 s.group_by_all = false;
13974 for (_, peer) in &mut s.unions {
13975 expand_group_by_all(peer);
13976 }
13977}
13978
13979fn resolve_order_by_position(s: &mut SelectStatement) {
13980 for order in &mut s.order_by {
13985 match &order.expr {
13986 Expr::Literal(Literal::Integer(n)) if *n >= 1 => {
13987 if let Ok(idx_one_based) = usize::try_from(*n) {
13988 let idx = idx_one_based - 1;
13989 if idx < s.items.len()
13990 && let SelectItem::Expr { expr, .. } = &s.items[idx]
13991 {
13992 order.expr = expr.clone();
13993 }
13994 }
13995 }
13996 Expr::Column(c) if c.qualifier.is_none() => {
13997 for item in &s.items {
13999 if let SelectItem::Expr {
14000 expr,
14001 alias: Some(a),
14002 } = item
14003 && a == &c.name
14004 {
14005 order.expr = expr.clone();
14006 break;
14007 }
14008 }
14009 }
14010 _ => {}
14011 }
14012 }
14013 for (_, peer) in &mut s.unions {
14014 resolve_order_by_position(peer);
14015 }
14016}
14017
14018fn partial_sort_tagged(tagged: &mut Vec<(Vec<f64>, Row)>, keep: Option<usize>, descs: &[bool]) {
14031 let cmp = |a: &(Vec<f64>, Row), b: &(Vec<f64>, Row)| cmp_multi_key(&a.0, &b.0, descs);
14032 match keep {
14033 Some(k) if k < tagged.len() && k > 0 => {
14034 let pivot = k - 1;
14035 tagged.select_nth_unstable_by(pivot, cmp);
14036 tagged[..k].sort_by(cmp);
14037 tagged.truncate(k);
14038 }
14039 _ => {
14040 tagged.sort_by(cmp);
14041 }
14042 }
14043}
14044
14045fn sort_by_keys(tagged: &mut [(Vec<f64>, Row)], descs: &[bool]) {
14046 tagged.sort_by(|a, b| cmp_multi_key(&a.0, &b.0, descs));
14047}
14048
14049fn cmp_multi_key(a: &[f64], b: &[f64], descs: &[bool]) -> core::cmp::Ordering {
14053 use core::cmp::Ordering;
14054 for (i, (ka, kb)) in a.iter().zip(b.iter()).enumerate() {
14055 let ord = ka.partial_cmp(kb).unwrap_or(Ordering::Equal);
14056 let ord = if descs.get(i).copied().unwrap_or(false) {
14057 ord.reverse()
14058 } else {
14059 ord
14060 };
14061 if ord != Ordering::Equal {
14062 return ord;
14063 }
14064 }
14065 Ordering::Equal
14066}
14067
14068fn build_order_keys(
14071 order_by: &[OrderBy],
14072 row: &Row,
14073 ctx: &EvalContext,
14074) -> Result<Vec<f64>, EngineError> {
14075 let mut keys = Vec::with_capacity(order_by.len());
14076 for o in order_by {
14077 let v = eval::eval_expr(&o.expr, row, ctx)?;
14078 keys.push(value_to_order_key(&v)?);
14079 }
14080 Ok(keys)
14081}
14082
14083fn apply_offset_and_limit(rows: &mut Vec<Row>, offset: Option<u32>, limit: Option<u32>) {
14087 if let Some(off) = offset {
14088 let off = off as usize;
14089 if off >= rows.len() {
14090 rows.clear();
14091 } else {
14092 rows.drain(..off);
14093 }
14094 }
14095 if let Some(n) = limit {
14096 rows.truncate(n as usize);
14097 }
14098}
14099
14100fn apply_offset_and_limit_tagged(
14111 tagged: &mut Vec<(Vec<f64>, Row)>,
14112 offset: Option<u32>,
14113 limit: Option<u32>,
14114 with_ties: bool,
14115) {
14116 if let Some(off) = offset {
14117 let off = off as usize;
14118 if off >= tagged.len() {
14119 tagged.clear();
14120 } else {
14121 tagged.drain(..off);
14122 }
14123 }
14124 if let Some(n) = limit {
14125 let n = n as usize;
14126 if with_ties && n > 0 && n < tagged.len() {
14127 let cutoff_key = tagged[n - 1].0.clone();
14128 let mut end = n;
14129 while end < tagged.len() && tagged[end].0 == cutoff_key {
14130 end += 1;
14131 }
14132 tagged.truncate(end);
14133 } else {
14134 tagged.truncate(n);
14135 }
14136 }
14137}
14138
14139fn check_with_ties_requires_order_by(stmt: &SelectStatement) -> Result<(), EngineError> {
14145 if stmt.limit_with_ties && stmt.order_by.is_empty() {
14146 return Err(EngineError::Unsupported(alloc::string::String::from(
14147 "FETCH FIRST … ROWS WITH TIES requires an ORDER BY clause",
14148 )));
14149 }
14150 Ok(())
14151}
14152
14153fn resolve_foreign_key(
14167 local_table_name: &str,
14168 local_cols: &[ColumnSchema],
14169 fk: spg_sql::ast::ForeignKeyConstraint,
14170 catalog: &Catalog,
14171) -> Result<spg_storage::ForeignKeyConstraint, EngineError> {
14172 let mut local_columns = Vec::with_capacity(fk.columns.len());
14174 for name in &fk.columns {
14175 let pos = local_cols
14176 .iter()
14177 .position(|c| c.name == *name)
14178 .ok_or_else(|| {
14179 EngineError::Unsupported(alloc::format!(
14180 "FOREIGN KEY references unknown local column {name:?}"
14181 ))
14182 })?;
14183 local_columns.push(pos);
14184 }
14185 let is_self_ref = fk.parent_table == local_table_name;
14189 let (parent_cols_for_lookup, parent_table_str): (&[ColumnSchema], &str) = if is_self_ref {
14190 (local_cols, local_table_name)
14191 } else {
14192 let parent_table = catalog.get(&fk.parent_table).ok_or_else(|| {
14193 EngineError::Storage(StorageError::TableNotFound {
14194 name: fk.parent_table.clone(),
14195 })
14196 })?;
14197 (
14198 parent_table.schema().columns.as_slice(),
14199 fk.parent_table.as_str(),
14200 )
14201 };
14202 let parent_columns: Vec<usize> = if fk.parent_columns.is_empty() {
14207 if fk.columns.len() != 1 {
14208 return Err(EngineError::Unsupported(
14209 "composite FOREIGN KEY without explicit parent column list is not supported \
14210 — list the parent columns explicitly"
14211 .into(),
14212 ));
14213 }
14214 let pos = pick_pk_index_column(catalog, parent_table_str, is_self_ref, local_cols)
14216 .ok_or_else(|| {
14217 EngineError::Unsupported(alloc::format!(
14218 "parent table {parent_table_str:?} has no PRIMARY-key / UNIQUE BTree index \
14219 to default the FOREIGN KEY against"
14220 ))
14221 })?;
14222 alloc::vec![pos]
14223 } else {
14224 let mut out = Vec::with_capacity(fk.parent_columns.len());
14225 for name in &fk.parent_columns {
14226 let pos = parent_cols_for_lookup
14227 .iter()
14228 .position(|c| c.name == *name)
14229 .ok_or_else(|| {
14230 EngineError::Unsupported(alloc::format!(
14231 "FOREIGN KEY references unknown parent column \
14232 {name:?} on table {parent_table_str:?}"
14233 ))
14234 })?;
14235 out.push(pos);
14236 }
14237 out
14238 };
14239 if parent_columns.len() != local_columns.len() {
14240 return Err(EngineError::Unsupported(alloc::format!(
14241 "FOREIGN KEY arity mismatch: {} local columns vs {} parent columns",
14242 local_columns.len(),
14243 parent_columns.len()
14244 )));
14245 }
14246 if !is_self_ref {
14256 let parent_table = catalog.get(&fk.parent_table).expect("checked above");
14257 let primary_parent_col = parent_columns[0];
14258 let has_btree = parent_table
14259 .schema()
14260 .columns
14261 .get(primary_parent_col)
14262 .is_some()
14263 && parent_table.indices().iter().any(|idx| {
14264 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14265 && idx.column_position == primary_parent_col
14266 && idx.partial_predicate.is_none()
14267 });
14268 if !has_btree {
14269 return Err(EngineError::Unsupported(alloc::format!(
14270 "FOREIGN KEY parent column on {:?} is not covered by an unconditional BTree \
14271 index — create one with `CREATE INDEX ... ON {} ({})` first",
14272 parent_table_str,
14273 parent_table_str,
14274 parent_table.schema().columns[primary_parent_col].name,
14275 )));
14276 }
14277 }
14278 let on_delete = fk_action_sql_to_storage(fk.on_delete);
14279 let on_update = fk_action_sql_to_storage(fk.on_update);
14280 Ok(spg_storage::ForeignKeyConstraint {
14281 name: fk.name,
14282 local_columns,
14283 parent_table: fk.parent_table,
14284 parent_columns,
14285 on_delete,
14286 on_update,
14287 })
14288}
14289
14290fn pick_pk_index_column(
14296 catalog: &Catalog,
14297 parent_name: &str,
14298 is_self_ref: bool,
14299 local_cols: &[ColumnSchema],
14300) -> Option<usize> {
14301 if is_self_ref {
14302 let _ = local_cols;
14306 return Some(0);
14307 }
14308 let parent = catalog.get(parent_name)?;
14309 parent.indices().iter().find_map(|idx| {
14310 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14311 && idx.partial_predicate.is_none()
14312 && idx.included_columns.is_empty()
14313 && idx.expression.is_none()
14314 {
14315 Some(idx.column_position)
14316 } else {
14317 None
14318 }
14319 })
14320}
14321
14322fn resolve_on_conflict_columns(
14329 catalog: &Catalog,
14330 table_name: &str,
14331 target: &[String],
14332) -> Result<Vec<usize>, EngineError> {
14333 let table = catalog.get(table_name).ok_or_else(|| {
14334 EngineError::Storage(StorageError::TableNotFound {
14335 name: table_name.into(),
14336 })
14337 })?;
14338 if target.is_empty() {
14339 if let Some(uc) = table.schema().uniqueness_constraints.first() {
14349 return Ok(uc.columns.clone());
14350 }
14351 let pos = table
14352 .indices()
14353 .iter()
14354 .find_map(|idx| {
14355 if matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14356 && idx.partial_predicate.is_none()
14357 && idx.included_columns.is_empty()
14358 && idx.expression.is_none()
14359 {
14360 Some(idx.column_position)
14361 } else {
14362 None
14363 }
14364 })
14365 .ok_or_else(|| {
14366 EngineError::Unsupported(alloc::format!(
14367 "ON CONFLICT without target requires a UNIQUE BTree index on {table_name:?}"
14368 ))
14369 })?;
14370 return Ok(alloc::vec![pos]);
14371 }
14372 let mut out = Vec::with_capacity(target.len());
14373 for name in target {
14374 let pos = table
14375 .schema()
14376 .columns
14377 .iter()
14378 .position(|c| c.name == *name)
14379 .ok_or_else(|| {
14380 EngineError::Unsupported(alloc::format!(
14381 "ON CONFLICT target column {name:?} not found on {table_name:?}"
14382 ))
14383 })?;
14384 out.push(pos);
14385 }
14386 Ok(out)
14387}
14388
14389fn on_conflict_key_exists(
14392 catalog: &Catalog,
14393 table_name: &str,
14394 column_pos: usize,
14395 key: &Value,
14396) -> bool {
14397 let Some(table) = catalog.get(table_name) else {
14398 return false;
14399 };
14400 let Some(idx_key) = spg_storage::IndexKey::from_value(key) else {
14401 return false;
14402 };
14403 table.indices().iter().any(|idx| {
14404 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
14405 && idx.column_position == column_pos
14406 && idx.partial_predicate.is_none()
14407 && !idx.lookup_eq(&idx_key).is_empty()
14408 })
14409}
14410
14411fn lookup_row_position_by_keys(
14417 catalog: &Catalog,
14418 table_name: &str,
14419 column_positions: &[usize],
14420 key: &[&Value],
14421) -> Option<usize> {
14422 let table = catalog.get(table_name)?;
14423 table.rows().iter().position(|r| {
14424 column_positions
14425 .iter()
14426 .enumerate()
14427 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14428 })
14429}
14430
14431fn on_conflict_keys_exist(
14436 catalog: &Catalog,
14437 table_name: &str,
14438 column_positions: &[usize],
14439 key: &[&Value],
14440) -> bool {
14441 if column_positions.len() == 1 {
14442 return on_conflict_key_exists(catalog, table_name, column_positions[0], key[0]);
14443 }
14444 let Some(table) = catalog.get(table_name) else {
14445 return false;
14446 };
14447 table.rows().iter().any(|r| {
14448 column_positions
14449 .iter()
14450 .enumerate()
14451 .all(|(i, &pos)| r.values.get(pos) == Some(key[i]))
14452 })
14453}
14454
14455fn apply_on_conflict_assignments(
14468 catalog: &Catalog,
14469 table_name: &str,
14470 target_pos: usize,
14471 incoming: &[Value],
14472 assignments: &[(String, Expr)],
14473 where_: Option<&Expr>,
14474) -> Result<Option<Vec<Value>>, EngineError> {
14475 let table = catalog.get(table_name).ok_or_else(|| {
14476 EngineError::Storage(StorageError::TableNotFound {
14477 name: table_name.into(),
14478 })
14479 })?;
14480 let schema_cols = table.schema().columns.clone();
14481 let existing = table
14482 .rows()
14483 .get(target_pos)
14484 .ok_or_else(|| {
14485 EngineError::Unsupported(alloc::format!(
14486 "ON CONFLICT DO UPDATE: row position {target_pos} out of bounds on {table_name:?}"
14487 ))
14488 })?
14489 .clone();
14490 let ctx = eval::EvalContext::new(&schema_cols, Some(table_name));
14491 if let Some(w) = where_ {
14493 let pred = w.clone();
14494 let pred = substitute_excluded_refs(pred, &schema_cols, incoming);
14495 let v = eval::eval_expr(&pred, &existing, &ctx)?;
14496 if !matches!(v, Value::Bool(true)) {
14497 return Ok(None);
14498 }
14499 }
14500 let mut new_values = existing.values.clone();
14501 for (col_name, expr) in assignments {
14502 let target_idx = schema_cols
14503 .iter()
14504 .position(|c| c.name == *col_name)
14505 .ok_or_else(|| {
14506 EngineError::Eval(EvalError::ColumnNotFound {
14507 name: col_name.clone(),
14508 })
14509 })?;
14510 let sub = substitute_excluded_refs(expr.clone(), &schema_cols, incoming);
14511 let v = eval::eval_expr(&sub, &existing, &ctx)?;
14512 let coerced = coerce_value(v, schema_cols[target_idx].ty, col_name, target_idx)?;
14513 check_unsigned_range(&coerced, &schema_cols[target_idx], target_idx)?;
14514 new_values[target_idx] = coerced;
14515 }
14516 Ok(Some(new_values))
14517}
14518
14519fn substitute_excluded_refs(expr: Expr, schema_cols: &[ColumnSchema], incoming: &[Value]) -> Expr {
14524 use spg_sql::ast::ColumnName;
14525 match expr {
14526 Expr::Column(ColumnName { qualifier, name })
14527 if qualifier
14528 .as_deref()
14529 .is_some_and(|q| q.eq_ignore_ascii_case("excluded")) =>
14530 {
14531 let pos = schema_cols.iter().position(|c| c.name == name);
14532 match pos {
14533 Some(p) => {
14534 let v = incoming.get(p).cloned().unwrap_or(Value::Null);
14535 value_to_literal_expr(v)
14536 .unwrap_or_else(|_| Expr::Literal(spg_sql::ast::Literal::Null))
14537 }
14538 None => Expr::Column(ColumnName { qualifier, name }),
14539 }
14540 }
14541 Expr::Binary { op, lhs, rhs } => Expr::Binary {
14542 op,
14543 lhs: Box::new(substitute_excluded_refs(*lhs, schema_cols, incoming)),
14544 rhs: Box::new(substitute_excluded_refs(*rhs, schema_cols, incoming)),
14545 },
14546 Expr::Unary { op, expr } => Expr::Unary {
14547 op,
14548 expr: Box::new(substitute_excluded_refs(*expr, schema_cols, incoming)),
14549 },
14550 Expr::FunctionCall { name, args } => Expr::FunctionCall {
14551 name,
14552 args: args
14553 .into_iter()
14554 .map(|a| substitute_excluded_refs(a, schema_cols, incoming))
14555 .collect(),
14556 },
14557 other => other,
14558 }
14559}
14560
14561fn enforce_uniqueness_inserts(
14584 catalog: &Catalog,
14585 child_table: &str,
14586 constraints: &[spg_storage::UniquenessConstraint],
14587 rows: &[Vec<Value>],
14588) -> Result<(), EngineError> {
14589 if constraints.is_empty() {
14590 return Ok(());
14591 }
14592 let table = catalog.get(child_table).ok_or_else(|| {
14593 EngineError::Storage(StorageError::TableNotFound {
14594 name: child_table.into(),
14595 })
14596 })?;
14597 let schema = table.schema();
14598 for uc in constraints {
14599 for (batch_idx, row_values) in rows.iter().enumerate() {
14600 let key: Vec<Value> = uc
14609 .columns
14610 .iter()
14611 .map(|&i| collated_key_cell(&row_values[i], i, schema))
14612 .collect();
14613 let has_null = key.iter().any(|v| matches!(v, Value::Null));
14614 if has_null && !uc.nulls_not_distinct {
14619 continue;
14620 }
14621 let collides_in_table = table.rows().iter().any(|prow| {
14623 uc.columns.iter().enumerate().all(|(i, &p)| {
14624 prow.values
14625 .get(p)
14626 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14627 })
14628 });
14629 let collides_in_batch = rows[..batch_idx].iter().any(|earlier| {
14631 uc.columns.iter().enumerate().all(|(i, &p)| {
14632 earlier
14633 .get(p)
14634 .is_some_and(|v| collated_key_cell(v, p, schema) == key[i])
14635 })
14636 });
14637 if collides_in_table || collides_in_batch {
14638 let kind = if uc.is_primary_key {
14639 "PRIMARY KEY"
14640 } else {
14641 "UNIQUE"
14642 };
14643 let col_names: Vec<String> = uc
14644 .columns
14645 .iter()
14646 .map(|&i| table.schema().columns[i].name.clone())
14647 .collect();
14648 return Err(EngineError::Unsupported(alloc::format!(
14649 "{kind} violation on {child_table:?} columns {col_names:?}: \
14650 row #{batch_idx} duplicates an existing key"
14651 )));
14652 }
14653 }
14654 }
14655 Ok(())
14656}
14657
14658fn collated_key_cell(
14665 v: &spg_storage::Value,
14666 column_position: usize,
14667 schema: &spg_storage::TableSchema,
14668) -> spg_storage::Value {
14669 match (v, schema.columns.get(column_position).map(|c| c.collation)) {
14670 (spg_storage::Value::Text(s), Some(spg_storage::Collation::CaseInsensitive)) => {
14671 spg_storage::Value::Text(s.to_ascii_lowercase())
14672 }
14673 _ => v.clone(),
14674 }
14675}
14676
14677fn predicate_truthy(v: &spg_storage::Value) -> bool {
14685 use spg_storage::Value as V;
14686 match v {
14687 V::Bool(b) => *b,
14688 V::Int(n) => *n != 0,
14689 V::BigInt(n) => *n != 0,
14690 V::SmallInt(n) => *n != 0,
14691 _ => false,
14692 }
14693}
14694
14695fn check_existing_unique_violation(
14700 idx: &spg_storage::Index,
14701 schema: &spg_storage::TableSchema,
14702 rows: &[spg_storage::Row],
14703) -> Result<(), EngineError> {
14704 let predicate_expr = match idx.partial_predicate.as_deref() {
14705 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14706 EngineError::Unsupported(alloc::format!(
14707 "stored partial predicate {s:?} failed to re-parse: {e:?}"
14708 ))
14709 })?),
14710 None => None,
14711 };
14712 let ctx = eval::EvalContext::new(&schema.columns, None);
14713 let key_positions = unique_key_positions(idx);
14714 let mut seen: alloc::vec::Vec<alloc::vec::Vec<spg_storage::Value>> = alloc::vec::Vec::new();
14715 for row in rows {
14716 if let Some(expr) = &predicate_expr {
14717 let v = eval::eval_expr(expr, row, &ctx).map_err(|e| {
14718 EngineError::Unsupported(alloc::format!(
14719 "evaluating UNIQUE INDEX predicate against existing row: {e:?}"
14720 ))
14721 })?;
14722 if !predicate_truthy(&v) {
14723 continue;
14724 }
14725 }
14726 let key: alloc::vec::Vec<spg_storage::Value> = key_positions
14727 .iter()
14728 .map(|&p| {
14729 let v = row
14730 .values
14731 .get(p)
14732 .cloned()
14733 .unwrap_or(spg_storage::Value::Null);
14734 collated_key_cell(&v, p, schema)
14735 })
14736 .collect();
14737 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14738 continue;
14739 }
14740 if seen.iter().any(|other| *other == key) {
14741 return Err(EngineError::Unsupported(alloc::format!(
14742 "CREATE UNIQUE INDEX {:?}: existing rows already violate the constraint",
14743 idx.name
14744 )));
14745 }
14746 seen.push(key);
14747 }
14748 Ok(())
14749}
14750
14751fn unique_key_positions(idx: &spg_storage::Index) -> alloc::vec::Vec<usize> {
14755 let mut out = alloc::vec::Vec::with_capacity(1 + idx.extra_column_positions.len());
14756 out.push(idx.column_position);
14757 out.extend_from_slice(&idx.extra_column_positions);
14758 out
14759}
14760
14761fn enforce_unique_index_inserts(
14769 catalog: &Catalog,
14770 table_name: &str,
14771 rows: &[alloc::vec::Vec<spg_storage::Value>],
14772) -> Result<(), EngineError> {
14773 let table = catalog.get(table_name).ok_or_else(|| {
14774 EngineError::Storage(StorageError::TableNotFound {
14775 name: table_name.into(),
14776 })
14777 })?;
14778 let schema = table.schema();
14779 let ctx = eval::EvalContext::new(&schema.columns, None);
14780 for idx in table.indices() {
14781 if !idx.is_unique {
14782 continue;
14783 }
14784 let predicate_expr = match idx.partial_predicate.as_deref() {
14786 Some(s) => Some(spg_sql::parser::parse_expression(s).map_err(|e| {
14787 EngineError::Unsupported(alloc::format!(
14788 "UNIQUE INDEX {:?} predicate {s:?} failed to re-parse: {e:?}",
14789 idx.name
14790 ))
14791 })?),
14792 None => None,
14793 };
14794 let key_positions = unique_key_positions(idx);
14795 let key_of = |values: &[spg_storage::Value]| -> alloc::vec::Vec<spg_storage::Value> {
14796 key_positions
14800 .iter()
14801 .map(|&p| {
14802 let v = values.get(p).cloned().unwrap_or(spg_storage::Value::Null);
14803 collated_key_cell(&v, p, schema)
14804 })
14805 .collect()
14806 };
14807 let participates = |values: &[spg_storage::Value]| -> Result<bool, EngineError> {
14811 let Some(expr) = &predicate_expr else {
14812 return Ok(true);
14813 };
14814 let tmp_row = spg_storage::Row {
14815 values: values.to_vec(),
14816 };
14817 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14818 EngineError::Unsupported(alloc::format!(
14819 "UNIQUE INDEX {:?} predicate eval: {e:?}",
14820 idx.name
14821 ))
14822 })?;
14823 Ok(predicate_truthy(&v))
14824 };
14825 for (batch_idx, row_values) in rows.iter().enumerate() {
14826 if !participates(row_values)? {
14827 continue;
14828 }
14829 let key = key_of(row_values);
14830 if key.iter().any(|v| matches!(v, spg_storage::Value::Null)) {
14831 continue;
14832 }
14833 for prow in table.rows() {
14835 if !participates(&prow.values)? {
14836 continue;
14837 }
14838 if key_of(&prow.values) == key {
14839 return Err(EngineError::Unsupported(alloc::format!(
14840 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14841 row #{batch_idx} duplicates an existing key",
14842 idx.name
14843 )));
14844 }
14845 }
14846 for earlier in &rows[..batch_idx] {
14848 if !participates(earlier)? {
14849 continue;
14850 }
14851 if key_of(earlier) == key {
14852 return Err(EngineError::Unsupported(alloc::format!(
14853 "UNIQUE INDEX {:?} violation on {table_name:?}: \
14854 row #{batch_idx} duplicates an earlier row in the same batch",
14855 idx.name
14856 )));
14857 }
14858 }
14859 }
14860 }
14861 Ok(())
14862}
14863
14864fn any_column_changed(
14872 filter_cols: &[String],
14873 schema_cols: &[ColumnSchema],
14874 old_row: &Row,
14875 new_row: &Row,
14876) -> bool {
14877 for col_name in filter_cols {
14878 let Some(pos) = schema_cols
14879 .iter()
14880 .position(|c| c.name.eq_ignore_ascii_case(col_name))
14881 else {
14882 continue;
14883 };
14884 let old_v = old_row.values.get(pos);
14885 let new_v = new_row.values.get(pos);
14886 if old_v != new_v {
14887 return true;
14888 }
14889 }
14890 false
14891}
14892
14893fn enforce_check_constraints(
14898 catalog: &Catalog,
14899 table_name: &str,
14900 rows: &[alloc::vec::Vec<spg_storage::Value>],
14901) -> Result<(), EngineError> {
14902 let table = catalog.get(table_name).ok_or_else(|| {
14903 EngineError::Storage(StorageError::TableNotFound {
14904 name: table_name.into(),
14905 })
14906 })?;
14907 let schema = table.schema();
14908 let mut domain_checks_per_col: alloc::vec::Vec<(usize, alloc::vec::Vec<Expr>)> =
14912 alloc::vec::Vec::new();
14913 for (idx, col) in schema.columns.iter().enumerate() {
14914 let Some(dname) = &col.user_domain_type else {
14915 continue;
14916 };
14917 let Some(dom) = catalog.domain_types().get(dname) else {
14918 continue;
14919 };
14920 let mut parsed_for_col: alloc::vec::Vec<Expr> =
14921 alloc::vec::Vec::with_capacity(dom.checks.len());
14922 for src in &dom.checks {
14923 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14924 EngineError::Unsupported(alloc::format!(
14925 "DOMAIN {dname:?} CHECK ({src:?}) on column {:?}: re-parse failed: {e:?}",
14926 col.name
14927 ))
14928 })?;
14929 parsed_for_col.push(expr);
14930 }
14931 if !parsed_for_col.is_empty() {
14932 domain_checks_per_col.push((idx, parsed_for_col));
14933 }
14934 }
14935 if schema.checks.is_empty() && domain_checks_per_col.is_empty() {
14936 return Ok(());
14937 }
14938 let ctx = eval::EvalContext::new(&schema.columns, None);
14939 let mut parsed: alloc::vec::Vec<(usize, Expr)> = alloc::vec::Vec::new();
14940 for (i, src) in schema.checks.iter().enumerate() {
14941 let expr = spg_sql::parser::parse_expression(src).map_err(|e| {
14942 EngineError::Unsupported(alloc::format!(
14943 "CHECK constraint #{i} on {table_name:?} ({src:?}) failed to re-parse: {e:?}"
14944 ))
14945 })?;
14946 parsed.push((i, expr));
14947 }
14948 for (batch_idx, row_values) in rows.iter().enumerate() {
14949 let tmp_row = spg_storage::Row {
14950 values: row_values.clone(),
14951 };
14952 for (i, expr) in &parsed {
14953 let v = eval::eval_expr(expr, &tmp_row, &ctx).map_err(|e| {
14954 EngineError::Unsupported(alloc::format!(
14955 "CHECK constraint #{i} on {table_name:?} eval at row #{batch_idx}: {e:?}"
14956 ))
14957 })?;
14958 if matches!(v, spg_storage::Value::Bool(false)) {
14960 return Err(EngineError::Unsupported(alloc::format!(
14961 "CHECK constraint violation on {table_name:?} (row #{batch_idx}): {:?}",
14962 schema.checks[*i]
14963 )));
14964 }
14965 }
14966 for (col_idx, checks) in &domain_checks_per_col {
14972 let cell = row_values
14973 .get(*col_idx)
14974 .cloned()
14975 .unwrap_or(spg_storage::Value::Null);
14976 let synth_cols = alloc::vec![spg_storage::ColumnSchema::new(
14977 "value",
14978 schema.columns[*col_idx].ty,
14979 schema.columns[*col_idx].nullable,
14980 )];
14981 let synth_ctx = eval::EvalContext::new(&synth_cols, None);
14982 let synth_row = spg_storage::Row {
14983 values: alloc::vec![cell],
14984 };
14985 for (ci, expr) in checks.iter().enumerate() {
14986 let v = eval::eval_expr(expr, &synth_row, &synth_ctx).map_err(|e| {
14987 EngineError::Unsupported(alloc::format!(
14988 "DOMAIN CHECK #{ci} on column {:?} eval at row #{batch_idx}: {e:?}",
14989 schema.columns[*col_idx].name
14990 ))
14991 })?;
14992 if matches!(v, spg_storage::Value::Bool(false)) {
14993 return Err(EngineError::Unsupported(alloc::format!(
14994 "DOMAIN CHECK violation on column {:?} (row #{batch_idx})",
14995 schema.columns[*col_idx].name
14996 )));
14997 }
14998 }
14999 }
15000 }
15001 Ok(())
15002}
15003
15004fn enforce_fk_inserts(
15005 catalog: &Catalog,
15006 child_table: &str,
15007 fks: &[spg_storage::ForeignKeyConstraint],
15008 rows: &[Vec<Value>],
15009) -> Result<(), EngineError> {
15010 for fk in fks {
15011 let parent_is_self = fk.parent_table == child_table;
15012 let parent = if parent_is_self {
15013 catalog.get(child_table).ok_or_else(|| {
15016 EngineError::Storage(StorageError::TableNotFound {
15017 name: child_table.into(),
15018 })
15019 })?
15020 } else {
15021 catalog.get(&fk.parent_table).ok_or_else(|| {
15022 EngineError::Storage(StorageError::TableNotFound {
15023 name: fk.parent_table.clone(),
15024 })
15025 })?
15026 };
15027 for (batch_idx, row_values) in rows.iter().enumerate() {
15028 if fk.local_columns.len() == 1 {
15032 let v = &row_values[fk.local_columns[0]];
15033 if matches!(v, Value::Null) {
15034 continue;
15035 }
15036 let parent_col = fk.parent_columns[0];
15037 let key = spg_storage::IndexKey::from_value(v).ok_or_else(|| {
15038 EngineError::Unsupported(alloc::format!(
15039 "FOREIGN KEY column value of type {:?} is not index-eligible",
15040 v.data_type()
15041 ))
15042 })?;
15043 let present_committed = parent.indices().iter().any(|idx| {
15044 matches!(idx.kind, spg_storage::IndexKind::BTree(_))
15045 && idx.column_position == parent_col
15046 && idx.partial_predicate.is_none()
15047 && !idx.lookup_eq(&key).is_empty()
15048 });
15049 let present_in_batch = parent_is_self
15053 && rows[..batch_idx]
15054 .iter()
15055 .any(|earlier| earlier.get(parent_col) == Some(v));
15056 if !(present_committed || present_in_batch) {
15057 return Err(EngineError::Unsupported(alloc::format!(
15058 "FOREIGN KEY violation: no parent row in {:?} where {} = {:?}",
15059 fk.parent_table,
15060 parent
15061 .schema()
15062 .columns
15063 .get(parent_col)
15064 .map_or("?", |c| c.name.as_str()),
15065 v,
15066 )));
15067 }
15068 } else {
15069 if fk
15073 .local_columns
15074 .iter()
15075 .all(|&i| matches!(row_values.get(i), Some(Value::Null)))
15076 {
15077 continue;
15078 }
15079 let local: Vec<&Value> = fk.local_columns.iter().map(|&i| &row_values[i]).collect();
15080 let parent_match_committed = parent.rows().iter().any(|prow| {
15081 fk.parent_columns
15082 .iter()
15083 .enumerate()
15084 .all(|(i, &pi)| prow.values.get(pi) == Some(local[i]))
15085 });
15086 let parent_match_in_batch = parent_is_self
15087 && rows[..batch_idx].iter().any(|earlier| {
15088 fk.parent_columns
15089 .iter()
15090 .enumerate()
15091 .all(|(i, &pi)| earlier.get(pi) == Some(local[i]))
15092 });
15093 if !(parent_match_committed || parent_match_in_batch) {
15094 return Err(EngineError::Unsupported(alloc::format!(
15095 "FOREIGN KEY violation: no parent row in {:?} matching composite key",
15096 fk.parent_table,
15097 )));
15098 }
15099 }
15100 }
15101 }
15102 Ok(())
15103}
15104
15105#[derive(Debug, Clone)]
15109struct FkChildStep {
15110 child_table: String,
15111 action: FkChildAction,
15112}
15113
15114#[derive(Debug, Clone)]
15115enum FkChildAction {
15116 Delete { positions: Vec<usize> },
15118 SetNull {
15122 positions: Vec<usize>,
15123 columns: Vec<usize>,
15124 },
15125 SetDefault {
15129 positions: Vec<usize>,
15130 columns: Vec<usize>,
15131 defaults: Vec<Value>,
15132 },
15133}
15134
15135fn plan_fk_parent_deletions(
15151 catalog: &Catalog,
15152 parent_table_name: &str,
15153 to_delete_positions: &[usize],
15154 to_delete_rows: &[Vec<Value>],
15155) -> Result<Vec<FkChildStep>, EngineError> {
15156 use alloc::collections::{BTreeMap, BTreeSet};
15157 if to_delete_rows.is_empty() {
15158 return Ok(Vec::new());
15159 }
15160 let mut delete_plan: BTreeMap<String, BTreeSet<usize>> = BTreeMap::new();
15161 let mut setnull_plan: BTreeMap<String, BTreeSet<(usize, usize)>> = BTreeMap::new();
15163 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15164 let mut visited: BTreeSet<(String, usize)> = BTreeSet::new();
15165 for &p in to_delete_positions {
15166 visited.insert((parent_table_name.to_string(), p));
15167 }
15168 let mut work: Vec<(String, Vec<Value>)> = to_delete_rows
15169 .iter()
15170 .map(|r| (parent_table_name.to_string(), r.clone()))
15171 .collect();
15172 while let Some((cur_parent, parent_row)) = work.pop() {
15173 for child_name in catalog.table_names() {
15174 let child = catalog
15175 .get(&child_name)
15176 .expect("table_names → catalog.get round-trip is total");
15177 for fk in &child.schema().foreign_keys {
15178 if fk.parent_table != cur_parent {
15179 continue;
15180 }
15181 let parent_key: Vec<&Value> = fk
15182 .parent_columns
15183 .iter()
15184 .map(|&pi| &parent_row[pi])
15185 .collect();
15186 if parent_key.iter().any(|v| matches!(v, Value::Null)) {
15187 continue;
15188 }
15189 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15190 if child_name == cur_parent
15191 && visited.contains(&(child_name.clone(), child_row_idx))
15192 {
15193 continue;
15194 }
15195 let matches_key = fk
15196 .local_columns
15197 .iter()
15198 .enumerate()
15199 .all(|(i, &li)| child_row.values.get(li) == Some(parent_key[i]));
15200 if !matches_key {
15201 continue;
15202 }
15203 match fk.on_delete {
15204 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15205 return Err(EngineError::Unsupported(alloc::format!(
15206 "FOREIGN KEY violation: DELETE on {cur_parent:?} is \
15207 restricted by FK from {child_name:?}.{:?}",
15208 fk.local_columns,
15209 )));
15210 }
15211 spg_storage::FkAction::Cascade => {
15212 if visited.insert((child_name.clone(), child_row_idx)) {
15213 delete_plan
15214 .entry(child_name.clone())
15215 .or_default()
15216 .insert(child_row_idx);
15217 work.push((child_name.clone(), child_row.values.clone()));
15218 }
15219 }
15220 spg_storage::FkAction::SetNull => {
15221 for &li in &fk.local_columns {
15223 let col = child.schema().columns.get(li).ok_or_else(|| {
15224 EngineError::Unsupported(alloc::format!(
15225 "FK local column {li} missing in {child_name:?}"
15226 ))
15227 })?;
15228 if !col.nullable {
15229 return Err(EngineError::Unsupported(alloc::format!(
15230 "FOREIGN KEY ON DELETE SET NULL: column \
15231 {child_name:?}.{:?} is NOT NULL — cannot SET NULL",
15232 col.name,
15233 )));
15234 }
15235 }
15236 let entry = setnull_plan.entry(child_name.clone()).or_default();
15237 for &li in &fk.local_columns {
15238 entry.insert((child_row_idx, li));
15239 }
15240 }
15241 spg_storage::FkAction::SetDefault => {
15242 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15244 for &li in &fk.local_columns {
15245 let col = child.schema().columns.get(li).ok_or_else(|| {
15246 EngineError::Unsupported(alloc::format!(
15247 "FK local column {li} missing in {child_name:?}"
15248 ))
15249 })?;
15250 let default = col.default.clone().ok_or_else(|| {
15251 EngineError::Unsupported(alloc::format!(
15252 "FOREIGN KEY ON DELETE SET DEFAULT: column \
15253 {child_name:?}.{:?} has no DEFAULT declared",
15254 col.name,
15255 ))
15256 })?;
15257 entry.insert((child_row_idx, li), default);
15258 }
15259 }
15260 }
15261 }
15262 }
15263 }
15264 }
15265 let mut steps: Vec<FkChildStep> = Vec::new();
15273 for (child_table, entries) in setnull_plan {
15274 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15275 steps.push(FkChildStep {
15276 child_table,
15277 action: FkChildAction::SetNull { positions, columns },
15278 });
15279 }
15280 for (child_table, entries) in setdefault_plan {
15281 let mut positions = Vec::with_capacity(entries.len());
15282 let mut columns = Vec::with_capacity(entries.len());
15283 let mut defaults = Vec::with_capacity(entries.len());
15284 for ((p, c), v) in entries {
15285 positions.push(p);
15286 columns.push(c);
15287 defaults.push(v);
15288 }
15289 steps.push(FkChildStep {
15290 child_table,
15291 action: FkChildAction::SetDefault {
15292 positions,
15293 columns,
15294 defaults,
15295 },
15296 });
15297 }
15298 for (child_table, positions) in delete_plan {
15299 steps.push(FkChildStep {
15300 child_table,
15301 action: FkChildAction::Delete {
15302 positions: positions.into_iter().collect(),
15303 },
15304 });
15305 }
15306 Ok(steps)
15307}
15308
15309fn plan_fk_parent_updates(
15326 catalog: &Catalog,
15327 parent_table_name: &str,
15328 plan_with_old: &[(usize, Vec<Value>, Vec<Value>)],
15329) -> Result<Vec<FkChildStep>, EngineError> {
15330 use alloc::collections::BTreeMap;
15331 if plan_with_old.is_empty() {
15332 return Ok(Vec::new());
15333 }
15334 let delete_plan: BTreeMap<String, alloc::collections::BTreeSet<usize>> = BTreeMap::new();
15339 let mut setnull_plan: BTreeMap<String, alloc::collections::BTreeSet<(usize, usize)>> =
15340 BTreeMap::new();
15341 let mut setdefault_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15342 let mut cascade_plan: BTreeMap<String, BTreeMap<(usize, usize), Value>> = BTreeMap::new();
15344
15345 for child_name in catalog.table_names() {
15346 let child = catalog
15347 .get(&child_name)
15348 .expect("table_names → catalog.get total");
15349 for fk in &child.schema().foreign_keys {
15350 if fk.parent_table != parent_table_name {
15351 continue;
15352 }
15353 for (_pos, old_row, new_row) in plan_with_old {
15354 let key_changed = fk
15356 .parent_columns
15357 .iter()
15358 .any(|&pi| old_row.get(pi) != new_row.get(pi));
15359 if !key_changed {
15360 continue;
15361 }
15362 let old_key: Vec<&Value> =
15364 fk.parent_columns.iter().map(|&pi| &old_row[pi]).collect();
15365 if old_key.iter().any(|v| matches!(v, Value::Null)) {
15366 continue;
15368 }
15369 let new_key: Vec<&Value> =
15370 fk.parent_columns.iter().map(|&pi| &new_row[pi]).collect();
15371 for (child_row_idx, child_row) in child.rows().iter().enumerate() {
15372 if child_name == parent_table_name
15375 && plan_with_old.iter().any(|(p, _, _)| *p == child_row_idx)
15376 {
15377 continue;
15378 }
15379 let matches_key = fk
15380 .local_columns
15381 .iter()
15382 .enumerate()
15383 .all(|(i, &li)| child_row.values.get(li) == Some(old_key[i]));
15384 if !matches_key {
15385 continue;
15386 }
15387 match fk.on_update {
15388 spg_storage::FkAction::Restrict | spg_storage::FkAction::NoAction => {
15389 return Err(EngineError::Unsupported(alloc::format!(
15390 "FOREIGN KEY violation: UPDATE on {parent_table_name:?} PK is \
15391 restricted by FK from {child_name:?}.{:?}",
15392 fk.local_columns,
15393 )));
15394 }
15395 spg_storage::FkAction::Cascade => {
15396 let entry = cascade_plan.entry(child_name.clone()).or_default();
15398 for (i, &li) in fk.local_columns.iter().enumerate() {
15399 entry.insert((child_row_idx, li), new_key[i].clone());
15400 }
15401 }
15402 spg_storage::FkAction::SetNull => {
15403 for &li in &fk.local_columns {
15404 let col = child.schema().columns.get(li).ok_or_else(|| {
15405 EngineError::Unsupported(alloc::format!(
15406 "FK local column {li} missing in {child_name:?}"
15407 ))
15408 })?;
15409 if !col.nullable {
15410 return Err(EngineError::Unsupported(alloc::format!(
15411 "FOREIGN KEY ON UPDATE SET NULL: column \
15412 {child_name:?}.{:?} is NOT NULL",
15413 col.name,
15414 )));
15415 }
15416 }
15417 let entry = setnull_plan.entry(child_name.clone()).or_default();
15418 for &li in &fk.local_columns {
15419 entry.insert((child_row_idx, li));
15420 }
15421 }
15422 spg_storage::FkAction::SetDefault => {
15423 let entry = setdefault_plan.entry(child_name.clone()).or_default();
15424 for &li in &fk.local_columns {
15425 let col = child.schema().columns.get(li).ok_or_else(|| {
15426 EngineError::Unsupported(alloc::format!(
15427 "FK local column {li} missing in {child_name:?}"
15428 ))
15429 })?;
15430 let default = col.default.clone().ok_or_else(|| {
15431 EngineError::Unsupported(alloc::format!(
15432 "FOREIGN KEY ON UPDATE SET DEFAULT: column \
15433 {child_name:?}.{:?} has no DEFAULT",
15434 col.name,
15435 ))
15436 })?;
15437 entry.insert((child_row_idx, li), default);
15438 }
15439 }
15440 }
15441 }
15442 }
15443 }
15444 }
15445 let mut steps: Vec<FkChildStep> = Vec::new();
15448 for (child_table, entries) in cascade_plan {
15449 let mut positions = Vec::with_capacity(entries.len());
15450 let mut columns = Vec::with_capacity(entries.len());
15451 let mut defaults = Vec::with_capacity(entries.len());
15452 for ((p, c), v) in entries {
15453 positions.push(p);
15454 columns.push(c);
15455 defaults.push(v);
15456 }
15457 steps.push(FkChildStep {
15462 child_table,
15463 action: FkChildAction::SetDefault {
15464 positions,
15465 columns,
15466 defaults,
15467 },
15468 });
15469 }
15470 for (child_table, entries) in setnull_plan {
15471 let (positions, columns): (Vec<usize>, Vec<usize>) = entries.into_iter().unzip();
15472 steps.push(FkChildStep {
15473 child_table,
15474 action: FkChildAction::SetNull { positions, columns },
15475 });
15476 }
15477 for (child_table, entries) in setdefault_plan {
15478 let mut positions = Vec::with_capacity(entries.len());
15479 let mut columns = Vec::with_capacity(entries.len());
15480 let mut defaults = Vec::with_capacity(entries.len());
15481 for ((p, c), v) in entries {
15482 positions.push(p);
15483 columns.push(c);
15484 defaults.push(v);
15485 }
15486 steps.push(FkChildStep {
15487 child_table,
15488 action: FkChildAction::SetDefault {
15489 positions,
15490 columns,
15491 defaults,
15492 },
15493 });
15494 }
15495 let _ = delete_plan; Ok(steps)
15497}
15498
15499fn apply_fk_child_step(catalog: &mut Catalog, step: &FkChildStep) -> Result<(), EngineError> {
15503 let child = catalog.get_mut(&step.child_table).ok_or_else(|| {
15504 EngineError::Storage(StorageError::TableNotFound {
15505 name: step.child_table.clone(),
15506 })
15507 })?;
15508 match &step.action {
15509 FkChildAction::Delete { positions } => {
15510 let _ = child.delete_rows(positions);
15511 }
15512 FkChildAction::SetNull { positions, columns } => {
15513 apply_per_cell_writes(child, positions, columns, |_| Value::Null)?;
15514 }
15515 FkChildAction::SetDefault {
15516 positions,
15517 columns,
15518 defaults,
15519 } => {
15520 apply_per_cell_writes(child, positions, columns, |i| defaults[i].clone())?;
15521 }
15522 }
15523 Ok(())
15524}
15525
15526fn apply_per_cell_writes(
15532 child: &mut spg_storage::Table,
15533 positions: &[usize],
15534 columns: &[usize],
15535 mut value_for: impl FnMut(usize) -> Value,
15536) -> Result<(), EngineError> {
15537 use alloc::collections::BTreeMap;
15538 let mut by_row: BTreeMap<usize, Vec<(usize, Value)>> = BTreeMap::new();
15539 for i in 0..positions.len() {
15540 by_row
15541 .entry(positions[i])
15542 .or_default()
15543 .push((columns[i], value_for(i)));
15544 }
15545 for (pos, mutations) in by_row {
15546 let mut new_values = child.rows()[pos].values.clone();
15547 for (col, v) in mutations {
15548 if let Some(slot) = new_values.get_mut(col) {
15549 *slot = v;
15550 }
15551 }
15552 child
15553 .update_row(pos, new_values)
15554 .map_err(EngineError::Storage)?;
15555 }
15556 Ok(())
15557}
15558
15559fn fk_action_sql_to_storage(a: spg_sql::ast::FkAction) -> spg_storage::FkAction {
15560 match a {
15561 spg_sql::ast::FkAction::Restrict => spg_storage::FkAction::Restrict,
15562 spg_sql::ast::FkAction::Cascade => spg_storage::FkAction::Cascade,
15563 spg_sql::ast::FkAction::SetNull => spg_storage::FkAction::SetNull,
15564 spg_sql::ast::FkAction::SetDefault => spg_storage::FkAction::SetDefault,
15565 spg_sql::ast::FkAction::NoAction => spg_storage::FkAction::NoAction,
15566 }
15567}
15568
15569fn resolve_column_default_free(
15575 col: &ColumnSchema,
15576 clock_fn: Option<ClockFn>,
15577) -> Result<Value, EngineError> {
15578 if let Some(rt) = &col.runtime_default {
15579 return eval_runtime_default_free(rt, col.ty, clock_fn);
15580 }
15581 Ok(col.default.clone().unwrap_or(Value::Null))
15582}
15583
15584fn eval_runtime_default_free(
15585 rt: &str,
15586 ty: DataType,
15587 clock_fn: Option<ClockFn>,
15588) -> Result<Value, EngineError> {
15589 let s = rt.trim().to_ascii_lowercase();
15590 let with_no_parens = s.trim_end_matches("()");
15596 let canonical: &str = if let Some(open_idx) = with_no_parens.find('(') {
15597 if with_no_parens.ends_with(')') {
15598 &with_no_parens[..open_idx]
15599 } else {
15600 with_no_parens
15601 }
15602 } else {
15603 with_no_parens
15604 };
15605 let now_us = match clock_fn {
15606 Some(f) => f(),
15607 None => 0,
15608 };
15609 let v = match canonical {
15610 "now" | "current_timestamp" | "localtimestamp" => Value::Timestamp(now_us),
15611 "current_date" => Value::Date((now_us / 86_400_000_000) as i32),
15612 "current_time" | "localtime" => Value::Timestamp(now_us),
15613 "gen_random_uuid" | "uuid_generate_v4" => Value::Uuid(eval::gen_random_uuid_bytes()),
15619 other => {
15620 return Err(EngineError::Unsupported(alloc::format!(
15621 "runtime DEFAULT expression {other:?} not supported \
15622 (v7.17.0 whitelist: now() / current_timestamp / \
15623 current_date / current_time / localtimestamp / \
15624 localtime / gen_random_uuid() / \
15625 uuid_generate_v4())"
15626 )));
15627 }
15628 };
15629 coerce_value(v, ty, "DEFAULT", 0)
15630}
15631
15632fn is_runtime_default_expr(expr: &Expr) -> bool {
15638 match expr {
15639 Expr::FunctionCall { .. } => true,
15640 Expr::Unary { expr, .. } => is_runtime_default_expr(expr),
15641 _ => false,
15642 }
15643}
15644
15645fn canonicalize_set_value(
15658 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15659 col_idx: usize,
15660 col_name: &str,
15661 value: Value,
15662) -> Result<Value, EngineError> {
15663 let Some(variants) = lookup.get(&col_idx) else {
15664 return Ok(value);
15665 };
15666 match value {
15667 Value::Null => Ok(Value::Null),
15668 Value::Text(s) => {
15669 if s.is_empty() {
15670 return Ok(Value::Text(alloc::string::String::new()));
15671 }
15672 let mut present = alloc::vec![false; variants.len()];
15675 for raw in s.split(',') {
15676 let tok = raw.trim();
15677 if tok.is_empty() {
15678 continue;
15679 }
15680 let idx = variants.iter().position(|v| v == tok).ok_or_else(|| {
15681 EngineError::Unsupported(alloc::format!(
15682 "column {col_name:?}: invalid SET token {tok:?}; \
15683 allowed: {variants:?}"
15684 ))
15685 })?;
15686 present[idx] = true;
15687 }
15688 let mut out = alloc::string::String::new();
15690 let mut first = true;
15691 for (i, keep) in present.iter().enumerate() {
15692 if !keep {
15693 continue;
15694 }
15695 if !first {
15696 out.push(',');
15697 }
15698 first = false;
15699 out.push_str(&variants[i]);
15700 }
15701 Ok(Value::Text(out))
15702 }
15703 other => Err(EngineError::Unsupported(alloc::format!(
15704 "column {col_name:?}: SET-typed column expects TEXT, got {:?}",
15705 other.data_type()
15706 ))),
15707 }
15708}
15709
15710fn enforce_enum_label(
15711 lookup: &alloc::collections::BTreeMap<usize, Vec<String>>,
15712 col_idx: usize,
15713 col_name: &str,
15714 value: &Value,
15715) -> Result<(), EngineError> {
15716 if let Some(labels) = lookup.get(&col_idx) {
15717 match value {
15718 Value::Null => Ok(()),
15719 Value::Text(s) => {
15720 if labels.iter().any(|l| l == s) {
15721 Ok(())
15722 } else {
15723 Err(EngineError::Unsupported(alloc::format!(
15724 "column {col_name:?}: invalid enum label {s:?}; allowed: {labels:?}"
15725 )))
15726 }
15727 }
15728 other => Err(EngineError::Unsupported(alloc::format!(
15729 "column {col_name:?}: enum-typed column expects TEXT, got {:?}",
15730 other.data_type()
15731 ))),
15732 }
15733 } else {
15734 Ok(())
15735 }
15736}
15737
15738fn column_def_to_schema(c: ColumnDef) -> Result<ColumnSchema, EngineError> {
15739 let ty = column_type_to_data_type(c.ty);
15740 let mut schema = ColumnSchema::new(c.name.clone(), ty, c.nullable);
15741 if let Some(name) = c.user_type_ref {
15748 schema.user_enum_type = Some(name);
15749 }
15750 if let Some(expr) = c.on_update_runtime {
15753 schema.on_update_runtime = Some(alloc::format!("{expr}"));
15754 }
15755 schema.collation = match c.collation {
15759 spg_sql::ast::Collation::Binary => spg_storage::Collation::Binary,
15760 spg_sql::ast::Collation::CaseInsensitive => spg_storage::Collation::CaseInsensitive,
15761 };
15762 schema.is_unsigned = c.is_unsigned;
15765 schema.inline_enum_variants = c.inline_enum_variants;
15769 schema.inline_set_variants = c.inline_set_variants;
15773 if let Some(default_expr) = c.default {
15774 if is_runtime_default_expr(&default_expr) {
15780 let display = alloc::format!("{default_expr}");
15781 schema = schema.with_runtime_default(display);
15782 } else {
15783 let raw = literal_expr_to_value(default_expr)?;
15784 let coerced = coerce_value(raw, ty, &c.name, 0)?;
15785 schema = schema.with_default(coerced);
15786 }
15787 }
15788 if c.auto_increment {
15789 if !matches!(ty, DataType::SmallInt | DataType::Int | DataType::BigInt) {
15791 return Err(EngineError::Unsupported(alloc::format!(
15792 "AUTO_INCREMENT requires an integer column type, got {ty:?}"
15793 )));
15794 }
15795 schema = schema.with_auto_increment();
15796 }
15797 Ok(schema)
15798}
15799
15800fn decode_bytea_literal(s: &str) -> Result<alloc::vec::Vec<u8>, &'static str> {
15805 let s = s.trim();
15806 if let Some(hex) = s.strip_prefix("\\x").or_else(|| s.strip_prefix("\\X")) {
15807 let cleaned: alloc::string::String = hex.chars().filter(|c| !c.is_whitespace()).collect();
15809 if cleaned.len() % 2 != 0 {
15810 return Err("odd-length hex literal");
15811 }
15812 let mut out = alloc::vec::Vec::with_capacity(cleaned.len() / 2);
15813 let cleaned_bytes = cleaned.as_bytes();
15814 for i in (0..cleaned_bytes.len()).step_by(2) {
15815 let hi = hex_nibble(cleaned_bytes[i])?;
15816 let lo = hex_nibble(cleaned_bytes[i + 1])?;
15817 out.push((hi << 4) | lo);
15818 }
15819 return Ok(out);
15820 }
15821 let bytes = s.as_bytes();
15824 let mut out = alloc::vec::Vec::with_capacity(bytes.len());
15825 let mut i = 0;
15826 while i < bytes.len() {
15827 let b = bytes[i];
15828 if b == b'\\' && i + 1 < bytes.len() {
15829 let n = bytes[i + 1];
15830 if n == b'\\' {
15831 out.push(b'\\');
15832 i += 2;
15833 continue;
15834 }
15835 if n.is_ascii_digit()
15836 && i + 3 < bytes.len()
15837 && bytes[i + 2].is_ascii_digit()
15838 && bytes[i + 3].is_ascii_digit()
15839 {
15840 let oct = |x: u8| (x - b'0') as u32;
15841 let v = oct(n) * 64 + oct(bytes[i + 2]) * 8 + oct(bytes[i + 3]);
15842 if v <= 0xFF {
15843 out.push(v as u8);
15844 i += 4;
15845 continue;
15846 }
15847 }
15848 }
15849 out.push(b);
15850 i += 1;
15851 }
15852 Ok(out)
15853}
15854
15855fn hex_nibble(b: u8) -> Result<u8, &'static str> {
15856 match b {
15857 b'0'..=b'9' => Ok(b - b'0'),
15858 b'a'..=b'f' => Ok(b - b'a' + 10),
15859 b'A'..=b'F' => Ok(b - b'A' + 10),
15860 _ => Err("invalid hex digit"),
15861 }
15862}
15863
15864fn array_literal_widen(items: alloc::vec::Vec<Value>) -> Value {
15878 let mut has_text = false;
15879 let mut has_bigint = false;
15880 let mut has_int = false;
15881 for v in &items {
15882 match v {
15883 Value::Null => {}
15884 Value::Text(_) | Value::Json(_) => has_text = true,
15885 Value::BigInt(_) => has_bigint = true,
15886 Value::Int(_) | Value::SmallInt(_) => has_int = true,
15887 _ => has_text = true,
15888 }
15889 }
15890 if has_text || (!has_bigint && !has_int) {
15891 let out: alloc::vec::Vec<Option<alloc::string::String>> = items
15892 .into_iter()
15893 .map(|v| match v {
15894 Value::Null => None,
15895 Value::Text(s) | Value::Json(s) => Some(s),
15896 other => Some(alloc::format!("{other:?}")),
15897 })
15898 .collect();
15899 return Value::TextArray(out);
15900 }
15901 if has_bigint {
15902 let out: alloc::vec::Vec<Option<i64>> = items
15903 .into_iter()
15904 .map(|v| match v {
15905 Value::Null => None,
15906 Value::Int(n) => Some(i64::from(n)),
15907 Value::SmallInt(n) => Some(i64::from(n)),
15908 Value::BigInt(n) => Some(n),
15909 _ => unreachable!("widen: unexpected non-integer in BigInt path"),
15910 })
15911 .collect();
15912 return Value::BigIntArray(out);
15913 }
15914 let out: alloc::vec::Vec<Option<i32>> = items
15915 .into_iter()
15916 .map(|v| match v {
15917 Value::Null => None,
15918 Value::Int(n) => Some(n),
15919 Value::SmallInt(n) => Some(i32::from(n)),
15920 _ => unreachable!("widen: unexpected non-i32-compatible in Int path"),
15921 })
15922 .collect();
15923 Value::IntArray(out)
15924}
15925
15926fn decode_text_array_literal(
15927 s: &str,
15928) -> Result<alloc::vec::Vec<Option<alloc::string::String>>, &'static str> {
15929 let trimmed = s.trim();
15930 let inner = trimmed
15931 .strip_prefix('{')
15932 .and_then(|x| x.strip_suffix('}'))
15933 .ok_or("TEXT[] literal must be enclosed in '{...}'")?;
15934 let mut out: alloc::vec::Vec<Option<alloc::string::String>> = alloc::vec::Vec::new();
15935 if inner.trim().is_empty() {
15936 return Ok(out);
15937 }
15938 let bytes = inner.as_bytes();
15939 let mut i = 0;
15940 while i <= bytes.len() {
15941 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
15943 i += 1;
15944 }
15945 if i < bytes.len() && bytes[i] == b'"' {
15947 i += 1; let mut buf = alloc::string::String::new();
15949 while i < bytes.len() && bytes[i] != b'"' {
15950 if bytes[i] == b'\\' && i + 1 < bytes.len() {
15951 buf.push(bytes[i + 1] as char);
15952 i += 2;
15953 } else {
15954 buf.push(bytes[i] as char);
15955 i += 1;
15956 }
15957 }
15958 if i >= bytes.len() {
15959 return Err("unterminated quoted element");
15960 }
15961 i += 1; out.push(Some(buf));
15963 } else {
15964 let start = i;
15966 while i < bytes.len() && bytes[i] != b',' {
15967 i += 1;
15968 }
15969 let raw = inner[start..i].trim();
15970 if raw.eq_ignore_ascii_case("NULL") {
15971 out.push(None);
15972 } else {
15973 out.push(Some(alloc::string::ToString::to_string(raw)));
15974 }
15975 }
15976 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
15978 i += 1;
15979 }
15980 if i >= bytes.len() {
15981 break;
15982 }
15983 if bytes[i] != b',' {
15984 return Err("expected ',' between TEXT[] elements");
15985 }
15986 i += 1;
15987 }
15988 Ok(out)
15989}
15990
15991fn encode_text_array(items: &[Option<alloc::string::String>]) -> alloc::string::String {
15996 let mut out = alloc::string::String::with_capacity(2 + items.len() * 8);
15997 out.push('{');
15998 for (i, item) in items.iter().enumerate() {
15999 if i > 0 {
16000 out.push(',');
16001 }
16002 match item {
16003 None => out.push_str("NULL"),
16004 Some(s) => {
16005 let needs_quote = s.is_empty()
16006 || s.eq_ignore_ascii_case("NULL")
16007 || s.chars()
16008 .any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\' | ' ' | '\t'));
16009 if needs_quote {
16010 out.push('"');
16011 for c in s.chars() {
16012 if c == '"' || c == '\\' {
16013 out.push('\\');
16014 }
16015 out.push(c);
16016 }
16017 out.push('"');
16018 } else {
16019 out.push_str(s);
16020 }
16021 }
16022 }
16023 }
16024 out.push('}');
16025 out
16026}
16027
16028fn encode_bytea_hex(b: &[u8]) -> alloc::string::String {
16032 let mut out = alloc::string::String::with_capacity(2 + 2 * b.len());
16033 out.push_str("\\x");
16034 for byte in b {
16035 let hi = byte >> 4;
16036 let lo = byte & 0x0F;
16037 out.push(hex_digit(hi));
16038 out.push(hex_digit(lo));
16039 }
16040 out
16041}
16042
16043const fn hex_digit(n: u8) -> char {
16044 match n {
16045 0..=9 => (b'0' + n) as char,
16046 10..=15 => (b'a' + n - 10) as char,
16047 _ => '?',
16048 }
16049}
16050
16051fn parse_hstore_str(
16063 s: &str,
16064) -> Option<Vec<(alloc::string::String, Option<alloc::string::String>)>> {
16065 let bytes = s.as_bytes();
16066 let mut i = 0;
16067 let mut out: Vec<(alloc::string::String, Option<alloc::string::String>)> = Vec::new();
16068 let skip_ws = |bytes: &[u8], i: &mut usize| {
16069 while *i < bytes.len() && matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r') {
16070 *i += 1;
16071 }
16072 };
16073 let parse_token = |bytes: &[u8], i: &mut usize| -> Option<alloc::string::String> {
16074 if *i >= bytes.len() {
16075 return None;
16076 }
16077 if bytes[*i] == b'"' {
16078 *i += 1;
16079 let mut out = alloc::string::String::new();
16080 while *i < bytes.len() {
16081 match bytes[*i] {
16082 b'"' => {
16083 *i += 1;
16084 return Some(out);
16085 }
16086 b'\\' if *i + 1 < bytes.len() => {
16087 out.push(bytes[*i + 1] as char);
16088 *i += 2;
16089 }
16090 c => {
16091 out.push(c as char);
16092 *i += 1;
16093 }
16094 }
16095 }
16096 None
16097 } else {
16098 let start = *i;
16099 while *i < bytes.len()
16100 && !matches!(bytes[*i], b' ' | b'\t' | b'\n' | b'\r' | b',' | b'=')
16101 {
16102 *i += 1;
16103 }
16104 if *i == start {
16105 return None;
16106 }
16107 Some(alloc::str::from_utf8(&bytes[start..*i]).ok()?.to_string())
16108 }
16109 };
16110 skip_ws(bytes, &mut i);
16111 while i < bytes.len() {
16112 let key = parse_token(bytes, &mut i)?;
16113 skip_ws(bytes, &mut i);
16114 if i + 1 >= bytes.len() || bytes[i] != b'=' || bytes[i + 1] != b'>' {
16115 return None;
16116 }
16117 i += 2;
16118 skip_ws(bytes, &mut i);
16119 let val_token = if i + 4 <= bytes.len()
16121 && bytes[i..i + 4].eq_ignore_ascii_case(b"NULL")
16122 && (i + 4 == bytes.len() || matches!(bytes[i + 4], b' ' | b'\t' | b',' | b'\n' | b'\r'))
16123 {
16124 i += 4;
16125 None
16126 } else {
16127 Some(parse_token(bytes, &mut i)?)
16128 };
16129 if let Some(pos) = out.iter().position(|(k, _)| k == &key) {
16131 out[pos] = (key, val_token);
16132 } else {
16133 out.push((key, val_token));
16134 }
16135 skip_ws(bytes, &mut i);
16136 if i >= bytes.len() {
16137 break;
16138 }
16139 if bytes[i] == b',' {
16140 i += 1;
16141 skip_ws(bytes, &mut i);
16142 continue;
16143 }
16144 return None;
16145 }
16146 Some(out)
16147}
16148
16149fn format_hstore_str(
16153 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16154) -> alloc::string::String {
16155 let mut out = alloc::string::String::new();
16156 for (i, (k, v)) in pairs.iter().enumerate() {
16157 if i > 0 {
16158 out.push_str(", ");
16159 }
16160 out.push('"');
16161 out.push_str(k);
16162 out.push_str("\"=>");
16163 match v {
16164 None => out.push_str("NULL"),
16165 Some(val) => {
16166 out.push('"');
16167 out.push_str(val);
16168 out.push('"');
16169 }
16170 }
16171 }
16172 out
16173}
16174
16175pub fn format_hstore_text(
16178 pairs: &[(alloc::string::String, Option<alloc::string::String>)],
16179) -> alloc::string::String {
16180 format_hstore_str(pairs)
16181}
16182
16183fn split_2d_literal(s: &str) -> Result<Vec<Vec<alloc::string::String>>, &'static str> {
16188 let s = s.trim();
16189 let outer = s
16190 .strip_prefix('{')
16191 .and_then(|x| x.strip_suffix('}'))
16192 .ok_or("missing outer '{...}' braces")?;
16193 let trimmed = outer.trim();
16194 if trimmed.is_empty() {
16195 return Ok(Vec::new());
16196 }
16197 let mut rows: Vec<Vec<alloc::string::String>> = Vec::new();
16198 let mut i = 0;
16199 let bytes = trimmed.as_bytes();
16200 while i < bytes.len() {
16201 while i < bytes.len() && matches!(bytes[i], b' ' | b'\t' | b'\n' | b'\r' | b',') {
16202 i += 1;
16203 }
16204 if i >= bytes.len() {
16205 break;
16206 }
16207 if bytes[i] != b'{' {
16208 return Err("expected '{' opening a row");
16209 }
16210 i += 1;
16211 let row_start = i;
16212 let mut depth = 1;
16213 while i < bytes.len() && depth > 0 {
16214 match bytes[i] {
16215 b'{' => depth += 1,
16216 b'}' => depth -= 1,
16217 _ => {}
16218 }
16219 if depth > 0 {
16220 i += 1;
16221 }
16222 }
16223 if depth != 0 {
16224 return Err("unbalanced '{...}' in row");
16225 }
16226 let row_text = &trimmed[row_start..i];
16227 i += 1;
16228 let cells: Vec<alloc::string::String> = if row_text.trim().is_empty() {
16229 Vec::new()
16230 } else {
16231 row_text.split(',').map(|t| t.trim().to_string()).collect()
16232 };
16233 rows.push(cells);
16234 }
16235 if let Some(first) = rows.first() {
16236 let cols = first.len();
16237 for r in &rows {
16238 if r.len() != cols {
16239 return Err("ragged 2D array (rows have different column counts)");
16240 }
16241 }
16242 }
16243 Ok(rows)
16244}
16245
16246fn parse_int_2d_literal(s: &str) -> Result<Vec<Vec<Option<i32>>>, &'static str> {
16247 let raw = split_2d_literal(s)?;
16248 raw.into_iter()
16249 .map(|row| {
16250 row.into_iter()
16251 .map(|cell| {
16252 if cell.eq_ignore_ascii_case("NULL") {
16253 Ok(None)
16254 } else {
16255 cell.parse::<i32>()
16256 .map(Some)
16257 .map_err(|_| "invalid int element")
16258 }
16259 })
16260 .collect()
16261 })
16262 .collect()
16263}
16264
16265fn parse_bigint_2d_literal(s: &str) -> Result<Vec<Vec<Option<i64>>>, &'static str> {
16266 let raw = split_2d_literal(s)?;
16267 raw.into_iter()
16268 .map(|row| {
16269 row.into_iter()
16270 .map(|cell| {
16271 if cell.eq_ignore_ascii_case("NULL") {
16272 Ok(None)
16273 } else {
16274 cell.parse::<i64>()
16275 .map(Some)
16276 .map_err(|_| "invalid bigint element")
16277 }
16278 })
16279 .collect()
16280 })
16281 .collect()
16282}
16283
16284fn parse_text_2d_literal(s: &str) -> Result<Vec<Vec<Option<alloc::string::String>>>, &'static str> {
16285 let raw = split_2d_literal(s)?;
16286 Ok(raw
16287 .into_iter()
16288 .map(|row| {
16289 row.into_iter()
16290 .map(|cell| {
16291 if cell.eq_ignore_ascii_case("NULL") {
16292 None
16293 } else {
16294 Some(cell.trim_matches('"').to_string())
16295 }
16296 })
16297 .collect()
16298 })
16299 .collect())
16300}
16301
16302fn format_int_2d_text(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16303 let mut out = alloc::string::String::from("{");
16304 for (i, row) in rows.iter().enumerate() {
16305 if i > 0 {
16306 out.push(',');
16307 }
16308 out.push('{');
16309 for (j, cell) in row.iter().enumerate() {
16310 if j > 0 {
16311 out.push(',');
16312 }
16313 match cell {
16314 None => out.push_str("NULL"),
16315 Some(n) => out.push_str(&alloc::format!("{n}")),
16316 }
16317 }
16318 out.push('}');
16319 }
16320 out.push('}');
16321 out
16322}
16323
16324fn format_bigint_2d_text(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16325 let mut out = alloc::string::String::from("{");
16326 for (i, row) in rows.iter().enumerate() {
16327 if i > 0 {
16328 out.push(',');
16329 }
16330 out.push('{');
16331 for (j, cell) in row.iter().enumerate() {
16332 if j > 0 {
16333 out.push(',');
16334 }
16335 match cell {
16336 None => out.push_str("NULL"),
16337 Some(n) => out.push_str(&alloc::format!("{n}")),
16338 }
16339 }
16340 out.push('}');
16341 }
16342 out.push('}');
16343 out
16344}
16345
16346fn format_text_2d_text(rows: &[Vec<Option<alloc::string::String>>]) -> alloc::string::String {
16347 let mut out = alloc::string::String::from("{");
16348 for (i, row) in rows.iter().enumerate() {
16349 if i > 0 {
16350 out.push(',');
16351 }
16352 out.push('{');
16353 for (j, cell) in row.iter().enumerate() {
16354 if j > 0 {
16355 out.push(',');
16356 }
16357 match cell {
16358 None => out.push_str("NULL"),
16359 Some(s) => out.push_str(s),
16360 }
16361 }
16362 out.push('}');
16363 }
16364 out.push('}');
16365 out
16366}
16367
16368pub fn format_int_2d_text_pub(rows: &[Vec<Option<i32>>]) -> alloc::string::String {
16371 format_int_2d_text(rows)
16372}
16373pub fn format_bigint_2d_text_pub(rows: &[Vec<Option<i64>>]) -> alloc::string::String {
16374 format_bigint_2d_text(rows)
16375}
16376pub fn format_text_2d_text_pub(
16377 rows: &[Vec<Option<alloc::string::String>>],
16378) -> alloc::string::String {
16379 format_text_2d_text(rows)
16380}
16381
16382fn parse_range_str(s: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16387 let s = s.trim();
16388 if s.eq_ignore_ascii_case("empty") {
16389 return Some(Value::Range {
16390 kind,
16391 lower: None,
16392 upper: None,
16393 lower_inc: false,
16394 upper_inc: false,
16395 empty: true,
16396 });
16397 }
16398 let bytes = s.as_bytes();
16399 if bytes.len() < 3 {
16400 return None;
16401 }
16402 let lower_inc = match bytes[0] {
16403 b'[' => true,
16404 b'(' => false,
16405 _ => return None,
16406 };
16407 let upper_inc = match bytes[bytes.len() - 1] {
16408 b']' => true,
16409 b')' => false,
16410 _ => return None,
16411 };
16412 let inner = &s[1..s.len() - 1];
16413 let (lo_text, up_text) = inner.split_once(',')?;
16414 let lower = if lo_text.is_empty() {
16415 None
16416 } else {
16417 Some(alloc::boxed::Box::new(parse_range_element(lo_text, kind)?))
16418 };
16419 let upper = if up_text.is_empty() {
16420 None
16421 } else {
16422 Some(alloc::boxed::Box::new(parse_range_element(up_text, kind)?))
16423 };
16424 Some(Value::Range {
16425 kind,
16426 lower,
16427 upper,
16428 lower_inc,
16429 upper_inc,
16430 empty: false,
16431 })
16432}
16433
16434fn parse_range_element(text: &str, kind: spg_storage::RangeKind) -> Option<Value> {
16437 let text = text.trim().trim_matches('"');
16438 use spg_storage::RangeKind as K;
16439 match kind {
16440 K::Int4 => text.parse::<i32>().ok().map(Value::Int),
16441 K::Int8 => text.parse::<i64>().ok().map(Value::BigInt),
16442 K::Num => {
16443 let dot = text.find('.');
16446 let scale: u8 = dot.map_or(0, |p| (text.len() - p - 1) as u8);
16447 let digits: alloc::string::String = text
16448 .chars()
16449 .filter(|c| *c == '-' || c.is_ascii_digit())
16450 .collect();
16451 let scaled: i128 = digits.parse().ok()?;
16452 Some(Value::Numeric { scaled, scale })
16453 }
16454 K::Ts | K::TsTz => {
16455 crate::eval::parse_timestamp_literal(text).map(Value::Timestamp)
16460 }
16461 K::Date => crate::eval::parse_date_literal(text).map(Value::Date),
16462 }
16463}
16464
16465pub fn format_range_text(v: &Value) -> alloc::string::String {
16469 format_range_str(v)
16470}
16471
16472fn format_range_str(v: &Value) -> alloc::string::String {
16473 let Value::Range {
16474 lower,
16475 upper,
16476 lower_inc,
16477 upper_inc,
16478 empty,
16479 ..
16480 } = v
16481 else {
16482 return alloc::string::String::new();
16483 };
16484 if *empty {
16485 return "empty".into();
16486 }
16487 let mut out = alloc::string::String::new();
16488 out.push(if *lower_inc { '[' } else { '(' });
16489 if let Some(l) = lower {
16490 out.push_str(&format_range_element(l));
16491 }
16492 out.push(',');
16493 if let Some(u) = upper {
16494 out.push_str(&format_range_element(u));
16495 }
16496 out.push(if *upper_inc { ']' } else { ')' });
16497 out
16498}
16499
16500fn format_range_element(v: &Value) -> alloc::string::String {
16501 match v {
16502 Value::Int(n) => alloc::format!("{n}"),
16503 Value::BigInt(n) => alloc::format!("{n}"),
16504 Value::Date(d) => crate::eval::format_date(*d),
16505 Value::Timestamp(t) => crate::eval::format_timestamp(*t),
16506 Value::Numeric { scaled, scale } => crate::eval::format_numeric(*scaled, *scale),
16507 other => alloc::format!("{other:?}"),
16508 }
16509}
16510
16511fn parse_money_str(s: &str) -> Option<i64> {
16522 let s = s.trim();
16523 let (neg, rest) = match s.strip_prefix('-') {
16524 Some(r) => (true, r.trim_start()),
16525 None => (false, s),
16526 };
16527 let rest = rest.strip_prefix('$').unwrap_or(rest).trim_start();
16528 let (int_part, frac_part) = match rest.split_once('.') {
16529 Some((i, f)) => (i, Some(f)),
16530 None => (rest, None),
16531 };
16532 if int_part.is_empty() {
16533 return None;
16534 }
16535 let mut int_digits = alloc::string::String::with_capacity(int_part.len());
16537 for b in int_part.bytes() {
16538 match b {
16539 b',' => {}
16540 b'0'..=b'9' => int_digits.push(b as char),
16541 _ => return None,
16542 }
16543 }
16544 if int_digits.is_empty() {
16545 return None;
16546 }
16547 let dollars: i64 = int_digits.parse().ok()?;
16548 let cents: i64 = match frac_part {
16549 None => 0,
16550 Some(f) => {
16551 if f.is_empty() || f.len() > 2 || !f.bytes().all(|b| b.is_ascii_digit()) {
16552 return None;
16553 }
16554 let padded = if f.len() == 1 {
16555 alloc::format!("{f}0")
16556 } else {
16557 f.to_string()
16558 };
16559 padded.parse().ok()?
16560 }
16561 };
16562 let total = dollars.checked_mul(100)?.checked_add(cents)?;
16563 Some(if neg { -total } else { total })
16564}
16565
16566fn parse_timetz_str(s: &str) -> Option<(i64, i32)> {
16577 let s = s.trim();
16578 let bytes = s.as_bytes();
16582 let sign_pos = bytes
16583 .iter()
16584 .enumerate()
16585 .rev()
16586 .find(|&(_, &b)| b == b'+' || b == b'-')
16587 .map(|(i, _)| i)?;
16588 if sign_pos == 0 {
16589 return None; }
16591 let time_part = &s[..sign_pos];
16592 let offset_part = &s[sign_pos..];
16593 let us = parse_time_str(time_part)?;
16594 let sign: i32 = if offset_part.starts_with('+') { 1 } else { -1 };
16595 let offset_body = &offset_part[1..];
16596 let (hh_str, mm_str) = match offset_body.split_once(':') {
16597 Some((h, m)) => (h, m),
16598 None => (offset_body, "0"),
16599 };
16600 let hh: i32 = hh_str.parse().ok()?;
16601 let mm: i32 = mm_str.parse().ok()?;
16602 if !(0..=14).contains(&hh) || !(0..=59).contains(&mm) {
16603 return None;
16604 }
16605 let total = sign * (hh * 3600 + mm * 60);
16606 if total.abs() > 50_400 {
16607 return None;
16608 }
16609 Some((us, total))
16610}
16611
16612fn coerce_int_to_year(n: i64, col_name: &str) -> Result<Value, EngineError> {
16617 if n == 0 || (1901..=2155).contains(&n) {
16618 return Ok(Value::Year(n as u16));
16621 }
16622 Err(EngineError::Eval(EvalError::TypeMismatch {
16623 detail: alloc::format!(
16624 "year value out of range: {n} (column `{col_name}`; \
16625 MySQL accepts 0 or 1901..=2155)"
16626 ),
16627 }))
16628}
16629
16630fn parse_time_str(s: &str) -> Option<i64> {
16642 let s = s.trim();
16643 let (hms, frac) = match s.split_once('.') {
16644 Some((h, f)) => (h, Some(f)),
16645 None => (s, None),
16646 };
16647 let mut parts = hms.split(':');
16648 let hh: u32 = parts.next()?.parse().ok()?;
16649 let mm: u32 = parts.next()?.parse().ok()?;
16650 let ss: u32 = parts.next()?.parse().ok()?;
16651 if parts.next().is_some() {
16652 return None;
16653 }
16654 if hh > 23 || mm > 59 || ss > 59 {
16655 return None;
16656 }
16657 let frac_us: i64 = match frac {
16658 None => 0,
16659 Some(f) => {
16660 if f.is_empty() || f.len() > 6 || !f.bytes().all(|b| b.is_ascii_digit()) {
16661 return None;
16662 }
16663 let mut padded = alloc::string::String::with_capacity(6);
16665 padded.push_str(f);
16666 while padded.len() < 6 {
16667 padded.push('0');
16668 }
16669 padded.parse().ok()?
16670 }
16671 };
16672 Some(
16673 i64::from(hh) * 3_600_000_000
16674 + i64::from(mm) * 60_000_000
16675 + i64::from(ss) * 1_000_000
16676 + frac_us,
16677 )
16678}
16679
16680const fn column_type_to_data_type(t: ColumnTypeName) -> DataType {
16681 match t {
16682 ColumnTypeName::SmallInt => DataType::SmallInt,
16683 ColumnTypeName::Int => DataType::Int,
16684 ColumnTypeName::BigInt => DataType::BigInt,
16685 ColumnTypeName::Float => DataType::Float,
16686 ColumnTypeName::Text => DataType::Text,
16687 ColumnTypeName::Varchar(n) => DataType::Varchar(n),
16688 ColumnTypeName::Char(n) => DataType::Char(n),
16689 ColumnTypeName::Bool => DataType::Bool,
16690 ColumnTypeName::Vector { dim, encoding } => DataType::Vector {
16691 dim,
16692 encoding: match encoding {
16693 SqlVecEncoding::F32 => VecEncoding::F32,
16694 SqlVecEncoding::Sq8 => VecEncoding::Sq8,
16695 SqlVecEncoding::F16 => VecEncoding::F16,
16696 },
16697 },
16698 ColumnTypeName::Numeric(precision, scale) => DataType::Numeric { precision, scale },
16699 ColumnTypeName::Date => DataType::Date,
16700 ColumnTypeName::Timestamp => DataType::Timestamp,
16701 ColumnTypeName::Timestamptz => DataType::Timestamptz,
16702 ColumnTypeName::Json => DataType::Json,
16703 ColumnTypeName::Jsonb => DataType::Jsonb,
16704 ColumnTypeName::Bytes => DataType::Bytes,
16705 ColumnTypeName::TextArray => DataType::TextArray,
16706 ColumnTypeName::IntArray => DataType::IntArray,
16707 ColumnTypeName::BigIntArray => DataType::BigIntArray,
16708 ColumnTypeName::TsVector => DataType::TsVector,
16709 ColumnTypeName::TsQuery => DataType::TsQuery,
16710 ColumnTypeName::Uuid => DataType::Uuid,
16711 ColumnTypeName::Time => DataType::Time,
16712 ColumnTypeName::Year => DataType::Year,
16713 ColumnTypeName::TimeTz => DataType::TimeTz,
16714 ColumnTypeName::Money => DataType::Money,
16715 ColumnTypeName::Range(k) => DataType::Range(match k {
16716 spg_sql::ast::RangeKindAst::Int4 => spg_storage::RangeKind::Int4,
16717 spg_sql::ast::RangeKindAst::Int8 => spg_storage::RangeKind::Int8,
16718 spg_sql::ast::RangeKindAst::Num => spg_storage::RangeKind::Num,
16719 spg_sql::ast::RangeKindAst::Ts => spg_storage::RangeKind::Ts,
16720 spg_sql::ast::RangeKindAst::TsTz => spg_storage::RangeKind::TsTz,
16721 spg_sql::ast::RangeKindAst::Date => spg_storage::RangeKind::Date,
16722 }),
16723 ColumnTypeName::Hstore => DataType::Hstore,
16724 ColumnTypeName::IntArray2D => DataType::IntArray2D,
16725 ColumnTypeName::BigIntArray2D => DataType::BigIntArray2D,
16726 ColumnTypeName::TextArray2D => DataType::TextArray2D,
16727 }
16728}
16729
16730fn literal_expr_to_value(expr: Expr) -> Result<Value, EngineError> {
16734 match expr {
16735 Expr::Literal(l) => Ok(literal_to_value(l)),
16736 Expr::Cast { expr, target } => {
16737 let inner_value = literal_expr_to_value(*expr)?;
16738 crate::eval::cast_value(inner_value, target).map_err(EngineError::Eval)
16739 }
16740 Expr::Unary {
16741 op: UnOp::Neg,
16742 expr,
16743 } => match *expr {
16744 Expr::Literal(Literal::Integer(n)) => {
16745 let neg = n.checked_neg().ok_or_else(|| {
16748 EngineError::Unsupported("integer literal overflow on negation".into())
16749 })?;
16750 Ok(int_value_for(neg))
16751 }
16752 Expr::Literal(Literal::Float(x)) => Ok(Value::Float(-x)),
16753 other => Err(EngineError::Unsupported(alloc::format!(
16754 "unary minus over non-literal expression: {other:?}"
16755 ))),
16756 },
16757 Expr::Array(items) => {
16765 let mut materialised: alloc::vec::Vec<Value> =
16766 alloc::vec::Vec::with_capacity(items.len());
16767 for elem in items {
16768 materialised.push(literal_expr_to_value(elem)?);
16769 }
16770 Ok(array_literal_widen(materialised))
16771 }
16772 other => {
16785 let empty_schema: alloc::vec::Vec<spg_storage::ColumnSchema> = alloc::vec::Vec::new();
16786 let ctx = EvalContext::new(&empty_schema, None);
16787 let empty_row = spg_storage::Row::new(alloc::vec::Vec::new());
16788 crate::eval::eval_expr(&other, &empty_row, &ctx).map_err(EngineError::Eval)
16789 }
16790 }
16791}
16792
16793fn literal_to_value(l: Literal) -> Value {
16794 match l {
16795 Literal::Integer(n) => int_value_for(n),
16796 Literal::Float(x) => Value::Float(x),
16797 Literal::String(s) => Value::Text(s),
16798 Literal::Bool(b) => Value::Bool(b),
16799 Literal::Null => Value::Null,
16800 Literal::Vector(v) => Value::Vector(v),
16801 Literal::Interval { months, micros, .. } => Value::Interval { months, micros },
16802 }
16803}
16804
16805fn int_value_for(n: i64) -> Value {
16809 if let Ok(small) = i32::try_from(n) {
16810 Value::Int(small)
16811 } else {
16812 Value::BigInt(n)
16813 }
16814}
16815
16816#[allow(clippy::too_many_lines)]
16822fn check_unsigned_range(
16827 v: &Value,
16828 schema: &ColumnSchema,
16829 position: usize,
16830) -> Result<(), EngineError> {
16831 if !schema.is_unsigned {
16832 return Ok(());
16833 }
16834 let n = match v {
16835 Value::SmallInt(x) => i64::from(*x),
16836 Value::Int(x) => i64::from(*x),
16837 Value::BigInt(x) => *x,
16838 _ => return Ok(()), };
16840 if n < 0 {
16841 return Err(EngineError::Unsupported(alloc::format!(
16842 "column {:?} is UNSIGNED but got negative value {n} at position {position}",
16843 schema.name
16844 )));
16845 }
16846 Ok(())
16847}
16848
16849fn coerce_value(
16850 v: Value,
16851 expected: DataType,
16852 col_name: &str,
16853 position: usize,
16854) -> Result<Value, EngineError> {
16855 if v.is_null() {
16856 return Ok(Value::Null);
16857 }
16858 let actual = v.data_type().expect("non-null");
16859 if actual == expected {
16860 return Ok(v);
16861 }
16862 let coerced = match (v, expected) {
16863 (Value::Int(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16864 (Value::Int(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16865 (Value::Int(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16866 (Value::Int(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16867 i128::from(n),
16868 precision,
16869 scale,
16870 col_name,
16871 )?),
16872 (Value::SmallInt(n), DataType::Int) => Some(Value::Int(i32::from(n))),
16873 (Value::SmallInt(n), DataType::BigInt) => Some(Value::BigInt(i64::from(n))),
16874 (Value::SmallInt(n), DataType::Float) => Some(Value::Float(f64::from(n))),
16875 (Value::SmallInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16876 i128::from(n),
16877 precision,
16878 scale,
16879 col_name,
16880 )?),
16881 (Value::BigInt(n), DataType::Int) => i32::try_from(n).ok().map(Value::Int),
16882 (Value::BigInt(n), DataType::SmallInt) => i16::try_from(n).ok().map(Value::SmallInt),
16883 #[allow(clippy::cast_precision_loss)]
16884 (Value::BigInt(n), DataType::Float) => Some(Value::Float(n as f64)),
16885 (Value::BigInt(n), DataType::Numeric { precision, scale }) => Some(numeric_from_integer(
16886 i128::from(n),
16887 precision,
16888 scale,
16889 col_name,
16890 )?),
16891 (Value::Float(x), DataType::Numeric { precision, scale }) => {
16892 Some(numeric_from_float(x, precision, scale, col_name)?)
16893 }
16894 (Value::Text(s), DataType::Numeric { precision, scale }) => {
16905 let Some((mantissa, src_scale)) = parse_numeric_text(&s) else {
16906 return Err(EngineError::Eval(EvalError::TypeMismatch {
16907 detail: alloc::format!("cannot parse {s:?} as NUMERIC for column `{col_name}`"),
16908 }));
16909 };
16910 Some(numeric_rescale(
16911 mantissa, src_scale, precision, scale, col_name,
16912 )?)
16913 }
16914 (Value::Text(s), DataType::Date) => {
16916 let d = eval::parse_date_literal(&s).ok_or_else(|| {
16917 EngineError::Eval(EvalError::TypeMismatch {
16918 detail: alloc::format!("cannot parse {s:?} as DATE for column `{col_name}`"),
16919 })
16920 })?;
16921 Some(Value::Date(d))
16922 }
16923 (Value::Text(s), DataType::SmallInt) => s.parse::<i16>().ok().map(Value::SmallInt),
16930 (Value::Text(s), DataType::Int) => s.parse::<i32>().ok().map(Value::Int),
16931 (Value::Text(s), DataType::BigInt) => s.parse::<i64>().ok().map(Value::BigInt),
16932 (Value::Text(s), DataType::Float) => s.parse::<f64>().ok().map(Value::Float),
16933 (Value::Text(s), DataType::Bool) => match s.to_ascii_lowercase().as_str() {
16934 "0" | "false" | "f" | "no" | "off" => Some(Value::Bool(false)),
16935 "1" | "true" | "t" | "yes" | "on" => Some(Value::Bool(true)),
16936 _ => None,
16937 },
16938 (Value::Int(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16947 (Value::SmallInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16948 (Value::BigInt(n), DataType::Bool) => Some(Value::Bool(n != 0)),
16949 (Value::Text(s), DataType::Json | DataType::Jsonb) => Some(Value::Json(s)),
16953 (Value::Json(s), DataType::Text) => Some(Value::Text(s)),
16954 (Value::Json(s), DataType::Jsonb | DataType::Json) => Some(Value::Json(s)),
16962 (Value::Text(s), DataType::Bytes) => {
16969 let bytes = decode_bytea_literal(&s).map_err(|e| {
16970 EngineError::Eval(EvalError::TypeMismatch {
16971 detail: alloc::format!(
16972 "cannot parse {s:?} as BYTEA for column `{col_name}`: {e}"
16973 ),
16974 })
16975 })?;
16976 Some(Value::Bytes(bytes))
16977 }
16978 (Value::Bytes(b), DataType::Text) => Some(Value::Text(encode_bytea_hex(&b))),
16982 (Value::Text(s), DataType::Uuid) => match spg_storage::parse_uuid_str(&s) {
16990 Some(b) => Some(Value::Uuid(b)),
16991 None => {
16992 return Err(EngineError::Eval(EvalError::TypeMismatch {
16993 detail: alloc::format!(
16994 "invalid input syntax for type uuid: {s:?} (column `{col_name}`)"
16995 ),
16996 }));
16997 }
16998 },
16999 (Value::Uuid(b), DataType::Text) => Some(Value::Text(spg_storage::format_uuid(&b))),
17004 (Value::Text(s), DataType::Time) => match parse_time_str(&s) {
17010 Some(us) => Some(Value::Time(us)),
17011 None => {
17012 return Err(EngineError::Eval(EvalError::TypeMismatch {
17013 detail: alloc::format!(
17014 "invalid input syntax for type time: {s:?} (column `{col_name}`)"
17015 ),
17016 }));
17017 }
17018 },
17019 (Value::Time(us), DataType::Text) => Some(Value::Text(eval::format_time(us))),
17021 (Value::SmallInt(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17026 (Value::Int(n), DataType::Year) => Some(coerce_int_to_year(i64::from(n), col_name)?),
17027 (Value::BigInt(n), DataType::Year) => Some(coerce_int_to_year(n, col_name)?),
17028 (Value::Text(s), DataType::Year) => match s.trim().parse::<i64>() {
17032 Ok(n) => Some(coerce_int_to_year(n, col_name)?),
17033 Err(_) => {
17034 return Err(EngineError::Eval(EvalError::TypeMismatch {
17035 detail: alloc::format!(
17036 "invalid input syntax for type year: {s:?} (column `{col_name}`)"
17037 ),
17038 }));
17039 }
17040 },
17041 (Value::Year(y), DataType::Text) => Some(Value::Text(alloc::format!("{y:04}"))),
17043 (Value::Text(s), DataType::TimeTz) => match parse_timetz_str(&s) {
17047 Some((us, offset_secs)) => Some(Value::TimeTz { us, offset_secs }),
17048 None => {
17049 return Err(EngineError::Eval(EvalError::TypeMismatch {
17050 detail: alloc::format!(
17051 "invalid input syntax for type time with time zone: \
17052 {s:?} (column `{col_name}`)"
17053 ),
17054 }));
17055 }
17056 },
17057 (Value::TimeTz { us, offset_secs }, DataType::Text) => {
17059 Some(Value::Text(eval::format_timetz(us, offset_secs)))
17060 }
17061 (Value::Text(s), DataType::Money) => match parse_money_str(&s) {
17065 Some(c) => Some(Value::Money(c)),
17066 None => {
17067 return Err(EngineError::Eval(EvalError::TypeMismatch {
17068 detail: alloc::format!(
17069 "invalid input syntax for type money: {s:?} (column `{col_name}`)"
17070 ),
17071 }));
17072 }
17073 },
17074 (Value::SmallInt(n), DataType::Money) => {
17078 Some(Value::Money(i64::from(n).saturating_mul(100)))
17079 }
17080 (Value::Int(n), DataType::Money) => Some(Value::Money(i64::from(n).saturating_mul(100))),
17081 (Value::BigInt(n), DataType::Money) => Some(Value::Money(n.saturating_mul(100))),
17082 (Value::Float(x), DataType::Money) => {
17083 let scaled = x * 100.0;
17086 let cents = if scaled >= 0.0 {
17087 (scaled + 0.5) as i64
17088 } else {
17089 (scaled - 0.5) as i64
17090 };
17091 Some(Value::Money(cents))
17092 }
17093 (Value::Numeric { scaled, scale }, DataType::Money) => {
17094 let cents = if scale == 2 {
17097 scaled
17098 } else if scale < 2 {
17099 let mult = 10_i128.pow(u32::from(2 - scale));
17100 scaled.saturating_mul(mult)
17101 } else {
17102 let div = 10_i128.pow(u32::from(scale - 2));
17103 let half = div / 2;
17104 let bias = if scaled >= 0 { half } else { -half };
17105 (scaled + bias) / div
17106 };
17107 Some(Value::Money(i64::try_from(cents).unwrap_or(i64::MAX)))
17108 }
17109 (Value::Money(c), DataType::Text) => Some(Value::Text(eval::format_money(c))),
17111 (Value::Text(s), DataType::Range(kind)) => match parse_range_str(&s, kind) {
17115 Some(v) => Some(v),
17116 None => {
17117 return Err(EngineError::Eval(EvalError::TypeMismatch {
17118 detail: alloc::format!(
17119 "invalid input syntax for range type: {s:?} (column `{col_name}`)"
17120 ),
17121 }));
17122 }
17123 },
17124 (v @ Value::Range { .. }, DataType::Text) => Some(Value::Text(format_range_str(&v))),
17126 (Value::Text(s), DataType::Hstore) => match parse_hstore_str(&s) {
17128 Some(pairs) => Some(Value::Hstore(pairs)),
17129 None => {
17130 return Err(EngineError::Eval(EvalError::TypeMismatch {
17131 detail: alloc::format!(
17132 "invalid input syntax for type hstore: {s:?} (column `{col_name}`)"
17133 ),
17134 }));
17135 }
17136 },
17137 (Value::Hstore(pairs), DataType::Text) => Some(Value::Text(format_hstore_str(&pairs))),
17139 (Value::Text(s), DataType::IntArray2D) => match parse_int_2d_literal(&s) {
17142 Ok(m) => Some(Value::IntArray2D(m)),
17143 Err(e) => {
17144 return Err(EngineError::Eval(EvalError::TypeMismatch {
17145 detail: alloc::format!(
17146 "invalid input syntax for INT[][]: {s:?} (column `{col_name}`): {e}"
17147 ),
17148 }));
17149 }
17150 },
17151 (Value::Text(s), DataType::BigIntArray2D) => match parse_bigint_2d_literal(&s) {
17152 Ok(m) => Some(Value::BigIntArray2D(m)),
17153 Err(e) => {
17154 return Err(EngineError::Eval(EvalError::TypeMismatch {
17155 detail: alloc::format!(
17156 "invalid input syntax for BIGINT[][]: {s:?} (column `{col_name}`): {e}"
17157 ),
17158 }));
17159 }
17160 },
17161 (Value::Text(s), DataType::TextArray2D) => match parse_text_2d_literal(&s) {
17162 Ok(m) => Some(Value::TextArray2D(m)),
17163 Err(e) => {
17164 return Err(EngineError::Eval(EvalError::TypeMismatch {
17165 detail: alloc::format!(
17166 "invalid input syntax for TEXT[][]: {s:?} (column `{col_name}`): {e}"
17167 ),
17168 }));
17169 }
17170 },
17171 (Value::IntArray2D(rows), DataType::Text) => Some(Value::Text(format_int_2d_text(&rows))),
17173 (Value::BigIntArray2D(rows), DataType::Text) => {
17174 Some(Value::Text(format_bigint_2d_text(&rows)))
17175 }
17176 (Value::TextArray2D(rows), DataType::Text) => Some(Value::Text(format_text_2d_text(&rows))),
17177 (Value::Text(s), DataType::TextArray) => {
17182 let arr = decode_text_array_literal(&s).map_err(|e| {
17183 EngineError::Eval(EvalError::TypeMismatch {
17184 detail: alloc::format!(
17185 "cannot parse {s:?} as TEXT[] for column `{col_name}`: {e}"
17186 ),
17187 })
17188 })?;
17189 Some(Value::TextArray(arr))
17190 }
17191 (Value::Text(s), DataType::IntArray) => {
17197 let arr = decode_text_array_literal(&s).map_err(|e| {
17198 EngineError::Eval(EvalError::TypeMismatch {
17199 detail: alloc::format!(
17200 "cannot parse {s:?} as INT[] for column `{col_name}`: {e}"
17201 ),
17202 })
17203 })?;
17204 let mut out: Vec<Option<i32>> = Vec::with_capacity(arr.len());
17205 for elem in arr {
17206 match elem {
17207 None => out.push(None),
17208 Some(t) => {
17209 let n: i32 = t.parse().map_err(|_| {
17210 EngineError::Eval(EvalError::TypeMismatch {
17211 detail: alloc::format!(
17212 "cannot parse {t:?} as INT element for `{col_name}`"
17213 ),
17214 })
17215 })?;
17216 out.push(Some(n));
17217 }
17218 }
17219 }
17220 Some(Value::IntArray(out))
17221 }
17222 (Value::Text(s), DataType::BigIntArray) => {
17223 let arr = decode_text_array_literal(&s).map_err(|e| {
17224 EngineError::Eval(EvalError::TypeMismatch {
17225 detail: alloc::format!(
17226 "cannot parse {s:?} as BIGINT[] for column `{col_name}`: {e}"
17227 ),
17228 })
17229 })?;
17230 let mut out: Vec<Option<i64>> = Vec::with_capacity(arr.len());
17231 for elem in arr {
17232 match elem {
17233 None => out.push(None),
17234 Some(t) => {
17235 let n: i64 = t.parse().map_err(|_| {
17236 EngineError::Eval(EvalError::TypeMismatch {
17237 detail: alloc::format!(
17238 "cannot parse {t:?} as BIGINT element for `{col_name}`"
17239 ),
17240 })
17241 })?;
17242 out.push(Some(n));
17243 }
17244 }
17245 }
17246 Some(Value::BigIntArray(out))
17247 }
17248 (Value::TextArray(items), DataType::Text) => Some(Value::Text(encode_text_array(&items))),
17252 (Value::Text(s), DataType::Vector { dim, encoding }) => {
17261 let parsed = eval::parse_vector_text(&s).ok_or_else(|| {
17262 EngineError::Eval(EvalError::TypeMismatch {
17263 detail: alloc::format!("cannot parse {s:?} as VECTOR for column `{col_name}`"),
17264 })
17265 })?;
17266 if parsed.len() != dim as usize {
17267 return Err(EngineError::Eval(EvalError::TypeMismatch {
17268 detail: alloc::format!(
17269 "VECTOR({dim}) column `{col_name}` rejects literal of length {}",
17270 parsed.len()
17271 ),
17272 }));
17273 }
17274 Some(match encoding {
17275 VecEncoding::F32 => Value::Vector(parsed),
17276 VecEncoding::Sq8 => Value::Sq8Vector(spg_storage::quantize::quantize(&parsed)),
17277 VecEncoding::F16 => {
17278 Value::HalfVector(spg_storage::halfvec::HalfVector::from_f32_slice(&parsed))
17279 }
17280 })
17281 }
17282 (Value::Text(s), DataType::TsVector) => {
17292 let lexs = eval::decode_tsvector_external(&s).map_err(|e| {
17293 EngineError::Eval(EvalError::TypeMismatch {
17294 detail: alloc::format!(
17295 "cannot parse {s:?} as TSVECTOR for column `{col_name}`: {e}"
17296 ),
17297 })
17298 })?;
17299 Some(Value::TsVector(lexs))
17300 }
17301 (Value::Text(s), DataType::Timestamp | DataType::Timestamptz) => {
17302 let t = eval::parse_timestamp_literal(&s).ok_or_else(|| {
17303 EngineError::Eval(EvalError::TypeMismatch {
17304 detail: alloc::format!(
17305 "cannot parse {s:?} as TIMESTAMP for column `{col_name}`"
17306 ),
17307 })
17308 })?;
17309 Some(Value::Timestamp(t))
17310 }
17311 (Value::Date(d), DataType::Timestamp | DataType::Timestamptz) => {
17314 Some(Value::Timestamp(i64::from(d) * 86_400_000_000))
17315 }
17316 (Value::Timestamp(t), DataType::Timestamptz) => Some(Value::Timestamp(t)),
17320 (Value::Timestamp(t), DataType::Date) => {
17321 let days = t.div_euclid(86_400_000_000);
17322 i32::try_from(days).ok().map(Value::Date)
17323 }
17324 (
17325 Value::Numeric {
17326 scaled,
17327 scale: src_scale,
17328 },
17329 DataType::Numeric { precision, scale },
17330 ) => Some(numeric_rescale(
17331 scaled, src_scale, precision, scale, col_name,
17332 )?),
17333 #[allow(clippy::cast_precision_loss)]
17334 (Value::Numeric { scaled, scale }, DataType::Float) => {
17335 let mut div = 1.0_f64;
17336 for _ in 0..scale {
17337 div *= 10.0;
17338 }
17339 Some(Value::Float((scaled as f64) / div))
17340 }
17341 (Value::Numeric { scaled, scale }, DataType::Int) => {
17342 let truncated = numeric_truncate_to_integer(scaled, scale);
17343 i32::try_from(truncated).ok().map(Value::Int)
17344 }
17345 (Value::Numeric { scaled, scale }, DataType::BigInt) => {
17346 let truncated = numeric_truncate_to_integer(scaled, scale);
17347 i64::try_from(truncated).ok().map(Value::BigInt)
17348 }
17349 (Value::Numeric { scaled, scale }, DataType::SmallInt) => {
17350 let truncated = numeric_truncate_to_integer(scaled, scale);
17351 i16::try_from(truncated).ok().map(Value::SmallInt)
17352 }
17353 (Value::Text(s), DataType::Varchar(max)) => {
17355 if u32::try_from(s.chars().count()).unwrap_or(u32::MAX) <= max {
17356 Some(Value::Text(s))
17357 } else {
17358 return Err(EngineError::Unsupported(alloc::format!(
17359 "value for VARCHAR({max}) column `{col_name}` exceeds length: \
17360 {} chars",
17361 s.chars().count()
17362 )));
17363 }
17364 }
17365 (
17373 Value::Vector(v),
17374 DataType::Vector {
17375 dim,
17376 encoding: VecEncoding::Sq8,
17377 },
17378 ) if v.len() == dim as usize => Some(Value::Sq8Vector(spg_storage::quantize::quantize(&v))),
17379 (
17384 Value::Vector(v),
17385 DataType::Vector {
17386 dim,
17387 encoding: VecEncoding::F16,
17388 },
17389 ) if v.len() == dim as usize => Some(Value::HalfVector(
17390 spg_storage::halfvec::HalfVector::from_f32_slice(&v),
17391 )),
17392 (Value::Text(s), DataType::Char(size)) => {
17396 let len = u32::try_from(s.chars().count()).unwrap_or(u32::MAX);
17397 if len > size {
17398 return Err(EngineError::Unsupported(alloc::format!(
17399 "value for CHAR({size}) column `{col_name}` exceeds length: \
17400 {len} chars"
17401 )));
17402 }
17403 let need = (size - len) as usize;
17404 let mut padded = s;
17405 padded.reserve(need);
17406 for _ in 0..need {
17407 padded.push(' ');
17408 }
17409 Some(Value::Text(padded))
17410 }
17411 _ => None,
17412 };
17413 coerced.ok_or(EngineError::Storage(StorageError::TypeMismatch {
17414 column: col_name.into(),
17415 expected,
17416 actual,
17417 position,
17418 }))
17419}
17420
17421fn render_function_args(args: &[spg_sql::ast::FunctionArg]) -> alloc::string::String {
17427 use core::fmt::Write;
17428 let mut out = alloc::string::String::from("(");
17429 for (i, a) in args.iter().enumerate() {
17430 if i > 0 {
17431 out.push_str(", ");
17432 }
17433 match a.mode {
17434 spg_sql::ast::FunctionArgMode::In => {}
17435 spg_sql::ast::FunctionArgMode::Out => out.push_str("OUT "),
17436 spg_sql::ast::FunctionArgMode::InOut => out.push_str("INOUT "),
17437 }
17438 if let Some(n) = &a.name {
17439 out.push_str(n);
17440 out.push(' ');
17441 }
17442 match &a.ty {
17443 spg_sql::ast::FunctionArgType::Typed(t) => {
17444 let _ = write!(out, "{t}");
17445 }
17446 spg_sql::ast::FunctionArgType::Raw(s) => out.push_str(s),
17447 }
17448 }
17449 out.push(')');
17450 out
17451}
17452
17453fn is_top_level_unnest(expr: &spg_sql::ast::Expr) -> bool {
17462 match expr {
17463 spg_sql::ast::Expr::FunctionCall { name, args } => {
17464 name.eq_ignore_ascii_case("unnest") && args.len() == 1
17465 }
17466 _ => false,
17467 }
17468}
17469
17470fn top_level_unnest_arg(expr: &spg_sql::ast::Expr) -> Option<&spg_sql::ast::Expr> {
17474 match expr {
17475 spg_sql::ast::Expr::FunctionCall { name, args }
17476 if name.eq_ignore_ascii_case("unnest") && args.len() == 1 =>
17477 {
17478 Some(&args[0])
17479 }
17480 _ => None,
17481 }
17482}
17483
17484fn array_value_to_elements(v: &Value) -> Result<Vec<Value>, EngineError> {
17489 match v {
17490 Value::Null => Ok(Vec::new()),
17491 Value::TextArray(items) => Ok(items
17492 .iter()
17493 .map(|opt| {
17494 opt.as_ref()
17495 .map(|s| Value::Text(s.clone()))
17496 .unwrap_or(Value::Null)
17497 })
17498 .collect()),
17499 Value::IntArray(items) => Ok(items
17500 .iter()
17501 .map(|opt| opt.map(Value::Int).unwrap_or(Value::Null))
17502 .collect()),
17503 Value::BigIntArray(items) => Ok(items
17504 .iter()
17505 .map(|opt| opt.map(Value::BigInt).unwrap_or(Value::Null))
17506 .collect()),
17507 other => Err(EngineError::Eval(EvalError::TypeMismatch {
17508 detail: alloc::format!(
17509 "unnest() expects an array argument, got {:?}",
17510 other.data_type()
17511 ),
17512 })),
17513 }
17514}
17515
17516#[cfg(test)]
17517mod tests {
17518 use super::*;
17519 use alloc::vec;
17520
17521 fn unwrap_command_ok(r: &QueryResult) -> usize {
17522 match r {
17523 QueryResult::CommandOk { affected, .. } => *affected,
17524 QueryResult::Rows { .. } => panic!("expected CommandOk, got Rows"),
17525 }
17526 }
17527
17528 #[test]
17529 fn create_table_registers_schema() {
17530 let mut e = Engine::new();
17531 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT)")
17532 .unwrap();
17533 assert_eq!(e.catalog().table_count(), 1);
17534 let t = e.catalog().get("foo").unwrap();
17535 assert_eq!(t.schema().columns.len(), 2);
17536 assert_eq!(t.schema().columns[0].ty, DataType::Int);
17537 assert!(!t.schema().columns[0].nullable);
17538 assert_eq!(t.schema().columns[1].ty, DataType::Text);
17539 }
17540
17541 #[test]
17542 fn create_table_vector_default_is_f32_encoded() {
17543 let mut e = Engine::new();
17544 e.execute("CREATE TABLE t (v VECTOR(8))").unwrap();
17545 let t = e.catalog().get("t").unwrap();
17546 assert_eq!(
17547 t.schema().columns[0].ty,
17548 DataType::Vector {
17549 dim: 8,
17550 encoding: VecEncoding::F32,
17551 },
17552 );
17553 }
17554
17555 #[test]
17556 fn create_table_vector_using_sq8_succeeds() {
17557 let mut e = Engine::new();
17561 e.execute("CREATE TABLE t (v VECTOR(8) USING SQ8)").unwrap();
17562 let t = e.catalog().get("t").unwrap();
17563 assert_eq!(
17564 t.schema().columns[0].ty,
17565 DataType::Vector {
17566 dim: 8,
17567 encoding: VecEncoding::Sq8,
17568 },
17569 );
17570 }
17571
17572 #[test]
17573 fn insert_into_sq8_column_quantises_f32_payload() {
17574 let mut e = Engine::new();
17581 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17582 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17583 .unwrap();
17584 let t = e.catalog().get("t").unwrap();
17585 assert_eq!(t.rows().len(), 1);
17586 match &t.rows()[0].values[0] {
17587 Value::Sq8Vector(q) => {
17588 assert_eq!(q.bytes.len(), 4);
17589 assert!((q.min - 0.0).abs() < 1e-6);
17591 assert!((q.max - 1.0).abs() < 1e-6);
17592 }
17593 other => panic!("expected Sq8Vector cell, got {other:?}"),
17594 }
17595 }
17596
17597 #[test]
17598 fn create_table_vector_using_half_succeeds_and_insert_converts_to_f16() {
17599 let mut e = Engine::new();
17606 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17607 .unwrap();
17608 e.execute("INSERT INTO t VALUES ([0.0, 0.25, 0.5, 1.0])")
17609 .unwrap();
17610 let t = e.catalog().get("t").unwrap();
17611 assert_eq!(t.rows().len(), 1);
17612 match &t.rows()[0].values[0] {
17613 Value::HalfVector(h) => {
17614 assert_eq!(h.dim(), 4);
17615 let back = h.to_f32_vec();
17616 let expected = alloc::vec![0.0_f32, 0.25, 0.5, 1.0];
17617 for (g, e) in back.iter().zip(expected.iter()) {
17618 assert!(
17619 (g - e).abs() < 1e-6,
17620 "{g} vs {e} should be exact on f16 grid"
17621 );
17622 }
17623 }
17624 other => panic!("expected HalfVector cell, got {other:?}"),
17625 }
17626 }
17627
17628 #[test]
17629 fn alter_index_rebuild_in_place_succeeds() {
17630 let mut e = Engine::new();
17635 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
17636 .unwrap();
17637 for i in 0..8_i32 {
17638 #[allow(clippy::cast_precision_loss)]
17639 let base = (i as f32) * 0.1;
17640 e.execute(&alloc::format!(
17641 "INSERT INTO t VALUES ({i}, [{base}, {b1}, {b2}])",
17642 b1 = base + 0.01,
17643 b2 = base + 0.02,
17644 ))
17645 .unwrap();
17646 }
17647 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17648 e.execute("ALTER INDEX t_idx REBUILD").unwrap();
17649 assert_eq!(
17651 e.catalog().get("t").unwrap().schema().columns[1].ty,
17652 DataType::Vector {
17653 dim: 3,
17654 encoding: VecEncoding::F32,
17655 },
17656 );
17657 }
17658
17659 #[test]
17660 fn alter_index_rebuild_with_encoding_switches_cell_type() {
17661 let mut e = Engine::new();
17666 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(4) NOT NULL)")
17667 .unwrap();
17668 e.execute("INSERT INTO t VALUES (1, [0.0, 0.25, 0.5, 1.0])")
17669 .unwrap();
17670 e.execute("CREATE INDEX t_idx ON t USING hnsw (v)").unwrap();
17671 e.execute("ALTER INDEX t_idx REBUILD WITH (encoding = SQ8)")
17672 .unwrap();
17673 let t = e.catalog().get("t").unwrap();
17674 assert_eq!(
17675 t.schema().columns[1].ty,
17676 DataType::Vector {
17677 dim: 4,
17678 encoding: VecEncoding::Sq8,
17679 },
17680 );
17681 assert!(matches!(t.rows()[0].values[1], Value::Sq8Vector(_)));
17682 }
17683
17684 #[test]
17685 fn alter_index_rebuild_unknown_index_errors() {
17686 let mut e = Engine::new();
17687 let err = e.execute("ALTER INDEX nope REBUILD").unwrap_err();
17688 assert!(
17689 matches!(
17690 &err,
17691 EngineError::Storage(StorageError::IndexNotFound { name }) if name == "nope"
17692 ),
17693 "got: {err}"
17694 );
17695 }
17696
17697 #[test]
17698 fn alter_index_rebuild_on_btree_index_errors() {
17699 let mut e = Engine::new();
17702 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17703 e.execute("INSERT INTO t VALUES (1)").unwrap();
17704 e.execute("CREATE INDEX t_idx ON t (id)").unwrap();
17705 let err = e.execute("ALTER INDEX t_idx REBUILD").unwrap_err();
17706 assert!(
17707 matches!(&err, EngineError::Storage(StorageError::Unsupported(_))),
17708 "got: {err}"
17709 );
17710 }
17711
17712 #[test]
17713 fn prepared_insert_substitutes_placeholders() {
17714 let mut e = Engine::new();
17720 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17721 .unwrap();
17722 let stmt = e.prepare("INSERT INTO t VALUES ($1, $2)").unwrap();
17723 for (id, name) in [(1, "alice"), (2, "bob"), (3, "carol")] {
17724 e.execute_prepared(stmt.clone(), &[Value::Int(id), Value::Text(name.into())])
17725 .unwrap();
17726 }
17727 let rows_result = e.execute("SELECT id, name FROM t").unwrap();
17729 let QueryResult::Rows { rows, .. } = rows_result else {
17730 panic!("expected Rows")
17731 };
17732 assert_eq!(rows.len(), 3);
17733 }
17734
17735 #[test]
17736 fn prepared_select_with_placeholder_filters_rows() {
17737 let mut e = Engine::new();
17738 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17739 .unwrap();
17740 for i in 0..10_i32 {
17741 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17742 .unwrap();
17743 }
17744 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17745 let QueryResult::Rows { rows, .. } = e.execute_prepared(stmt, &[Value::Int(35)]).unwrap()
17746 else {
17747 panic!("expected Rows")
17748 };
17749 assert_eq!(rows.len(), 1);
17751 assert_eq!(rows[0].values[0], Value::Int(5));
17752 }
17753
17754 #[test]
17755 fn prepared_too_few_params_errors() {
17756 let mut e = Engine::new();
17757 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17758 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17759 let err = e.execute_prepared(stmt, &[]).unwrap_err();
17760 assert!(
17761 matches!(
17762 &err,
17763 EngineError::Eval(EvalError::PlaceholderOutOfRange { n: 1, bound: 0 })
17764 ),
17765 "got: {err}"
17766 );
17767 }
17768
17769 #[test]
17770 fn bytea_cast_round_trips_text_input() {
17771 let e = Engine::new();
17774 let r = e.execute_readonly("SELECT 'hello'::bytea").unwrap();
17775 let QueryResult::Rows { rows, .. } = r else {
17776 panic!("expected Rows")
17777 };
17778 assert_eq!(rows.len(), 1);
17779 assert_eq!(rows[0].values[0], Value::Bytes(b"hello".to_vec()));
17780 }
17781
17782 #[test]
17783 fn bytea_cast_pg_escape_hex_form() {
17784 let e = Engine::new();
17788 let r = e.execute_readonly(r"SELECT E'\\xdeadbeef'::bytea").unwrap();
17789 let QueryResult::Rows { rows, .. } = r else {
17790 panic!("expected Rows")
17791 };
17792 assert_eq!(
17793 rows[0].values[0],
17794 Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef])
17795 );
17796 }
17797
17798 #[test]
17799 fn bytea_cast_chains_through_octet_length() {
17800 let e = Engine::new();
17804 let r = e
17805 .execute_readonly("SELECT octet_length('hello'::bytea)")
17806 .unwrap();
17807 let QueryResult::Rows { rows, .. } = r else {
17808 panic!("expected Rows")
17809 };
17810 match &rows[0].values[0] {
17811 Value::Int(n) => assert_eq!(*n, 5),
17812 Value::BigInt(n) => assert_eq!(*n, 5),
17813 other => panic!("expected integer length, got {other:?}"),
17814 }
17815 }
17816
17817 #[test]
17818 fn readonly_prepared_on_snapshot_select_with_placeholder() {
17819 let mut e = Engine::new();
17825 e.execute("CREATE TABLE t (id INT NOT NULL, v INT NOT NULL)")
17826 .unwrap();
17827 for i in 0..10_i32 {
17828 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, {})", i * 7))
17829 .unwrap();
17830 }
17831 let snapshot = e.clone_snapshot();
17832 let stmt = e.prepare("SELECT id FROM t WHERE v = $1").unwrap();
17833 let QueryResult::Rows { rows, .. } =
17834 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(35)])
17835 .unwrap()
17836 else {
17837 panic!("expected Rows")
17838 };
17839 assert_eq!(rows.len(), 1);
17840 assert_eq!(rows[0].values[0], Value::Int(5));
17841 }
17842
17843 #[test]
17844 fn readonly_prepared_on_snapshot_rejects_writes() {
17845 let mut e = Engine::new();
17849 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17850 let snapshot = e.clone_snapshot();
17851 let stmt = e.prepare("INSERT INTO t VALUES ($1)").unwrap();
17852 let err = Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(1)])
17853 .unwrap_err();
17854 assert!(matches!(&err, EngineError::WriteRequired), "got: {err}");
17855 }
17856
17857 #[test]
17858 fn readonly_prepared_on_snapshot_frozen_view() {
17859 let mut e = Engine::new();
17865 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
17866 e.execute("INSERT INTO t VALUES (1)").unwrap();
17867 let snapshot = e.clone_snapshot();
17868 e.execute("INSERT INTO t VALUES (2)").unwrap();
17869 let stmt = e.prepare("SELECT id FROM t WHERE id = $1").unwrap();
17870 let QueryResult::Rows { rows, .. } =
17871 Engine::execute_readonly_prepared_on_snapshot(&snapshot, stmt, &[Value::Int(2)])
17872 .unwrap()
17873 else {
17874 panic!("expected Rows")
17875 };
17876 assert!(rows.is_empty(), "id=2 was inserted after snapshot");
17877 }
17878
17879 #[test]
17880 fn describe_prepared_on_snapshot_resolves_columns() {
17881 let mut e = Engine::new();
17886 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT NOT NULL)")
17887 .unwrap();
17888 let snapshot = e.clone_snapshot();
17889 let stmt = e.prepare("SELECT id, name FROM t WHERE id = $1").unwrap();
17890 let (_params, cols) = Engine::describe_prepared_on_snapshot(&snapshot, &stmt);
17891 assert_eq!(cols.len(), 2);
17892 assert_eq!(cols[0].name, "id");
17893 assert_eq!(cols[0].ty, DataType::Int);
17894 assert_eq!(cols[1].name, "name");
17895 assert_eq!(cols[1].ty, DataType::Text);
17896 }
17897
17898 #[test]
17899 fn insert_into_half_column_dim_mismatch_errors() {
17900 let mut e = Engine::new();
17901 e.execute("CREATE TABLE t (v VECTOR(4) USING HALF)")
17902 .unwrap();
17903 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
17904 assert!(matches!(
17905 &err,
17906 EngineError::Storage(StorageError::TypeMismatch { .. })
17907 ));
17908 }
17909
17910 #[test]
17911 fn insert_into_sq8_column_dim_mismatch_errors() {
17912 let mut e = Engine::new();
17917 e.execute("CREATE TABLE t (v VECTOR(4) USING SQ8)").unwrap();
17918 let err = e.execute("INSERT INTO t VALUES ([1.0, 2.0])").unwrap_err();
17919 assert!(
17920 matches!(
17921 &err,
17922 EngineError::Storage(StorageError::TypeMismatch { .. })
17923 ),
17924 "got: {err}",
17925 );
17926 }
17927
17928 #[test]
17929 fn create_table_duplicate_errors() {
17930 let mut e = Engine::new();
17931 e.execute("CREATE TABLE foo (a INT)").unwrap();
17932 let err = e.execute("CREATE TABLE foo (a INT)").unwrap_err();
17933 assert!(matches!(
17934 err,
17935 EngineError::Storage(StorageError::DuplicateTable { ref name }) if name == "foo"
17936 ));
17937 }
17938
17939 #[test]
17940 fn insert_into_unknown_table_errors() {
17941 let mut e = Engine::new();
17942 let err = e.execute("INSERT INTO ghost VALUES (1)").unwrap_err();
17943 assert!(matches!(
17944 err,
17945 EngineError::Storage(StorageError::TableNotFound { ref name }) if name == "ghost"
17946 ));
17947 }
17948
17949 #[test]
17950 fn insert_happy_path_reports_one_affected() {
17951 let mut e = Engine::new();
17952 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17953 let r = e.execute("INSERT INTO foo VALUES (42)").unwrap();
17954 assert_eq!(unwrap_command_ok(&r), 1);
17955 assert_eq!(e.catalog().get("foo").unwrap().row_count(), 1);
17956 }
17957
17958 #[test]
17959 fn insert_arity_mismatch_propagates() {
17960 let mut e = Engine::new();
17961 e.execute("CREATE TABLE foo (a INT, b TEXT)").unwrap();
17962 let err = e.execute("INSERT INTO foo VALUES (1)").unwrap_err();
17963 assert!(matches!(
17964 err,
17965 EngineError::Storage(StorageError::ArityMismatch { .. })
17966 ));
17967 }
17968
17969 #[test]
17970 fn insert_negative_integer_via_unary_minus() {
17971 let mut e = Engine::new();
17972 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17973 e.execute("INSERT INTO foo VALUES (-7)").unwrap();
17974 let rows = e.catalog().get("foo").unwrap().rows();
17975 assert_eq!(rows[0].values[0], Value::Int(-7));
17976 }
17977
17978 #[test]
17979 fn insert_expression_evaluated_against_empty_context() {
17980 let mut e = Engine::new();
17985 e.execute("CREATE TABLE foo (a INT NOT NULL)").unwrap();
17986 e.execute("INSERT INTO foo VALUES (1 + 2)").unwrap();
17987 let rows = e.catalog().get("foo").unwrap().rows();
17988 assert_eq!(rows[0].values[0], Value::Int(3));
17989 }
17990
17991 #[test]
17992 fn select_star_returns_all_rows_in_insertion_order() {
17993 let mut e = Engine::new();
17994 e.execute("CREATE TABLE foo (a INT NOT NULL, b TEXT NOT NULL)")
17995 .unwrap();
17996 e.execute("INSERT INTO foo VALUES (1, 'one')").unwrap();
17997 e.execute("INSERT INTO foo VALUES (2, 'two')").unwrap();
17998 e.execute("INSERT INTO foo VALUES (3, 'three')").unwrap();
17999
18000 let r = e.execute("SELECT * FROM foo").unwrap();
18001 let QueryResult::Rows { columns, rows } = r else {
18002 panic!("expected Rows")
18003 };
18004 assert_eq!(columns.len(), 2);
18005 assert_eq!(columns[0].name, "a");
18006 assert_eq!(rows.len(), 3);
18007 assert_eq!(
18008 rows[1].values,
18009 vec![Value::Int(2), Value::Text("two".into())]
18010 );
18011 }
18012
18013 #[test]
18014 fn select_star_on_empty_table_returns_zero_rows() {
18015 let mut e = Engine::new();
18016 e.execute("CREATE TABLE foo (a INT)").unwrap();
18017 let r = e.execute("SELECT * FROM foo").unwrap();
18018 match r {
18019 QueryResult::Rows { rows, .. } => assert!(rows.is_empty()),
18020 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18021 }
18022 }
18023
18024 fn make_three_row_users(e: &mut Engine) {
18027 e.execute("CREATE TABLE users (id INT NOT NULL, name TEXT NOT NULL, score INT)")
18028 .unwrap();
18029 e.execute("INSERT INTO users VALUES (1, 'alice', 90)")
18030 .unwrap();
18031 e.execute("INSERT INTO users VALUES (2, 'bob', NULL)")
18032 .unwrap();
18033 e.execute("INSERT INTO users VALUES (3, 'cara', 70)")
18034 .unwrap();
18035 }
18036
18037 fn unwrap_rows(r: QueryResult) -> (Vec<ColumnSchema>, Vec<Row>) {
18038 match r {
18039 QueryResult::Rows { columns, rows } => (columns, rows),
18040 QueryResult::CommandOk { .. } => panic!("expected Rows"),
18041 }
18042 }
18043
18044 #[test]
18045 fn where_filter_passes_only_true_rows() {
18046 let mut e = Engine::new();
18047 make_three_row_users(&mut e);
18048 let r = e.execute("SELECT * FROM users WHERE id > 1").unwrap();
18049 let (_, rows) = unwrap_rows(r);
18050 assert_eq!(rows.len(), 2);
18051 assert_eq!(rows[0].values[0], Value::Int(2));
18052 assert_eq!(rows[1].values[0], Value::Int(3));
18053 }
18054
18055 #[test]
18056 fn where_with_null_result_filters_out_row() {
18057 let mut e = Engine::new();
18058 make_three_row_users(&mut e);
18059 let r = e.execute("SELECT * FROM users WHERE score > 80").unwrap();
18061 let (_, rows) = unwrap_rows(r);
18062 assert_eq!(rows.len(), 1);
18063 assert_eq!(rows[0].values[1], Value::Text("alice".into()));
18064 }
18065
18066 #[test]
18067 fn projection_named_columns() {
18068 let mut e = Engine::new();
18069 make_three_row_users(&mut e);
18070 let r = e.execute("SELECT name, score FROM users").unwrap();
18071 let (cols, rows) = unwrap_rows(r);
18072 assert_eq!(cols.len(), 2);
18073 assert_eq!(cols[0].name, "name");
18074 assert_eq!(cols[1].name, "score");
18075 assert_eq!(rows.len(), 3);
18076 assert_eq!(
18077 rows[0].values,
18078 vec![Value::Text("alice".into()), Value::Int(90)]
18079 );
18080 }
18081
18082 #[test]
18083 fn projection_with_column_alias() {
18084 let mut e = Engine::new();
18085 make_three_row_users(&mut e);
18086 let r = e
18087 .execute("SELECT name AS who FROM users WHERE id = 1")
18088 .unwrap();
18089 let (cols, rows) = unwrap_rows(r);
18090 assert_eq!(cols[0].name, "who");
18091 assert_eq!(rows.len(), 1);
18092 assert_eq!(rows[0].values[0], Value::Text("alice".into()));
18093 }
18094
18095 #[test]
18096 fn qualified_column_with_table_alias_resolves() {
18097 let mut e = Engine::new();
18098 make_three_row_users(&mut e);
18099 let r = e
18100 .execute("SELECT u.id, u.name FROM users AS u WHERE u.id < 3")
18101 .unwrap();
18102 let (cols, rows) = unwrap_rows(r);
18103 assert_eq!(cols.len(), 2);
18104 assert_eq!(rows.len(), 2);
18105 }
18106
18107 #[test]
18108 fn qualified_column_with_wrong_alias_errors() {
18109 let mut e = Engine::new();
18110 make_three_row_users(&mut e);
18111 let err = e.execute("SELECT x.id FROM users AS u").unwrap_err();
18112 assert!(matches!(
18113 err,
18114 EngineError::Eval(EvalError::UnknownQualifier { ref qualifier }) if qualifier == "x"
18115 ));
18116 }
18117
18118 #[test]
18119 fn select_unknown_column_errors_in_projection() {
18120 let mut e = Engine::new();
18121 make_three_row_users(&mut e);
18122 let err = e.execute("SELECT ghost FROM users").unwrap_err();
18123 assert!(matches!(
18124 err,
18125 EngineError::Eval(EvalError::ColumnNotFound { ref name }) if name == "ghost"
18126 ));
18127 }
18128
18129 #[test]
18130 fn where_unknown_column_errors() {
18131 let mut e = Engine::new();
18132 make_three_row_users(&mut e);
18133 let err = e
18134 .execute("SELECT * FROM users WHERE ghost = 1")
18135 .unwrap_err();
18136 assert!(matches!(
18137 err,
18138 EngineError::Eval(EvalError::ColumnNotFound { .. })
18139 ));
18140 }
18141
18142 #[test]
18143 fn expression_projection_evaluates_and_renders() {
18144 let mut e = Engine::new();
18147 e.execute("CREATE TABLE t (a INT NOT NULL)").unwrap();
18148 e.execute("INSERT INTO t VALUES (3)").unwrap();
18149 let (_, rows) = unwrap_rows(e.execute("SELECT 1 + 2 FROM t").unwrap());
18150 assert_eq!(rows.len(), 1);
18151 assert_eq!(rows[0].values[0], Value::Int(3));
18154 }
18155
18156 #[test]
18157 fn select_unknown_table_errors() {
18158 let mut e = Engine::new();
18159 let err = e.execute("SELECT * FROM ghost").unwrap_err();
18160 assert!(matches!(
18161 err,
18162 EngineError::Storage(StorageError::TableNotFound { .. })
18163 ));
18164 }
18165
18166 #[test]
18167 fn invalid_sql_returns_parse_error() {
18168 let mut e = Engine::new();
18171 let err = e.execute("THIS_IS_NOT_A_KEYWORD foo bar baz").unwrap_err();
18172 assert!(matches!(err, EngineError::Parse(_)));
18173 }
18174
18175 #[test]
18178 fn create_index_registers_on_table() {
18179 let mut e = Engine::new();
18180 make_three_row_users(&mut e);
18181 e.execute("CREATE INDEX by_name ON users (name)").unwrap();
18182 let t = e.catalog().get("users").unwrap();
18183 assert_eq!(t.indices().len(), 1);
18184 assert_eq!(t.indices()[0].name, "by_name");
18185 }
18186
18187 #[test]
18188 fn create_index_on_unknown_table_errors() {
18189 let mut e = Engine::new();
18190 let err = e.execute("CREATE INDEX i ON ghost (a)").unwrap_err();
18191 assert!(matches!(
18192 err,
18193 EngineError::Storage(StorageError::TableNotFound { .. })
18194 ));
18195 }
18196
18197 #[test]
18198 fn create_index_on_unknown_column_errors() {
18199 let mut e = Engine::new();
18200 make_three_row_users(&mut e);
18201 let err = e.execute("CREATE INDEX i ON users (ghost)").unwrap_err();
18202 assert!(matches!(
18203 err,
18204 EngineError::Storage(StorageError::ColumnNotFound { .. })
18205 ));
18206 }
18207
18208 #[test]
18209 fn select_eq_uses_index_returns_same_rows_as_scan() {
18210 let mut without = Engine::new();
18214 make_three_row_users(&mut without);
18215 let mut with = Engine::new();
18216 make_three_row_users(&mut with);
18217 with.execute("CREATE INDEX by_id ON users (id)").unwrap();
18218
18219 let q = "SELECT * FROM users WHERE id = 2";
18220 let (_, no_idx_rows) = unwrap_rows(without.execute(q).unwrap());
18221 let (_, idx_rows) = unwrap_rows(with.execute(q).unwrap());
18222 assert_eq!(no_idx_rows, idx_rows);
18223 assert_eq!(idx_rows.len(), 1);
18224 }
18225
18226 #[test]
18227 fn select_eq_with_no_matching_index_value_returns_empty() {
18228 let mut e = Engine::new();
18229 make_three_row_users(&mut e);
18230 e.execute("CREATE INDEX by_id ON users (id)").unwrap();
18231 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM users WHERE id = 999").unwrap());
18232 assert_eq!(rows.len(), 0);
18233 }
18234
18235 #[test]
18238 fn begin_sets_in_transaction_flag() {
18239 let mut e = Engine::new();
18240 assert!(!e.in_transaction());
18241 e.execute("BEGIN").unwrap();
18242 assert!(e.in_transaction());
18243 }
18244
18245 #[test]
18246 fn double_begin_errors() {
18247 let mut e = Engine::new();
18248 e.execute("BEGIN").unwrap();
18249 let err = e.execute("BEGIN").unwrap_err();
18250 assert_eq!(err, EngineError::TransactionAlreadyOpen);
18251 }
18252
18253 #[test]
18254 fn commit_without_begin_errors() {
18255 let mut e = Engine::new();
18256 let err = e.execute("COMMIT").unwrap_err();
18257 assert_eq!(err, EngineError::NoActiveTransaction);
18258 }
18259
18260 #[test]
18261 fn rollback_without_begin_errors() {
18262 let mut e = Engine::new();
18263 let err = e.execute("ROLLBACK").unwrap_err();
18264 assert_eq!(err, EngineError::NoActiveTransaction);
18265 }
18266
18267 #[test]
18268 fn commit_applies_shadow_to_committed_catalog() {
18269 let mut e = Engine::new();
18270 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18271 e.execute("BEGIN").unwrap();
18272 e.execute("INSERT INTO t VALUES (1)").unwrap();
18273 e.execute("INSERT INTO t VALUES (2)").unwrap();
18274 e.execute("COMMIT").unwrap();
18275 assert!(!e.in_transaction());
18276 assert_eq!(e.catalog().get("t").unwrap().row_count(), 2);
18277 }
18278
18279 #[test]
18280 fn rollback_discards_shadow() {
18281 let mut e = Engine::new();
18282 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18283 e.execute("BEGIN").unwrap();
18284 e.execute("INSERT INTO t VALUES (1)").unwrap();
18285 e.execute("INSERT INTO t VALUES (2)").unwrap();
18286 e.execute("ROLLBACK").unwrap();
18287 assert!(!e.in_transaction());
18288 assert_eq!(e.catalog().get("t").unwrap().row_count(), 0);
18289 }
18290
18291 #[test]
18292 fn select_during_tx_sees_uncommitted_writes_own_session() {
18293 let mut e = Engine::new();
18296 e.execute("CREATE TABLE t (v INT NOT NULL)").unwrap();
18297 e.execute("BEGIN").unwrap();
18298 e.execute("INSERT INTO t VALUES (42)").unwrap();
18299 let (_, rows) = unwrap_rows(e.execute("SELECT * FROM t").unwrap());
18300 assert_eq!(rows.len(), 1);
18301 assert_eq!(rows[0].values[0], Value::Int(42));
18302 }
18303
18304 #[test]
18305 fn snapshot_with_no_users_is_bare_catalog_format() {
18306 let mut e = Engine::new();
18307 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18308 let bytes = e.snapshot();
18309 assert_eq!(
18310 &bytes[..8],
18311 b"SPGDB001",
18312 "must be the bare v3.x catalog magic"
18313 );
18314 let e2 = Engine::restore_envelope(&bytes).unwrap();
18315 assert!(e2.users().is_empty());
18316 assert_eq!(e2.catalog().table_count(), 1);
18317 }
18318
18319 #[test]
18320 fn snapshot_with_users_round_trips_both_via_envelope() {
18321 let mut e = Engine::new();
18322 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18323 e.create_user("alice", "pw1", Role::Admin, [9; 16]).unwrap();
18324 e.create_user("bob", "pw2", Role::ReadOnly, [5; 16])
18325 .unwrap();
18326 let bytes = e.snapshot();
18327 assert_eq!(&bytes[..8], b"SPGENV01", "must be the v4.1 envelope magic");
18328 let e2 = Engine::restore_envelope(&bytes).unwrap();
18329 assert_eq!(e2.users().len(), 2);
18330 assert_eq!(e2.verify_user("alice", "pw1"), Some(Role::Admin));
18331 assert_eq!(e2.verify_user("bob", "pw2"), Some(Role::ReadOnly));
18332 assert_eq!(e2.verify_user("alice", "wrong"), None);
18333 assert_eq!(e2.catalog().table_count(), 1);
18334 }
18335
18336 #[test]
18337 fn ddl_inside_tx_also_rolled_back() {
18338 let mut e = Engine::new();
18339 e.execute("BEGIN").unwrap();
18340 e.execute("CREATE TABLE t (v INT)").unwrap();
18341 e.execute("SELECT * FROM t").unwrap();
18343 e.execute("ROLLBACK").unwrap();
18344 let err = e.execute("SELECT * FROM t").unwrap_err();
18346 assert!(matches!(
18347 err,
18348 EngineError::Storage(StorageError::TableNotFound { .. })
18349 ));
18350 }
18351
18352 #[test]
18355 fn create_publication_lands_in_catalog() {
18356 let mut e = Engine::new();
18357 assert!(e.publications().is_empty());
18358 e.execute("CREATE PUBLICATION pub_a").unwrap();
18359 assert_eq!(e.publications().len(), 1);
18360 assert!(e.publications().contains("pub_a"));
18361 }
18362
18363 #[test]
18364 fn create_publication_duplicate_errors() {
18365 let mut e = Engine::new();
18366 e.execute("CREATE PUBLICATION pub_a").unwrap();
18367 let err = e.execute("CREATE PUBLICATION pub_a").unwrap_err();
18368 assert!(
18369 alloc::format!("{err:?}").contains("DuplicateName"),
18370 "got {err:?}"
18371 );
18372 }
18373
18374 #[test]
18375 fn drop_publication_silent_when_absent() {
18376 let mut e = Engine::new();
18377 let r = e.execute("DROP PUBLICATION nope").unwrap();
18380 match r {
18381 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18382 other => panic!("expected CommandOk, got {other:?}"),
18383 }
18384 }
18385
18386 #[test]
18387 fn drop_publication_present_reports_one_affected() {
18388 let mut e = Engine::new();
18389 e.execute("CREATE PUBLICATION pub_a").unwrap();
18390 let r = e.execute("DROP PUBLICATION pub_a").unwrap();
18391 match r {
18392 QueryResult::CommandOk {
18393 affected,
18394 modified_catalog,
18395 } => {
18396 assert_eq!(affected, 1);
18397 assert!(modified_catalog);
18398 }
18399 other => panic!("expected CommandOk, got {other:?}"),
18400 }
18401 assert!(e.publications().is_empty());
18402 }
18403
18404 #[test]
18405 fn publications_persist_across_snapshot_restore() {
18406 let mut e = Engine::new();
18411 e.execute("CREATE PUBLICATION pub_a").unwrap();
18412 e.execute("CREATE PUBLICATION pub_b FOR ALL TABLES")
18413 .unwrap();
18414 let snap = e.snapshot();
18415 let e2 = Engine::restore_envelope(&snap).unwrap();
18416 assert_eq!(e2.publications().len(), 2);
18417 assert!(e2.publications().contains("pub_a"));
18418 assert!(e2.publications().contains("pub_b"));
18419 }
18420
18421 #[test]
18422 fn create_publication_allowed_inside_transaction() {
18423 let mut e = Engine::new();
18427 e.execute("BEGIN").unwrap();
18428 e.execute("CREATE PUBLICATION pub_a").unwrap();
18429 e.execute("COMMIT").unwrap();
18430 assert!(e.publications().contains("pub_a"));
18431 }
18432
18433 #[test]
18436 fn create_publication_for_table_list_lands_with_scope() {
18437 let mut e = Engine::new();
18438 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18439 e.execute("CREATE TABLE t2 (id INT NOT NULL)").unwrap();
18440 e.execute("CREATE PUBLICATION pub_a FOR TABLE t1, t2")
18441 .unwrap();
18442 let scope = e.publications().get("pub_a").cloned();
18443 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = scope else {
18444 panic!("expected ForTables scope, got {scope:?}")
18445 };
18446 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18447 }
18448
18449 #[test]
18450 fn create_publication_all_tables_except_lands_with_scope() {
18451 let mut e = Engine::new();
18452 e.execute("CREATE PUBLICATION pub_a FOR ALL TABLES EXCEPT t3")
18453 .unwrap();
18454 let scope = e.publications().get("pub_a").cloned();
18455 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = scope else {
18456 panic!("expected AllTablesExcept scope, got {scope:?}")
18457 };
18458 assert_eq!(ts, alloc::vec!["t3".to_string()]);
18459 }
18460
18461 #[test]
18462 fn show_publications_empty_returns_zero_rows() {
18463 let e = Engine::new();
18464 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18465 let QueryResult::Rows { rows, columns } = r else {
18466 panic!()
18467 };
18468 assert!(rows.is_empty());
18469 assert_eq!(columns.len(), 3);
18470 assert_eq!(columns[0].name, "name");
18471 assert_eq!(columns[1].name, "scope");
18472 assert_eq!(columns[2].name, "table_count");
18473 }
18474
18475 #[test]
18476 fn show_publications_returns_one_row_per_publication_ordered_by_name() {
18477 let mut e = Engine::new();
18478 e.execute("CREATE PUBLICATION z_pub").unwrap();
18479 e.execute("CREATE PUBLICATION a_pub FOR TABLE t1, t2")
18480 .unwrap();
18481 e.execute("CREATE PUBLICATION m_pub FOR ALL TABLES EXCEPT bad")
18482 .unwrap();
18483 let r = e.execute_readonly("SHOW PUBLICATIONS").unwrap();
18484 let QueryResult::Rows { rows, .. } = r else {
18485 panic!()
18486 };
18487 assert_eq!(rows.len(), 3);
18488 let names: Vec<&str> = rows
18490 .iter()
18491 .map(|r| {
18492 if let Value::Text(s) = &r.values[0] {
18493 s.as_str()
18494 } else {
18495 panic!()
18496 }
18497 })
18498 .collect();
18499 assert_eq!(names, alloc::vec!["a_pub", "m_pub", "z_pub"]);
18500 match &rows[0].values[1] {
18502 Value::Text(s) => assert_eq!(s, "FOR TABLE t1, t2"),
18503 other => panic!("expected Text, got {other:?}"),
18504 }
18505 assert_eq!(rows[0].values[2], Value::Int(2));
18506 match &rows[1].values[1] {
18508 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES EXCEPT bad"),
18509 other => panic!("expected Text, got {other:?}"),
18510 }
18511 assert_eq!(rows[1].values[2], Value::Int(1));
18512 match &rows[2].values[1] {
18514 Value::Text(s) => assert_eq!(s, "FOR ALL TABLES"),
18515 other => panic!("expected Text, got {other:?}"),
18516 }
18517 assert_eq!(rows[2].values[2], Value::Null);
18518 }
18519
18520 #[test]
18521 fn for_list_scopes_persist_across_snapshot() {
18522 let mut e = Engine::new();
18525 e.execute("CREATE PUBLICATION p1 FOR TABLE t1, t2").unwrap();
18526 e.execute("CREATE PUBLICATION p2 FOR ALL TABLES EXCEPT bad, worse")
18527 .unwrap();
18528 let snap = e.snapshot();
18529 let e2 = Engine::restore_envelope(&snap).unwrap();
18530 assert_eq!(e2.publications().len(), 2);
18531 let p1 = e2.publications().get("p1").cloned();
18532 let Some(spg_sql::ast::PublicationScope::ForTables(ts)) = p1 else {
18533 panic!("p1 scope lost: {p1:?}")
18534 };
18535 assert_eq!(ts, alloc::vec!["t1".to_string(), "t2".to_string()]);
18536 let p2 = e2.publications().get("p2").cloned();
18537 let Some(spg_sql::ast::PublicationScope::AllTablesExcept(ts)) = p2 else {
18538 panic!("p2 scope lost: {p2:?}")
18539 };
18540 assert_eq!(ts, alloc::vec!["bad".to_string(), "worse".to_string()]);
18541 }
18542
18543 #[test]
18546 fn create_subscription_lands_in_catalog_with_defaults() {
18547 let mut e = Engine::new();
18548 e.execute(
18549 "CREATE SUBSCRIPTION sub_a CONNECTION 'host=127.0.0.1 port=20002' PUBLICATION pub_a",
18550 )
18551 .unwrap();
18552 let s = e.subscriptions().get("sub_a").cloned().expect("present");
18553 assert_eq!(s.conn_str, "host=127.0.0.1 port=20002");
18554 assert_eq!(s.publications, alloc::vec!["pub_a".to_string()]);
18555 assert!(s.enabled);
18556 assert_eq!(s.last_received_pos, 0);
18557 }
18558
18559 #[test]
18560 fn create_subscription_duplicate_name_errors() {
18561 let mut e = Engine::new();
18562 e.execute("CREATE SUBSCRIPTION s CONNECTION 'host=x' PUBLICATION p")
18563 .unwrap();
18564 let err = e
18565 .execute("CREATE SUBSCRIPTION s CONNECTION 'host=y' PUBLICATION p")
18566 .unwrap_err();
18567 assert!(
18568 alloc::format!("{err:?}").contains("DuplicateName"),
18569 "got {err:?}"
18570 );
18571 }
18572
18573 #[test]
18574 fn drop_subscription_silent_when_absent() {
18575 let mut e = Engine::new();
18576 let r = e.execute("DROP SUBSCRIPTION never").unwrap();
18577 match r {
18578 QueryResult::CommandOk { affected, .. } => assert_eq!(affected, 0),
18579 other => panic!("expected CommandOk, got {other:?}"),
18580 }
18581 }
18582
18583 #[test]
18584 fn subscription_advance_updates_last_pos_monotone() {
18585 let mut e = Engine::new();
18586 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18587 .unwrap();
18588 assert!(e.subscription_advance("s", 100));
18589 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18590 assert!(e.subscription_advance("s", 50)); assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 100);
18592 assert!(e.subscription_advance("s", 200));
18593 assert_eq!(e.subscriptions().get("s").unwrap().last_received_pos, 200);
18594 assert!(!e.subscription_advance("missing", 1));
18595 }
18596
18597 #[test]
18598 fn show_subscriptions_returns_rows_ordered_by_name() {
18599 let mut e = Engine::new();
18600 e.execute("CREATE SUBSCRIPTION z_sub CONNECTION 'h=x' PUBLICATION p1, p2")
18601 .unwrap();
18602 e.execute("CREATE SUBSCRIPTION a_sub CONNECTION 'h=y' PUBLICATION p3")
18603 .unwrap();
18604 let r = e.execute_readonly("SHOW SUBSCRIPTIONS").unwrap();
18605 let QueryResult::Rows { rows, columns } = r else {
18606 panic!()
18607 };
18608 assert_eq!(rows.len(), 2);
18609 assert_eq!(columns.len(), 5);
18610 assert_eq!(columns[0].name, "name");
18611 assert_eq!(columns[4].name, "last_received_pos");
18612 let names: Vec<&str> = rows
18614 .iter()
18615 .map(|r| {
18616 if let Value::Text(s) = &r.values[0] {
18617 s.as_str()
18618 } else {
18619 panic!()
18620 }
18621 })
18622 .collect();
18623 assert_eq!(names, alloc::vec!["a_sub", "z_sub"]);
18624 assert_eq!(rows[0].values[1], Value::Text("h=y".to_string()));
18626 assert_eq!(rows[0].values[2], Value::Text("p3".to_string()));
18627 assert_eq!(rows[0].values[3], Value::Bool(true));
18628 assert_eq!(rows[0].values[4], Value::BigInt(0));
18629 assert_eq!(rows[1].values[2], Value::Text("p1, p2".to_string()));
18631 }
18632
18633 #[test]
18634 fn subscriptions_persist_across_snapshot_envelope_v4() {
18635 let mut e = Engine::new();
18636 e.execute("CREATE SUBSCRIPTION s1 CONNECTION 'h=A' PUBLICATION p1, p2")
18637 .unwrap();
18638 e.execute("CREATE SUBSCRIPTION s2 CONNECTION 'h=B' PUBLICATION p3")
18639 .unwrap();
18640 e.subscription_advance("s2", 42);
18641 let snap = e.snapshot();
18642 let e2 = Engine::restore_envelope(&snap).unwrap();
18643 assert_eq!(e2.subscriptions().len(), 2);
18644 let s1 = e2.subscriptions().get("s1").unwrap();
18645 assert_eq!(s1.conn_str, "h=A");
18646 assert_eq!(
18647 s1.publications,
18648 alloc::vec!["p1".to_string(), "p2".to_string()]
18649 );
18650 assert_eq!(s1.last_received_pos, 0);
18651 let s2 = e2.subscriptions().get("s2").unwrap();
18652 assert_eq!(s2.last_received_pos, 42);
18653 }
18654
18655 #[test]
18656 fn v3_envelope_loads_with_empty_subscriptions() {
18657 let mut e = Engine::new();
18661 e.execute("CREATE PUBLICATION pub_legacy").unwrap();
18662 let catalog = e.catalog.serialize();
18663 let users = crate::users::serialize_users(&e.users);
18664 let pubs = e.publications.serialize();
18665 let mut buf = Vec::new();
18666 buf.extend_from_slice(b"SPGENV01");
18667 buf.push(3u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18669 buf.extend_from_slice(&catalog);
18670 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18671 buf.extend_from_slice(&users);
18672 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18673 buf.extend_from_slice(&pubs);
18674 let crc = spg_crypto::crc32::crc32(&buf);
18675 buf.extend_from_slice(&crc.to_le_bytes());
18676
18677 let e2 = Engine::restore_envelope(&buf).expect("v3 envelope restores under v4 reader");
18678 assert!(e2.subscriptions().is_empty());
18679 assert!(e2.publications().contains("pub_legacy"));
18680 }
18681
18682 #[test]
18683 fn create_subscription_allowed_inside_transaction() {
18684 let mut e = Engine::new();
18685 e.execute("BEGIN").unwrap();
18686 e.execute("CREATE SUBSCRIPTION s CONNECTION 'h=x' PUBLICATION p")
18687 .unwrap();
18688 e.execute("COMMIT").unwrap();
18689 assert!(e.subscriptions().contains("s"));
18690 }
18691
18692 #[test]
18694 fn analyze_populates_histogram_bounds() {
18695 let mut e = Engine::new();
18696 e.execute("CREATE TABLE t (id INT NOT NULL, name TEXT)")
18697 .unwrap();
18698 for i in 0..50 {
18699 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'name{i}')"))
18700 .unwrap();
18701 }
18702 e.execute("ANALYZE t").unwrap();
18703 let stats = e.statistics();
18704 let id_stats = stats.get("t", "id").unwrap();
18705 assert!(id_stats.histogram_bounds.len() >= 2);
18706 assert_eq!(id_stats.histogram_bounds.first().unwrap(), "0");
18707 assert_eq!(id_stats.histogram_bounds.last().unwrap(), "49");
18708 assert!((id_stats.null_frac - 0.0).abs() < 1e-6);
18709 assert_eq!(id_stats.n_distinct, 50);
18710 }
18711
18712 #[test]
18713 fn reanalyze_overwrites_prior_stats() {
18714 let mut e = Engine::new();
18715 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18716 for i in 0..10 {
18717 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18718 .unwrap();
18719 }
18720 e.execute("ANALYZE t").unwrap();
18721 let n1 = e.statistics().get("t", "id").unwrap().n_distinct;
18722 assert_eq!(n1, 10);
18723 for i in 10..30 {
18724 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18725 .unwrap();
18726 }
18727 e.execute("ANALYZE t").unwrap();
18728 let n2 = e.statistics().get("t", "id").unwrap().n_distinct;
18729 assert_eq!(n2, 30);
18730 }
18731
18732 #[test]
18733 fn analyze_unknown_table_errors() {
18734 let mut e = Engine::new();
18735 let err = e.execute("ANALYZE nonexistent").unwrap_err();
18736 assert!(matches!(
18737 err,
18738 EngineError::Storage(StorageError::TableNotFound { .. })
18739 ));
18740 }
18741
18742 #[test]
18743 fn bare_analyze_covers_all_user_tables() {
18744 let mut e = Engine::new();
18745 e.execute("CREATE TABLE t1 (id INT NOT NULL)").unwrap();
18746 e.execute("CREATE TABLE t2 (name TEXT NOT NULL)").unwrap();
18747 e.execute("INSERT INTO t1 VALUES (1)").unwrap();
18748 e.execute("INSERT INTO t2 VALUES ('alice')").unwrap();
18749 let r = e.execute("ANALYZE").unwrap();
18750 match r {
18751 QueryResult::CommandOk {
18752 affected,
18753 modified_catalog,
18754 } => {
18755 assert_eq!(affected, 2);
18756 assert!(modified_catalog);
18757 }
18758 other => panic!("expected CommandOk, got {other:?}"),
18759 }
18760 assert!(e.statistics().get("t1", "id").is_some());
18761 assert!(e.statistics().get("t2", "name").is_some());
18762 }
18763
18764 #[test]
18765 fn select_from_spg_statistic_returns_rows_per_column() {
18766 let mut e = Engine::new();
18767 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18768 .unwrap();
18769 e.execute("INSERT INTO t VALUES (1, 'a')").unwrap();
18770 e.execute("INSERT INTO t VALUES (2, 'b')").unwrap();
18771 e.execute("ANALYZE t").unwrap();
18772 let r = e.execute_readonly("SELECT * FROM spg_statistic").unwrap();
18773 let QueryResult::Rows { rows, columns } = r else {
18774 panic!()
18775 };
18776 assert_eq!(columns.len(), 6);
18778 assert_eq!(columns[0].name, "table_name");
18779 assert_eq!(columns[4].name, "histogram_bounds");
18780 assert_eq!(columns[5].name, "cold_row_count");
18781 assert_eq!(rows.len(), 2, "one row per column of t");
18782 match (&rows[0].values[0], &rows[0].values[1]) {
18784 (Value::Text(t), Value::Text(c)) => {
18785 assert_eq!(t, "t");
18786 assert_eq!(c, "id");
18788 }
18789 _ => panic!(),
18790 }
18791 }
18792
18793 #[test]
18794 fn analyze_skips_vector_columns() {
18795 let mut e = Engine::new();
18798 e.execute("CREATE TABLE t (id INT NOT NULL, v VECTOR(3) NOT NULL)")
18799 .unwrap();
18800 e.execute("INSERT INTO t VALUES (1, [1, 2, 3])").unwrap();
18801 e.execute("ANALYZE t").unwrap();
18802 assert!(e.statistics().get("t", "id").is_some());
18803 assert!(e.statistics().get("t", "v").is_none());
18804 }
18805
18806 #[test]
18807 fn statistics_persist_across_envelope_v5_round_trip() {
18808 let mut e = Engine::new();
18809 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18810 for i in 0..20 {
18811 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18812 .unwrap();
18813 }
18814 e.execute("ANALYZE").unwrap();
18815 let snap = e.snapshot();
18816 let e2 = Engine::restore_envelope(&snap).unwrap();
18817 let s = e2.statistics().get("t", "id").unwrap();
18818 assert_eq!(s.n_distinct, 20);
18819 }
18820
18821 #[test]
18824 fn auto_analyze_threshold_fires_after_10pct_of_min_rows_on_small_table() {
18825 let mut e = Engine::new();
18829 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18830 for i in 0..9 {
18831 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18832 .unwrap();
18833 }
18834 assert!(e.tables_needing_analyze().is_empty(), "9 < threshold");
18835 e.execute("INSERT INTO t VALUES (9)").unwrap();
18836 let needs = e.tables_needing_analyze();
18837 assert_eq!(needs, alloc::vec!["t".to_string()]);
18838 }
18839
18840 #[test]
18841 fn auto_analyze_threshold_uses_10pct_of_row_count_for_large_tables() {
18842 let mut e = Engine::new();
18848 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18849 for i in 0..1000 {
18850 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18851 .unwrap();
18852 }
18853 e.execute("ANALYZE t").unwrap();
18854 assert!(e.tables_needing_analyze().is_empty(), "fresh ANALYZE");
18855 for i in 1000..1050 {
18856 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18857 .unwrap();
18858 }
18859 assert!(
18860 e.tables_needing_analyze().is_empty(),
18861 "50 inserts < threshold of ~105"
18862 );
18863 for i in 1050..1200 {
18864 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18865 .unwrap();
18866 }
18867 assert_eq!(
18868 e.tables_needing_analyze(),
18869 alloc::vec!["t".to_string()],
18870 "200 inserts > 0.1 × 1200 threshold"
18871 );
18872 }
18873
18874 #[test]
18875 fn auto_analyze_threshold_resets_after_analyze() {
18876 let mut e = Engine::new();
18877 e.execute("CREATE TABLE t (id INT NOT NULL)").unwrap();
18878 for i in 0..200 {
18879 e.execute(&alloc::format!("INSERT INTO t VALUES ({i})"))
18880 .unwrap();
18881 }
18882 assert!(!e.tables_needing_analyze().is_empty());
18883 e.execute("ANALYZE").unwrap();
18884 assert!(
18885 e.tables_needing_analyze().is_empty(),
18886 "ANALYZE must reset the counter"
18887 );
18888 }
18889
18890 #[test]
18891 fn auto_analyze_threshold_tracks_updates_and_deletes() {
18892 let mut e = Engine::new();
18893 e.execute("CREATE TABLE t (id INT NOT NULL, label TEXT)")
18894 .unwrap();
18895 for i in 0..50 {
18896 e.execute(&alloc::format!("INSERT INTO t VALUES ({i}, 'x')"))
18897 .unwrap();
18898 }
18899 e.execute("ANALYZE t").unwrap();
18900 e.execute("UPDATE t SET label = 'y' WHERE id < 20").unwrap();
18903 e.execute("DELETE FROM t WHERE id >= 45").unwrap();
18904 assert_eq!(e.tables_needing_analyze(), alloc::vec!["t".to_string()]);
18905 }
18906
18907 #[test]
18908 fn v4_envelope_loads_with_empty_statistics() {
18909 let mut e = Engine::new();
18913 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
18914 .unwrap();
18915 let catalog = e.catalog.serialize();
18916 let users = crate::users::serialize_users(&e.users);
18917 let pubs = e.publications.serialize();
18918 let subs = e.subscriptions.serialize();
18919 let mut buf = Vec::new();
18920 buf.extend_from_slice(b"SPGENV01");
18921 buf.push(4u8);
18922 buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18923 buf.extend_from_slice(&catalog);
18924 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18925 buf.extend_from_slice(&users);
18926 buf.extend_from_slice(&u32::try_from(pubs.len()).unwrap().to_le_bytes());
18927 buf.extend_from_slice(&pubs);
18928 buf.extend_from_slice(&u32::try_from(subs.len()).unwrap().to_le_bytes());
18929 buf.extend_from_slice(&subs);
18930 let crc = spg_crypto::crc32::crc32(&buf);
18931 buf.extend_from_slice(&crc.to_le_bytes());
18932 let e2 = Engine::restore_envelope(&buf).expect("v4 envelope restores");
18933 assert!(e2.statistics().is_empty());
18934 }
18935
18936 #[test]
18937 fn v1_v2_envelope_loads_with_empty_publications() {
18938 let mut e = Engine::new();
18945 e.create_user("alice", "secret", crate::users::Role::ReadOnly, [0u8; 16])
18948 .unwrap();
18949
18950 let catalog = e.catalog.serialize();
18952 let users = crate::users::serialize_users(&e.users);
18953 let mut buf = Vec::new();
18954 buf.extend_from_slice(b"SPGENV01");
18955 buf.push(2u8); buf.extend_from_slice(&u32::try_from(catalog.len()).unwrap().to_le_bytes());
18957 buf.extend_from_slice(&catalog);
18958 buf.extend_from_slice(&u32::try_from(users.len()).unwrap().to_le_bytes());
18959 buf.extend_from_slice(&users);
18960 let crc = spg_crypto::crc32::crc32(&buf);
18961 buf.extend_from_slice(&crc.to_le_bytes());
18962
18963 let e2 = Engine::restore_envelope(&buf).expect("v2 envelope restores");
18964 assert!(e2.publications().is_empty());
18965 }
18966}